<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Stuff And Code</title>
        <link>https://stuffandcode.writizzy.blog</link>
        <description>Blog posts from Stuff And Code</description>
        <lastBuildDate>Fri, 11 Sep 2026 18:46:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Writizzy</generator>
        <language>en</language>
        <copyright>All rights reserved 2026, Stuff And Code</copyright>
        <item>
            <title><![CDATA[[FR] Et si le code, c'était de l'art ? ]]></title>
            <link>https://stuffandcode.writizzy.blog/p/fr-et-si-le-code-cetait-de-lart</link>
            <guid>https://stuffandcode.writizzy.blog/p/fr-et-si-le-code-cetait-de-lart</guid>
            <pubDate>Sat, 27 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Un p'tit changement de perspective sur le coding, l'AI etc ...]]></description>
            <content:encoded><![CDATA[<p>Je me souviens toujours, quand j&#39;ai commencé à coder il y a maintenant quelques années, un gars dont j&#39;ai oublié le nom disait :<br><strong>&quot;Writing code is writing stories for the computer to understand.&quot;</strong></p>
<p>Et je ne sais pas pourquoi, mais ça m&#39;a plutôt marqué.</p>
<p>Tellement, que dans mes premiers scripts pour le boulot, je commençais le script avec un commentaire <strong>&quot;# You Write Stories&quot;</strong> pour me rappeler.</p>
<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!UTLh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F549b238e-1ae3-47da-86b3-37da7004ba11_549x319.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731000917-549b238e-1ae3-47da-86b3-37da7004ba11_549x319.png)</a> Extrait de code que j&#39;ai retrouvé dans mes archives</p>
<br />

<p>Avec the rise of AI, on prompt ce qu&#39;on veut voir comme résultat, mais on choisit plus vraiment le chemin. C&#39;est comme demander à un chauffeur de nous emmener d&#39;un point A à un point B, mais par contre on a aucune idée de comment on va s&#39;y rendre, ni dans quel état on va arriver, on sait juste qu&#39;on arrivera.</p>
<br />

<p>Je ne vais pas m&#39;éterniser sur les sujets de la AI fatigue, sur le fait que finalement on passe des dizaines de fois plus de temps à review du code plutôt qu&#39;à raisonner et l&#39;écrire, se l&#39;approprier, de la perte de sens ...</p>
<p><strong>Mais finalement, est-ce que c&#39;est worth d&#39;apprendre tous ces concepts si l&#39;IA peut tout faire pour nous ?</strong></p>
<h2>L&#39;art vs le code</h2>
<p>Après réflexion, <strong>l&#39;IA pour le code, c&#39;est un peu comme l&#39;IA pour le design</strong>. Ça peut faire le taff, mais t&#39;as un million de manières de faire pour arriver à un résultat.</p>
<p>Puis, même si les images faites par IA sont stylées et que ça se fait en deux secondes. Ça ne sera jamais aussi original que si c&#39;était fait par un humain, et puis, une image générée par IA ça se voit, et ça montre vite ses limites. Si tu veux un truc HYPER spécifique, t&#39;es pas sorti de l&#39;auberge.</p>
<p>Les personnes qui savent à peu prés comment fonctionne un LLM savent que par définition, c&#39;est pas fait pour être original. Ils sont codés pour te donner la réponse la plus probable en fonction du contexte qu&#39;ils possèdent.</p>
<br />

<p><strong>Et si le code c&#39;était pareil ?</strong> Lorsqu&#39;on prompt, on dit ce qu&#39;on veut faire, parfois on guide en donnant une direction. Par exemple, quel langage, quel framework, quelles libs, l&#39;architecture globale.</p>
<p>Mais si on voulait vraiment, on pourrait prendre la casquette d&#39;artiste, et faire un truc qui sort des sentiers battus.</p>
<p><strong>Disclaimer:</strong></p>
<p><em>(bon, des fois c&#39;est bien de rester classique, on va pas se mentir, si c&#39;est pour devenir le Van Gogh du code et se couper l&#39;oreille, c&#39;est pas trop worth)</em></p>
<br />

<p><strong>Donc à tous ceux qui se demandent si ça sert toujours d&#39;apprendre à coder, apprendre les concepts etc</strong>.. Je dirais oui, car finalement, l&#39;IA répond à la question qu&#39;on lui pose, genre &quot;réalise ceci&quot;.</p>
<p>Mais le <strong>COMMENT</strong>, c&#39;est à vous, l&#39;artiste, de décider comment. Vous faites votre propre palette de couleurs, et vous créez.</p>
<br />

<h2>Un exemple basique</h2>
<p>Si demain j&#39;voulais réaliser une web app qui va récupérer des flux RSS sur des blogs et me les afficher, j&#39;pourrai le faire comme ceci. Ça fonctionne, c&#39;est simple. Mais, on pourrait faire de pleins de manières différentes.<br><a href="https://substackcdn.com/image/fetch/$s_!t249!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16a5ba13-9bf5-44bc-b85a-5d1687dabff6_1920x803.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731001581-16a5ba13-9bf5-44bc-b85a-5d1687dabff6_1920x803.png)</a> Version simpliste d&#39;un news feed system design</p>
<br />

<p>On pourrait décider d&#39;ajouter de nouveaux components, pour avoir une architecture plus robuste ou de meilleure performances.<br><a href="https://substackcdn.com/image/fetch/$s_!SZbS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2220a850-21d2-4066-b2c9-6d36a0df510f_1920x800.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731002708-2220a850-21d2-4066-b2c9-6d36a0df510f_1920x800.png)</a> Version plus robuste mais plus compliquée</p>
<br />

<p>Ou on pourrait tout simplement faire un truc abominable, mais qui marcherait et qui demanderait beaucoup moins de setup.<br><a href="https://substackcdn.com/image/fetch/$s_!Nmz4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fde0fe8-5faf-4c65-a260-05986bcf9377_1920x788.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731003132-4fde0fe8-5faf-4c65-a260-05986bcf9377_1920x788.png)</a> Don&#39;t do this plz</p>
<p><em>Les images dessus ont été réalisées grâce à mon tool <a href="https://polydraw.io/">https://polydraw.io</a> . C&#39;est un fork de Excalidraw avec des features en plus, des shortcuts sympas et une autre vibe.</em></p>
<br />

<h2>Le bonus</h2>
<p>Il y a tellement de gens qui perdent du sens, qui vont arrêter d&#39;apprendre, les gens vont s&#39;en remettre de plus en plus à l&#39;IA. Ils vont perdre en patience, résilience et gestion de la frustration et en connaissance. Toutes ces qualités essentielles pour un dev&#39;.</p>
<p>Ils vont devenir rouillés.</p>
<p>Finalement, je pense que d&#39;ici quelques temps, un bon niveau d&#39;il y a quelques années sera un très bon niveau demain. Parce que la barre aura baissé.</p>
<p>C&#39;est mon opinion, I guess we&#39;ll see.</p>
<br />

<p>Bref, c&#39;était un p&#39;tit exercice d&#39;écriture aussi, ça faisait un p&#39;tit moment que je ne l&#39;avais pas fait. J&#39;ai d&#39;autres sujets, plus techniques sur lesquels je voudrais bien écrire, mais il me reste à trouver le bon format / média.</p>
<br />

<p>See you!</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Profiling speed, memory & SQL : what tools ? Part 1]]></title>
            <link>https://stuffandcode.writizzy.blog/p/profiling-speed-memory-and-sql-what</link>
            <guid>https://stuffandcode.writizzy.blog/p/profiling-speed-memory-and-sql-what</guid>
            <pubDate>Wed, 02 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[There are many tools available for profiling Python code, each with its own strengths and weaknesses. Let’s take a look at which tool is best suited to which use case.]]></description>
            <content:encoded><![CDATA[<br />

<p>We&#39;ll be running profiling on the same code with different tools so you can see the output generated. At the end of this article we&#39;ll make a comparison of the tools. And when to use which one.</p>
<br />

<h2>The tools</h2>
<ul>
<li><p><strong><a href="https://github.com/joerick/pyinstrument">PyInstrument</a></strong> : Statistical profiler, great for getting a <strong>high-level overview</strong> of what your code is doing.</p>
</li>
<li><p><strong><a href="https://docs.python.org/3/library/profile.html#module-cProfile">cProfile</a></strong> : Deterministic profiler, useful for getting a <strong>detailed breakdown of every function call</strong>.</p>
</li>
<li><p><strong><a href="https://github.com/benfred/py-spy/">Py-Spy</a></strong> : Sampling profiler that can <strong>attach to running processes</strong> , great for <strong>production debugging</strong>.</p>
</li>
<li><p><strong><a href="https://github.com/plasma-umass/scalene">Scalene</a></strong> : A powerful profiler that tracks <strong>CPU time, wall time, and memory</strong>.</p>
</li>
<li><p><code>time</code><strong>(Linux shell)</strong> : Simple but effective for measuring <strong>total runtime</strong>, including imports, I/O, and startup overhead.</p>
</li>
</ul>
<br />

<h2>The code we&#39;ll profile</h2>
<p><strong><a href="https://adventofcode.com/2024/day/2">Day 2 of Advent of Code 2024</a></strong> (yeah, shame on me, I only did Day 1 last year, so I did the second day for this example...)</p>
<br />


<p>I&#39;ve implemented a solution to the <strong>second part</strong> of the challenge (usually the trickier one).  </p>
<p>To better highlight potential optimizations (and make profiling results more meaningful), I&#39;ve deliberately increased the input size, so we can observe <strong>where time or memory is being consumed</strong>.</p>
<br />

<p>Here&#39;s what I came up with, it could be improved (that&#39;s the point) but it works.<br><a href="https://substackcdn.com/image/fetch/$s_!sxo3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65b261b4-a663-4863-addb-4ffc14db39f4_1736x2240.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731007651-65b261b4-a663-4863-addb-4ffc14db39f4_1736x2240.png)</a></p>
<br />

<br />

<h3>time</h3>
<p><em><strong>time python aoc2-2.py</strong></em><br><a href="https://substackcdn.com/image/fetch/$s_!reJ5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7635f4e9-1151-4095-8147-dd6a3a1104f7_1460x64.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731008157-7635f4e9-1151-4095-8147-dd6a3a1104f7_1460x64.png)</a></p>
<p>The classic <code>time</code> command gives us a quick look at how long the entire script took to execute. In this example, the total runtime is reported as <strong>2.850 seconds</strong> , with almost all of it spent in user space (<code>2.81s user</code>) and very little in system calls. This tool is extremely simple but useful to establish a performance baseline. <strong>However, it gives us no insight into</strong> <em><strong>what</strong></em> <strong>inside the program took time</strong>, just that it took time.</p>
<br />

<br />

<h3>PyInstrument:</h3>
<p><em><strong>pyinstrument -r html aoc2-2.py</strong></em><br><a href="https://substackcdn.com/image/fetch/$s_!y7ag!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a36f576-70d5-47d2-a734-74a11c1cd8cd_944x934.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731008865-4a36f576-70d5-47d2-a734-74a11c1cd8cd_944x934.png)</a></p>
<p>PyInstrument offers a <strong>view of where your code spent its time</strong> , using stack sampling. In the screenshot, we can see that most of the time is spent inside the <code>main</code> function, and more precisely inside a generator expression and the <code>check_is_safe</code> function. PyInstrument makes it easy to spot where time accumulates.  </p>
<p>Also, the interface is clear and beautiful.</p>
<br />

<br />

<h3>cProfile</h3>
<p><em><strong>python -m cProfile aoc2-2.py -s cumtime</strong></em><br><a href="https://substackcdn.com/image/fetch/$s_!Epdv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee566ae-a748-4fa8-b17b-451771466a16_2140x1210.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731009419-eee566ae-a748-4fa8-b17b-451771466a16_2140x1210.png)</a></p>
<p>With cProfile, we get a detailed view of function calls, how many times each was called, the total time spent, and how it contributes to cumulative runtime. In this case, <code>check_is_safe</code> was called over 5 million times, and <code>sorted</code> and <code>abs</code> were also heavily used. This profiler is very precise.</p>
<p>Still, it&#39;s great for seeing <strong>exact function-level cost</strong> and how often they&#39;re triggered.  </p>
<p>I personally like to start with PyInstrument to get a rough idea and then use cProfile. Since it&#39;s a deterministic profiler, the overhead is superior to tools like PyInstrument.</p>
<br />

<p><strong>Did you learn something ? Subscribe for free to receive new posts.</strong>  </p>
<br />

<br />

<h3>py-spy</h3>
<p><em><strong>sudo py-spy record -o profile.svg -- python aoc2-2.py</strong></em><br><a href="https://substackcdn.com/image/fetch/$s_!1ALp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2a8aa08-2057-4bed-9e3b-dbc99ef9d525_2480x548.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731010397-f2a8aa08-2057-4bed-9e3b-dbc99ef9d525_2480x548.png)</a></p>
<p>Py-Spy generates a <strong>flame graph</strong> , which visualizes stack traces over time. In this case, we see a wide block for <code>&lt;module&gt;</code> and <code>main</code>, then narrower blocks representing the generator expression and <code>check_is_safe</code>. This tool is great for <strong>profiling production code</strong> or long-running scripts without modifying source code or restarting processes. It gives a quick visual cue about <strong>where time is spent</strong>, though not as detailed as line-level profilers.</p>
<br />

<p>I like this tool, but I only use it when <strong>I need to debug something that is already running and I cannot restart</strong> or reproduce locally. It also could be used to see where a program is stuck. It has very low overhead, and run in a separate process, so it should not impact your production code.</p>
<br />

<h3>Scalene</h3>
<p><em><strong>scalene aoc2-2.py</strong></em><br><a href="https://substackcdn.com/image/fetch/$s_!NNet!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0b81289-ee5a-423f-be46-10947782f06c_3400x1792.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731010943-b0b81289-ee5a-423f-be46-10947782f06c_3400x1792.png)</a></p>
<p>Scalene combines <strong>CPU profiling, memory tracking, and even line-level timing</strong> into one HTML report. In the screenshot, we see that the <code>check_is_safe</code> function is a hotspot, and memory usage spikes within that block. It even highlights how much time is spent on each line. This tool is ideal when you want to understand not just <em>how long</em> things take, but also <strong>how much memory they consume</strong>, line by line.</p>
<br />

<br />

<h2>What would TK do ? My workflow.</h2>
<ul>
<li><p>Start with <code>time</code> if your script is easy to run and you just want a quick sense of how long it takes overall.</p>
<br />
</li>
<li><p>Then use <strong>PyInstrument</strong> to get a <strong>high-level overview</strong> of where the bottlenecks are. It&#39;s fast, has low overhead, and gives a clear call-stack timeline. It has amazing integrations with web frameworks and all kind of things you can run into.</p>
</li>
</ul>
<br />

<ul>
<li><p>If you want to <strong>dig deeper and optimize specific functions</strong> , reach for <strong>cProfile</strong>. It&#39;ll give you exact call counts and timings, which is great for tight loops or recursive code. The overhead is higher though.</p>
<br />
</li>
<li><p>When you&#39;re dealing with something running <strong>in production</strong> , or hard to reproduce locally, <strong>Py-Spy</strong> is your go-to tool. You can attach it to a live process without restarting anything.</p>
</li>
</ul>
<br />

<ul>
<li><p>And finally, if you want to get performance &amp; memory at the same time, <strong>Scalene</strong> gives you per-line CPU, wall time, and memory usage in a single report. I discovered this tool recently, I need to dig deeper.</p>
<br /></li>
</ul>
<p><strong>Performance is just one part of the story.</strong><br><strong>Next up</strong>: tackling memory usage, ORM madness, and other sneaky slowdowns. Stay tuned!</p>
<br />

<p><strong>Did you learn something ? Subscribe for free to receive new posts</strong>  </p>
<br />

<br />

]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Welcome to the Hall Of Fails : Part 1]]></title>
            <link>https://stuffandcode.writizzy.blog/p/welcome-to-the-hall-of-fails-part</link>
            <guid>https://stuffandcode.writizzy.blog/p/welcome-to-the-hall-of-fails-part</guid>
            <pubDate>Wed, 12 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Entrepreneurship is a journey, and I do have a lot of fails. Wanted to share some with you.]]></description>
            <content:encoded><![CDATA[<p>Yeah, so I&#39;ve been working on side projects for a while now. Listing everything I did and what went wrong would be too time consuming, but I can share some of it with you, and also it&#39;s kind of a reminder of the journey for me.   </p>
<p>Since I do freelance work, I only work a few months each year to have more time to spend on my projects. And in 2024, I decided to go all in. Meaning that from November 2023 to today, I&#39;ve only been working on my side projects.</p>
<br />

<p><strong>Let&#39;s start from early 2024:</strong></p>
<h2><strong>January 2024:</strong> <em>pinock.io</em></h2>
<p>For a few months, I had been working with a friend on a B2B tool called Pinock.io. It was designed for IT consulting firms (ESNs) that regularly present developer profiles to their clients. These companies often need to refine contractors resumes correcting errors, and reformatting them to match the company&#39;s branding and color scheme.</p>
<p>This process is tedious and time-consuming, so we built an automated tool to make it easier.</p>
<p>We started working on Pinock.io in mid&#39; 2023, but by January 2024, we decided to stop. We weren&#39;t aligned on the timeline anymore, and we had exhausted ourselves. So, January was still dedicated to this project, but that was the end of it.</p>
<p>No regrets, though. I learned a lot from the experience and took valuable lessons from it.<br><a href="https://substackcdn.com/image/fetch/$s_!bjAi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f8a1ceb-e81a-40be-b053-3a50cd0ab922_1280x800.png">![Pinock.io - Chrome Web Store](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731011313-0f8a1ceb-e81a-40be-b053-3a50cd0ab922_1280x800.png "Pinock.io - Chrome Web Store")</a></p>
<br />

<h2><strong>February &amp; Mars 2024</strong> : <em>blog.stuffandcode.com</em>(this blog)</h2>
<p>Over the years, I&#39;ve created a few blogs. I&#39;ve always liked sharing my knowledge and findings, and I&#39;ve tried multiple times to keep a blog going. But that kind of work isn&#39;t for me. Yeah, that&#39;s ironic since I&#39;m literally journaling on my blog right now.</p>
<p>The problem is, I get exhausted. I can&#39;t keep up with being consistent.</p>
<p>So I ran a blog for two months, focusing on Python specifically performance issues, how to identify them, and how to solve them. It&#39;s a topic I enjoy, and I wanted to create content in my mother tongue, French, since there are already amazing resources in English.</p>
<p>But I guess it was too niche. Python developers + performance issues + French readers... too narrow of an audience.</p>
<p>I eventually gave up because, why bother?</p>
<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!JqG6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb859ccfc-cccc-4b97-8aa7-2feb81ac6c87_1051x1157.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731012010-b859ccfc-cccc-4b97-8aa7-2feb81ac6c87_1051x1157.png)</a></p>
<br />

<br />

<h2><strong>March 2024:</strong> <em>Building a template for the next projects</em></h2>
<p>In March 2024, I came across a French entrepreneur who was launching SaaS projects at lightning speed. He really inspired me. He kept saying that you shouldn&#39;t spend too much time on ideas, just build, launch, see if it works, and repeat.</p>
<p>Like most people, every time I built something, I wanted it to be perfect: beautiful UI, no bugs, scalable, fully tested. But doing that, you end up spending months instead of days or weeks. So I thought :<br><em>I&#39;ve been doing things the same way for years, and it never worked. Let&#39;s try something different.</em></p>
<p>TK 2024</p>
<p>At first, it was hard. It felt messy. No tests? Come on bro&#39;?</p>
<p>Some parts of my code looked awful and could&#39;ve been refactored, but they worked, so why spend more time on something that would most likely never even be used? The first few weeks were rough. It didn&#39;t feel natural.</p>
<p><strong>So I made a rule</strong> : from now on, my projects should take <strong>no more than two weeks to build</strong>.</p>
<p>So I&#39;ve decided to take time to build an awesome template for my future projects. I&#39;ve setup everything I need, here&#39;s a glimpse of the tech&#39; stack.</p>
<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!Dq1h!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadcf502a-63b5-4ba8-99a5-c5e2e3fe60e1_1428x1133.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731012584-adcf502a-63b5-4ba8-99a5-c5e2e3fe60e1_1428x1133.png)</a></p>
<p>(I built this tool early 2025, it&#39;s free to use, enjoy <a href="https://weuse.dev">https://weuse.dev</a>)</p>
<p>You can find out my board <strong><a href="https://weuse.dev/b/j4Z8zeK6">here</a>.</strong></p>
<br />

<p>I&#39;ve setup all the sign up login logic, payments hooks, metrics, user management, monitoring, deployments ...</p>
<p>All I need to do is fork it, rename the occurrences and get started. And to deploy, it literally takes me 10 minutes, 8 to setup de VPS and 2 to deploy with Docker Compose, and Docker Swarm if I need horizontal scaling.</p>
<br />

<h2><strong>April 2024</strong> : <em>Interactive-Tailwind.com</em></h2>
<br />

<p>Backend and deployment weren&#39;t really an issue for me, but the frontend? That&#39;s what took most of my time. I really didn&#39;t like it. And I sucked at it. Everything I built looked terrible and took me way too long.</p>
<p>I tried Vue.js, React, plain JavaScript, TypeScript... but none of them clicked. Then I discovered Svelte. It had a small learning curve, but it suited me well, plain JS, no unnecessary complexity. It was a good starting point.</p>
<p>But the real monster was still ahead.</p>
<h3><strong>CSS.</strong></h3>
<p>I&#39;ve always hated it. I never got the hang of it.</p>
<p>I tried multiple times, bought a few courses, watched online tutorials. I managed to do some things, but the effort never felt worth the result. So I decided to try something different.</p>
<h3><strong>Tailwind.</strong></h3>
<p>I liked the idea of not leaving my HTML, of not having to name my elements with class names... but in the end, it was still CSS. So, back to square one.</p>
<p>I looked for practical online exercises to learn Tailwind, but I couldn&#39;t find anything that suited me. Then I remembered</p>
<br />

<p><em>The best way to learn is to teach others.</em></p>
<p>TK 2024 (Again)</p>
<p>So there I was, building a course to learn Tailwind CSS from scratch, even though I had no knowledge of it myself.</p>
<p>The UI looked terrible, but the concept was cool. I showed a target design, and users had to write Tailwind code to match it and move on.</p>
<br />

<p>Two weeks passed. I integrated payments and launched on Product Hunt.<br><a href="https://substackcdn.com/image/fetch/$s_!HkJ0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcbb3300f-146a-468d-9d3e-8ac8f67c3b6c_1018x130.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731012874-cbb3300f-146a-468d-9d3e-8ac8f67c3b6c_1018x130.png)</a></p>
<br />

<p>Yeah, pretty sad. But I did not care, I learned a lot, I saw the whole process and I was ready to go on the next one. And I managed to get it done under 2 weeks. Success.</p>
<p>P.S: Don&#39;t be too sad, because actually, since then, <a href="https://interactive-tailwind.com/">https://interactive-tailwind.com</a> has become my most successful project. But it had a lot of revamping throughout the months. That&#39;s for next time.</p>
<p>Here&#39;s a sneak peek on how it looks like today :<br><a href="https://substackcdn.com/image/fetch/$s_!MbYU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93e637b3-6ff7-4e8d-a91e-edb1176a8a5a_865x1016.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731013228-93e637b3-6ff7-4e8d-a91e-edb1176a8a5a_865x1016.png)</a></p>
<br />

<br />

<h2><strong>May 2024 :</strong> <em>PushBeacon.com</em></h2>
<br />


<p>To collect payments, I went with LemonSqueezy instead of Stripe because they act as a Merchant of Record (MoR). Basically, they handle all the tax stuff for you, which is a lifesaver when selling internationally. Doing it yourself is a real pain.</p>
<p>The problem is, LemonSqueezy doesn&#39;t have a mobile app. So I had no way of knowing when I got paid unless I kept checking my emails. And yeah, no way I was doing that.</p>
<p>I started looking for an easy way to send custom notifications to my phone. I didn&#39;t want to set up a Discord or Slack bot, and I keep most notifications disabled to avoid distractions, so I needed something else.</p>
<p>Then I thought, why not just build a super simple mobile app whose only job is to receive notifications I send it? Sounds fun. So I opened PyCharm, set up React Native with Expo, and after some <em>bibidi bobbidi boo</em>, I had an app in two weeks.</p>
<p>Releasing it on the stores was a nightmare, especially on iOS. I also spent way too much time on the landing page because I wanted users to instantly get what it was about, try it out, and see how in just a few seconds and a simple HTTP request, they could send notifications to their phones, tablets, or smartwatches.</p>
<p>Then I launched it on Product Hunt :<br><a href="https://substackcdn.com/image/fetch/$s_!pd1l!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febd1c919-226b-40c7-ab9c-2294251b46ed_1022x122.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731013751-ebd1c919-226b-40c7-ab9c-2294251b46ed_1022x122.png)</a></p>
<br />

<p>Daaamn! That&#39;s what I&#39;m talking about! And the best part? I got my first two payments ever for this. It was a lifetime deal, 45€ each. Not crazy money, I know, but that psychological barrier was gone. I finally made money from something I built.</p>
<p>A few people subscribed to the monthly plan too, but it didn&#39;t take long before things went flat. Today, it&#39;s mostly just me and some friends using it to set up custom alerts for our own tools. I don&#39;t really plan on maintaining it, especially when Apple asks for 100 bucks a year just to keep the app live on the store.</p>
<p>So yeah, I&#39;ll keep using it as long as it works, but I&#39;m not putting any more dev time into it.</p>
<br />

<p><strong>Almost on Time... But Mobile is a Different Beast</strong></p>
<p>The initial goal was to ship it in two weeks, and honestly, we were almost there. The app was up and running within that time. But mobile isn&#39;t like pure web SaaS. Getting approved on the stores, dealing with app signing, waiting for Apple&#39;s review it all adds extra delays. That alone stretched the timeline a bit more than expected.</p>
<p>It made me realize that launching a mobile app is a whole different game compared to a simple web product. Would I do it again? Maybe, not haha.</p>
<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!3M5X!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656045ea-d6ad-47fb-b7eb-ec7b452d715c_805x971.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731014219-656045ea-d6ad-47fb-b7eb-ec7b452d715c_805x971.png)</a></p>
<br />

<h2><strong>May &amp; June 2024</strong> : <em>Make-Landing.com</em></h2>
<p>No time to lose. I realized I was spending way too much time building landing pages, and when you look at it, most people use the same kind of layouts anyway. So why not create a landing page generator?</p>
<p>I went on a deep dive, scrolling and scraping a ton of beautiful websites built with Tailwind. I broke them down into components, Hero sections, Pricing, About, and so on.</p>
<p>Then I made those components available in VueJS, React, Svelte, and plain JS, and added a button to shuffle and generate landing pages with different themes.</p>
<p>You could just keep hitting the button until you found the perfect layout. Once you were happy with the result, you had to pay to download the source files.</p>
<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!J13t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e60fabf-e5b5-4307-8f6b-1e7b50d455fd_1213x600.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731014609-0e60fabf-e5b5-4307-8f6b-1e7b50d455fd_1213x600.png)</a></p>
<p>Decided to launch on Product Hunt :<br><a href="https://substackcdn.com/image/fetch/$s_!Q_0N!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F505f2665-38a1-492c-8628-008ca202ae0f_1026x128.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731015225-505f2665-38a1-492c-8628-008ca202ae0f_1026x128.png)</a></p>
<br />

<p>Back to square one. This project has been taken down since, it showed no interested, but again, my frontend skills improved.</p>
<br />

<h2><strong>June 2024:</strong> <em>Couldn&#39;t code much</em></h2>
<p>In June 2024, I had eye surgery on both eyes. For about two to three weeks, I couldn&#39;t really focus on screens. I was seeing double, which made it almost impossible to get anything done. So yeah, coding wasn&#39;t really an option that month, but I kept myself busy with other things.</p>
<br />

<h2><strong>July &amp; August 2024</strong> : <em>Palantly.com</em></h2>
<p>This time, I broke my own rule. I was spending the summer at a friend&#39;s place, so I wanted to enjoy it and take things slow.</p>
<p>As I started juggling multiple projects, I needed a way to track if everything was running smoothly. I had Plausible Analytics to monitor traffic on my websites, but I had no way of knowing if my backend services were actually working. Setting up backend analytics, with health checks, response times, and all that, is always a hassle. So I thought, why not build something simpler?</p>
<p>Like <em>waaay</em> simpler.</p>
<p>Something as easy as dropping a <code>&lt;script&gt;</code> tag on a website and that&#39;s it. Just like Plausible, but instead of tracking page views, it would track API calls and display everything in a clean, visual way. Nice graphs showing which routes were hit the most, how long they took, response statuses... all at a glance.</p>
<p>It was an interesting project because I got to play with new technologies, like ClickHouse. I went with it because I read some Plausible blog posts where they mentioned that Postgres wasn&#39;t ideal for this kind of workload long-term. A little pretentious to assume I&#39;d hit the same scaling issues, but hey, why not do it right from the start?</p>
<p>I spent the summer working on it. The UI was clean, I spent a lot of time finding the right graphs, and everything was running smoothly. Whenever a user browsed one of my websites, it would track API calls, measure their response times, and periodically send the stats to my backend. It worked well.</p>
<p>The logo looked amazing (thanks TK), so I was all set to launch on Product Hunt.</p>
<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!v6FO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69b9c7d2-588c-47ba-8040-4690a649d83b_1022x136.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731015496-69b9c7d2-588c-47ba-8040-4690a649d83b_1022x136.png)</a></p>
<br />

<p>Damn, that&#39;s harsh. But you know, as time goes by, more and more products are released on Product Hunt, there are a lot of competition, a lot of bots, so it&#39;s hard to have visibility. I kept using this for myself, it was useful for me. But today, I discontinued it. But i&#39;m proud of it.<br><img src="https://stuffandcode.writizzy.blog/img/missing-image.png" alt="" /><br><a href="https://substackcdn.com/image/fetch/$s_!unFS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb16dcbd6-0e07-40aa-a9d5-9d73771e4d0c_2048x3046.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731015919-b16dcbd6-0e07-40aa-a9d5-9d73771e4d0c_2048x3046.png)</a></p>
<br />

<h2><strong>Wrapping Up -- Part 1</strong></h2>
<br />

<p>So yeah, we&#39;re only at August 2024, and there&#39;s still a lot more to cover. But this gives you a sneak peek into what I&#39;ve been up to these past months, just building, testing ideas, seeing what sticks, stopping, and repeating.</p>
<p>I won&#39;t lie, it&#39;s exhausting. I know I should take longer breaks sometimes to avoid burning out, but until I can actually make a living from these projects, I don&#39;t really have that luxury. I know I&#39;ll get there eventually, but it takes time, effort, perseverance... and a bit of luck.</p>
<p>Writing this down helps too.</p>
<p>Thanks for reading.</p>
<br />

<p>See you soon! 🦝</p>
<p>TK.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Symfoz - The return of the Feed]]></title>
            <link>https://stuffandcode.writizzy.blog/p/symfoz-the-return-of-the-feed</link>
            <guid>https://stuffandcode.writizzy.blog/p/symfoz-the-return-of-the-feed</guid>
            <pubDate>Tue, 04 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Yeah, it's happening ! ]]></description>
            <content:encoded><![CDATA[<p>Do you remember <strong>Symfoz</strong> ?</p>
<p>If you&#39;re an OG you remember this ⬇️<br><a href="https://substackcdn.com/image/fetch/$s_!rbhb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd483144a-b9fc-491e-a330-12ce2f2a9b6c_2048x1184.png">![w=2000 (2048×1184)](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731016420-d483144a-b9fc-491e-a330-12ce2f2a9b6c_2048x1184.png "w=2000 (2048×1184)")</a></p>
<br />

<p>I made this a while ago, like 3 years maybe, to try out to build something for me.  </p>
<p>But for the new comers, what&#39;s Symfoz ?  </p>
<p>Basically, it&#39;s an RSS aggregator. Where you could create custom feeds (<strong>playlists</strong> ) of content that you found interesting. Like resources about Python programming language 🙄 , or music, design etc... And everything is aggregated in this playlist, so you just have to check this.  </p>
<p>You were able to add RSS feeds ofc, but also <strong>Youtube channels</strong> , <strong>Spotify shows,</strong> <strong>Twitter</strong> handles ... And the best, you were able to share your playlist with other and follow other.</p>
<br />

<p>Anyhow, I was not good at frontend, the UX was not incredible, so I kept it online for my most loyal user (Hey mom 👋).</p>
<p>And decided to take it down a few months ago.   </p>
<p>But I felt sad about this.</p>
<br />


<p>So I&#39;ve decided to revamp this tool, to add new features, try out new libs, and improve. And the best part ? <strong>I want to take you with me in this adventure.</strong>   </p>
<br />


<p>See you soon !<br>If you don&#39;t want to miss new articles, just subscribe to my newsletter ! Or just wait for Symfoz to come out!  </p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Switch to English]]></title>
            <link>https://stuffandcode.writizzy.blog/p/switch-to-english</link>
            <guid>https://stuffandcode.writizzy.blog/p/switch-to-english</guid>
            <pubDate>Sat, 25 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Sorry to my french speakers, but we're too few :/ ]]></description>
            <content:encoded><![CDATA[<p>Yup, it&#39;s me!</p>
<p>I&#39;ve decided to pick up the blog where I left off. But when I reflected on what I&#39;d done previously, I realized it was &quot;too niche.&quot; By that, I mean it was aimed at:</p>
<ul>
<li><p>French-speaking people</p>
</li>
<li><p>Python developers (mostly)</p>
</li>
<li><p>Those interested in improving performance</p>
</li>
<li><p>Readers of articles, blogs, or content about the subject</p>
</li>
</ul>
<p>No need to say the target audience was pretty narrow. So I&#39;ve decided to work on these aspects, and the first decision is to switch to English.</p>
<p>Don&#39;t worry, I&#39;m not a native speaker, so I&#39;ll keep the vocabulary simple to make things easy to follow.</p>
<p>I&#39;m not entirely sure what I&#39;m going to write about yet. Maybe I&#39;ll document my journey building a SaaS, share examples of open-source contributions, or just post random stuff. I don&#39;t know. I guess we&#39;ll see.</p>
<br />

<p>Talk to you soon!</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Explosion de performance Python avec Rust]]></title>
            <link>https://stuffandcode.writizzy.blog/p/explosion-de-performance-python-avec</link>
            <guid>https://stuffandcode.writizzy.blog/p/explosion-de-performance-python-avec</guid>
            <pubDate>Tue, 26 Mar 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Intégrer du Rust dans du Python, quelle drôle d'idée ! Est-ce worth ?]]></description>
            <content:encoded><![CDATA[<p><a href="https://substackcdn.com/image/fetch/$s_!0t1V!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6e2da8a-0b8f-4ae6-a47d-7ca12913c895_1024x1024.webp">![Illustrate a concept where a Python snake wraps around and integrates Rust elements for enhancing performance. The scene should visually represent a Python application, symbolized by a large, vibrant Python snake, incorporating Rust's distinct features like the Rust logo and a recognizable Rustacean figure for optimizing specific performance bottlenecks. The Python snake should be seen actively engaging with these Rust elements, such as gears and code snippets, to signify the seamless incorporation of Rust into Python for performance improvements. This depiction should highlight the strategic use of Rust within a Python environment to address and enhance critical sections of the application, emphasizing the collaboration between the two programming languages through visual metaphors that clearly identify both Python's and Rust's contributions.](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731049754-b6e2da8a-0b8f-4ae6-a47d-7ca12913c895_1024x1024.webp "Illustrate a concept where a Python snake wraps around and integrates Rust elements for enhancing performance. The scene should visually represent a Python application, symbolized by a large, vibrant Python snake, incorporating Rust's distinct features like the Rust logo and a recognizable Rustacean figure for optimizing specific performance bottlenecks. The Python snake should be seen actively engaging with these Rust elements, such as gears and code snippets, to signify the seamless incorporation of Rust into Python for performance improvements. This depiction should highlight the strategic use of Rust within a Python environment to address and enhance critical sections of the application, emphasizing the collaboration between the two programming languages through visual metaphors that clearly identify both Python's and Rust's contributions.")</a></p>
<br />

<p>On entend souvent dire que Python est lent.</p>
<p>Mais entre nous, c&#39;est plus nuancé que ça. En optimisant le code, il est possible de faire des merveilles sans avoir à dire adieu à Python.</p>
<p>Bien sûr, il est évident que les <strong>langages compilés</strong> , genre Rust, <strong>ont une longueur d&#39;avance côté vitesse.</strong> Mais peu importe car Python peut embarquer ces langages tels que nous pouvons le voir dans des bibliothèques qui intègrent du <strong>C</strong> ou du <strong>Rust</strong> , comme <strong>Numpy</strong> ou <strong>Pydantic v2</strong>.</p>
<br />

<p><em><strong>Disclaimer</strong> : J&#39;ai commencé à faire du Rust très récemment, donc si j&#39;ai raté des optimisations ou des bonnes pratiques, c&#39;est normal.</em></p>
<br />

<h2>Notre challenge : Advent Of Code 2022</h2>
<br />


<p>Afin d&#39;apprendre un nouveau langage, je trouve qu&#39;il est pertinent de tenter de résoudre des problèmes concrets, et de monter en difficulté.</p>
<p>Pour ce faire j&#39;ai décidé de prendre le <strong>Advent Of Code de 2022</strong> et d&#39;essayer de résoudre le challenge une première fois en full Python et une seconde fois en Python qui intègre du Rust afin de comparer les performances.</p>
<p>Pour plus de détails sur le défi, vous pouvez <a href="https://adventofcode.com/2022/day/1">cliquer ici</a>.</p>
<p>Je n&#39;entrerai pas spécialement dans les détails techniques de la solution. Pour résumer l&#39;énoncé, voici l&#39;idée du challenge :</p>
<p>Nous avons une string contenant des calories pour différents elfes (univers de Noël). Nous devons trouver quel elfe possède le plus de calories. Le jeu de données ressemble à ceci :</p>
<pre><code>1000
233

1000
1000
6000

400
... 
</code></pre>
<p>Dans ce cas là, c&#39;est le deuxième elfe qui possède le plus de calories, avec 1000 + 1000 + 6 000 calories = 8 000.</p>
<p>Le résultat attendu est donc 8 000.</p>
<br />

<h2>Notre implémentation en Python</h2>
<p>Voilà, c&#39;est presque du one-line. Concrètement on itère sur chaque bloc représentant un elfe (les double &quot;\n&quot;), on prend chaque ligne que nous convertissons en entier puis les additionnons ensemble.  </p>
<p>Enfin, nous ordonnons la liste de sommes afin d&#39;avoir le plus grand nombre en premier .<br><a href="https://substackcdn.com/image/fetch/$s_!Ilg4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc0255b1-0b12-4e49-93c0-c410f8cc4acb_1488x372.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731050106-bc0255b1-0b12-4e49-93c0-c410f8cc4acb_1488x372.png)</a></p>
<p>Tadaaaa ! Nous savons quel est le maximum de calories parmi ces elfes.</p>
<br />

<h2>Réécrire notre logique en Rust</h2>
<p>Et maintenant, place à <strong>Rust</strong>.</p>
<p>On va utiliser une librairie appelée <strong><a href="https://github.com/mityax/rustimport">rustimport</a></strong> . Cet outil nous facilite la vie en compilant notre code Rust pour qu&#39;il puisse être utilisé dans notre script Python, grâce à <strong><a href="https://github.com/PyO3/pyo3">PyO3</a></strong>.</p>
<br />

<blockquote>
<p><em><a href="https://www.rust-lang.org/">Rust</a> bindings for <a href="https://www.python.org/">Python</a>, including tools for creating native Python extension modules. Running and interacting with Python code from a Rust binary is also supported.</em></p>
<p>Tiré de <a href="https://github.com/PyO3/pyo3">https://github.com/PyO3/pyo3</a></p>
</blockquote>
<br />

<p>Voici notre fichier <strong>aoc.rs</strong></p>
<p>Nous avons défini une fonction &quot;<strong>compute</strong>&quot; qui prend en paramètre du texte et qui réalise les mêmes traitements que notre script Python précédent.<br><a href="https://substackcdn.com/image/fetch/$s_!0Mcr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F268d237d-81c7-4944-a801-991aac6c704a_1120x780.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731050492-268d237d-81c7-4944-a801-991aac6c704a_1120x780.png)</a></p>
<p>Pour avoir plus de détails sur la syntaxe adéquate afin d&#39;utiliser votre script dans votre programme Python, je vous renvoie vers <strong><a href="https://pyo3.rs/v0.20.2/">la documentation</a></strong>.</p>
<br />

<p>Maintenant, voici à quoi ressemble <strong>notre script Python</strong> qui utilise ce fichier <strong>aoc.rs</strong><br><a href="https://substackcdn.com/image/fetch/$s_!iysv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6afcc51-c9b1-4175-acb2-a089fcf0a0cc_982x446.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731050799-f6afcc51-c9b1-4175-acb2-a089fcf0a0cc_982x446.png)</a></p>
<p>Vous pouvez voir que le tout premier import vient de <strong>rustimport</strong>.</p>
<p>Qu&#39;est-ce que <strong>rustimport.import_hook</strong> ?</p>
<p>C&#39;est une ligne de code qui va permettre de regarder dans notre dossier s&#39;il y a des fichiers .rs, et de les compiler afin d&#39;être utilisables par Python.</p>
<p>Par exemple, nous avons un fichier <strong>aoc.rs</strong> , ce qui signifie que <strong>notre module Python s&#39;appelle &quot;aoc&quot;</strong>.</p>
<p>Ensuite, nous pouvons appeler la méthode &quot;<strong>compute</strong> &quot; de <strong>aoc</strong> pour faire les calculs.</p>
<br />

<hr>
<p>Si vous appréciez la newsletter <strong>StuffAndCode</strong>, vous pouvez vous inscrire afin de ne manquer aucun article !</p>
<p>🐍 Et recevez en PLUS, un <strong>article exclusif</strong> sur comment profiler n&#39;importe quel type de code Python.</p>
<p><a href="https://blog.stuffandcode.com/subscribe?">Abonnez-vous maintenant</a>  </p>
<hr>
<br />

<h2>Mesurons les performances</h2>
<br />


<p>Pour cette occasion, j&#39;ai décidé de mesurer le temps d&#39;execution des deux fonctions &quot;main&quot; de mes scripts. Ceci exclut<strong>le temps de compilation</strong> mais mesure uniquement le temps d&#39;execution de notre fonction.</p>
<br />


<p>On recommence cette opération 1 000 fois, on calcule <strong>la moyenne du speedup de Rust par rapport a Python</strong> et également <strong>l&#39;écart type</strong> pour voir si les résultats sont proches.<br><a href="https://substackcdn.com/image/fetch/$s_!hxe2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97ec2e02-00f7-4576-9117-eef09ee83965_1488x1042.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731051186-97ec2e02-00f7-4576-9117-eef09ee83965_1488x1042.png)</a></p>
<p>En lançant notre comparatif nous avons ces résultats là :</p>
<pre><code>`Results :
Mean Speedup: 1.1833177238773642
Standard Deviation: 0.06543422441011415`
</code></pre>
<br />

<p><strong>WTF</strong> ? Rust est uniquement <strong>1.18 foi</strong>s plus rapide que Python ?</p>
<p>C&#39;est étonnant, ne trouvez-vous pas ?</p>
<p>Et si ! Effectivement, il y un tips à savoir, un piège à éviter. Lorsqu&#39;on compile &quot;normalement&quot; notre script Rust, le compilateur va <strong>générer une version &quot;Debug&quot;</strong> de notre script, et <strong>non pas une version optimisée</strong> telle qu&#39;elle serait en production.</p>
<br />


<p>Pour ce faire, <strong>il suffit d&#39;ajouter une ligne</strong> dans notre script Python pour préciser que nous voulons activer le build optimisé.<br><a href="https://substackcdn.com/image/fetch/$s_!CWh4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff16f6eea-9d28-4f83-9fca-59e48cb7039c_1136x558.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731051693-f16f6eea-9d28-4f83-9fca-59e48cb7039c_1136x558.png)</a></p>
<br />

<p>Et là, en relançant la mesure du temps, nous obtenons des résultats plus proches de ce qu&#39;on espérait. <strong>Un speedup de quasiment x10</strong>.</p>
<pre><code>Results :
Mean Speedup: 9.970547291038583
Standard Deviation: 0.44621048695561444
</code></pre>
<br />

<h2>Aller plus loin</h2>
<p>Comme je l&#39;ai mentionné au début de cet article, je suis un débutant en Rust. Mais je suis sûr qu&#39;avec plus d&#39;expertise, les gains seraient bien plus importants.</p>
<p>A titre d&#39;expérience, j&#39;ai demandé à GPT de m&#39;écrire une version optimisée de mon code. En parallèle, j&#39;ai vérifié que les résultats étaient les mêmes.</p>
<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!nj0-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb820337a-5da0-4476-b90d-535c4505fccb_1300x1702.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731052047-b820337a-5da0-4476-b90d-535c4505fccb_1300x1702.png)</a></p>
<p>J&#39;ignore exactement ce qu&#39;il se passe dans ce code, mais en l&#39;executant nous obtenons les résultats suivants :</p>
<pre><code>Results :
Mean Speedup: 21.137375292664284
Standard Deviation: 0.9441126168701539
</code></pre>
<p>Notre script Rust est <strong>21x plus rapide</strong> que notre fonction Python originale ! C&#39;est assez impressionant ! Nous avons <strong>plus que doublé les performances de notre version Rust initiale</strong>.</p>
<br />

<h2>Conclusions</h2>
<ol>
<li><p><strong>Intégration de fonctions Rust dans Python</strong> : Nous avons vu que l&#39;intégration de fonctions Rust dans des programmes Python est plus accessible qu&#39;il n&#39;y paraît.</p>
</li>
<li><p><strong>Gains de performance</strong> : L&#39;utilisation de Rust peut offrir des avantages significatifs en termes de performance.</p>
<p>Ces gains de performance peuvent être utilisés pour optimiser certaines parties d&#39;applications Python, des bottlenecks.</p>
</li>
<li><p><strong>Ajout d&#39;options pour optimiser la performance</strong> : En utilisant <strong>rustimport,</strong> il est essentiel d&#39;ajouter certaines options spécifiques telles que &quot;<strong>compile_release_binaries</strong>&quot; lors de vos tests, sinon les résultats ne seront pas représentatifs du gain réel.</p>
</li>
</ol>
<br />

<p>Cependant, il est important de noter que <strong>rustimport</strong> est un outil pour <strong>faciliter le prototypage</strong>, et savoir rapidement si intégrer Rust vous sera bénéfique.</p>
<br />


<p>Si vous comptez réellement utiliser du Rust dans votre codebase, vous devrez le <strong>packager plus proprement</strong>; de la même manière si vous souhaitez utiliser des 3rd party librairies Rust, vous devrez également ajouter de la configuration.</p>
<br />


<p>ET tout cela sera l&#39;occasion d&#39;un prochain article !</p>
<br />

<hr>
<p>Si vous appréciez la newsletter <strong>StuffAndCode</strong>, vous pouvez vous inscrire afin de ne manquer aucun article !</p>
<p>🐍 Et recevez en PLUS, un <strong>article exclusif</strong> sur comment profiler n&#39;importe quel type de code Python.</p>
<p><a href="https://blog.stuffandcode.com/subscribe?">Abonnez-vous maintenant</a>  </p>
<hr>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Testez la robustesse de votre API avec Locust]]></title>
            <link>https://stuffandcode.writizzy.blog/p/testez-la-robustesse-de-votre-api</link>
            <guid>https://stuffandcode.writizzy.blog/p/testez-la-robustesse-de-votre-api</guid>
            <pubDate>Tue, 12 Mar 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Notre API fonctionne. Bien ! Mais tient-elle la charge ? C'est parti pour du load testing avec Locust !]]></description>
            <content:encoded><![CDATA[<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!ugV2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F788befa0-5cbb-49d8-a32e-2e28141491c3_1024x1024.webp">![An illustration representing the concept of load testing in Python using the Locust library. The image should feature a stylized python snake wrapped around a computer server, symbolizing the stress test. The server should look robust and emit digital signals, indicating activity. The Locust library can be represented by a swarm of digital locusts surrounding the server, applying load to it. The background should be a digital landscape, representing a network or cyberspace. Include Python and Locust logos subtly in the composition.](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731016932-788befa0-5cbb-49d8-a32e-2e28141491c3_1024x1024.webp "An illustration representing the concept of load testing in Python using the Locust library. The image should feature a stylized python snake wrapped around a computer server, symbolizing the stress test. The server should look robust and emit digital signals, indicating activity. The Locust library can be represented by a swarm of digital locusts surrounding the server, applying load to it. The background should be a digital landscape, representing a network or cyberspace. Include Python and Locust logos subtly in the composition.")</a></p>
<br />

<p>Lorsque nous développons des services destinés à être utilisés avec un certain volume, il est crucial de tester notre code.</p>
<p>Les tests unitaires, fonctionnels, d&#39;intégration et end-to-end, sont souvent mis en place dans les projets. C&#39;est très bien, mais nous pouvons aller encore plus loin afin d&#39;être plus serein.</p>
<p><strong>Le load testing</strong> (test de charge en français).</p>
<p>L&#39;idée est d&#39;envoyer un grand nombre de requêtes à notre service pour observer sa réaction face à la charge.</p>
<p>Cela inclut l&#39;évaluation de la capacité d&#39;absorption de la charge, du changement des temps de réponse, des éventuelles erreurs dans les réponses, et des comportements inattendus.</p>
<p>Cela permet aussi de déterminer le nombre maximal d&#39;utilisateurs simultanés, afin d&#39;ajuster le sizing ou le nombre de nos machines (vertical scaling vs horizontal scaling).</p>
<br />

<p>De plus, ces tests fournissent des informations sur la <strong>latency</strong> (temps nécessaire pour compléter une action) versus le <strong>throughput</strong> (nombre d&#39;opérations par unité de temps).</p>
<p>Voici <strong>ce magnifique GIF</strong> que j&#39;ai réalisé avec la librairie <a href="https://github.com/motion-canvas/motion-canvas">Motion Canvas</a>. Il y a encore un peu de boulot mais voici une illustration de la différence entre <strong>Latency</strong> et <strong>Throughput</strong>.<br><a href="https://substackcdn.com/image/fetch/$s_!-LaN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad086e1e-9e84-4e15-a8f6-b7affa09cc26_1920x1080.gif">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731017654-ad086e1e-9e84-4e15-a8f6-b7affa09cc26_1920x1080.gif)</a><br><a href="https://substackcdn.com/image/fetch/$s_!u1Mz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99e8e67a-9fc1-413f-99c9-bebab2f9b141_960x540.gif">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731018294-99e8e67a-9fc1-413f-99c9-bebab2f9b141_960x540.gif)</a><br><a href="https://substackcdn.com/image/fetch/$s_!Z0sH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13557316-08ae-4564-9c9d-a22225daa96b_1920x1080.gif">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731019279-13557316-08ae-4564-9c9d-a22225daa96b_1920x1080.gif)</a></p>
<p>Je vais vous parler de la librairie <strong><a href="https://github.com/locustio/locust">Locust</a></strong> , une bibliothèque Python permettant de faire du load testing en Python. <strong>Locust</strong> peut être utilisé via la <strong>ligne de commande</strong> ou une <strong>interface web</strong>.</p>
<br />

<p>❓<em>Petit moment culture : Locust veut dire Sauterelle / Criquet pour rappeler les 10 plaies d&#39;Egypte. L&#39;idée sous-jacente est que Locust envahit notre API comme les sauterelles ont envahi l&#39;Egypte tel que décrit dans <a href="https://fr.wikipedia.org/wiki/Livre_de_l%27Exode">le livre de l'Exode</a>.</em></p>
<br />

<p>Avec Locust, on définit le <strong>nombre de connexions simultanées</strong> souhaitées (par exemple, 1 000 pour simuler mille utilisateurs), et également un &quot;<strong>ramp-up</strong>&quot; pour augmenter progressivement la charge. Si nous choisissons de tester 100 connexions simultannées avec un ramp-up de 10 utilisateurs, cela signifie que chaque seconde, nous ajoutons 10 utilisateurs pour monter progressivement la charge.</p>
<br />

<p>Nous pouvons choisir les endpoints à cibler et leur attribuer des poids relatifs. Locust permet également de définir un &quot;<strong>wait_time</strong> &quot; pour simuler de manière plus réaliste le comportement d&#39;un utilisateur, en introduisant des <strong>pauses entre les requêtes</strong>.</p>
<br />

<h2>Tester notre première API</h2>
<p>Sans plus attendre, voici un exemple. Nous avons une application FastAPI simple avec une seule route sur &quot;/&quot;. Nous voulons évaluer combien de requêtes peuvent être gérées par cette application, sur mon ordinateur.<br><a href="https://substackcdn.com/image/fetch/$s_!M9gs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa82e5f84-81ad-4464-937f-126a2d2fde7d_1636x484.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731019700-a82e5f84-81ad-4464-937f-126a2d2fde7d_1636x484.png)</a></p>
<p>Nous utilisons la commande suivante pour démarrer notre API :</p>
<pre><code>uvicorn app:app --workers 32
</code></pre>
<p>J&#39;ai choisi 32 workers car mon ordinateur a 32 cœurs.</p>
<p>Apparemment, <a href="https://stackoverflow.com/questions/72374634/how-many-uvicorn-workers-do-i-have-to-have-in-production">la formule idéale</a> serait le nombre de CPU * 2 + 1, donc 65 dans mon cas. Mais n&#39;ayant pas constaté de gain de performance, j&#39;ai gardé 32 workers.</p>
<br />

<hr>
<p>Si vous appréciez la newsletter StuffAndCode, vous pouvez vous inscrire afin de ne manquer aucun article !</p>
<p>🐍 Et recevez en PLUS, un <strong>article exclusif</strong> sur comment profiler n&#39;importe quel type de code Python.</p>
<p><a href="https://blog.stuffandcode.com/subscribe?">Abonnez-vous maintenant</a>  </p>
<hr>
<br />

<p>Ensuite, écrivons notre premier <strong>test de charge</strong> avec Locust.<br><a href="https://substackcdn.com/image/fetch/$s_!5NZE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94687c28-0ef2-4b89-8cae-289767571ded_1636x446.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731020044-94687c28-0ef2-4b89-8cae-289767571ded_1636x446.png)</a></p>
<p>Nous créons une classe qui hérite de <code>HttpUser</code>, instanciée pour chaque utilisateur.</p>
<p>Par exemple, avec 1000 utilisateurs, cette classe sera instanciée 1000 fois.</p>
<p>Nous définissons une seule tâche avec le décorateur <code>@task</code>.</p>
<p>Si plusieurs tâches sont définies, Locust les <strong>choisit aléatoirement</strong> . Dans le test <code>test_home_page</code>, on utilise <code>self.client.get(&quot;/&quot;)</code>.</p>
<p>Pour <strong>spécifier l&#39;host</strong> , nous pouvons utiliser une <strong>variable d&#39;environnement</strong> ou <strong>l&#39;interface web</strong> de Locust.</p>
<p>Pour ce tutoriel, j&#39;utilise l&#39;interface web, parce que : why not ?</p>
<p>Après avoir écrit notre test, démarrons Locust avec la commande :</p>
<pre><code>`locust -f locustfile_part1.py --processes -1 --modern-ui`
</code></pre>
<ul>
<li><p>L&#39;option -f précise le fichier à exécuter.</p>
</li>
<li><p>--processes -1 permet d&#39;utiliser tous les processeurs disponibles sur notre machine pour envoyer le plus de requêtes possible</p>
</li>
<li><p>L&#39;option --modern-ui utilise une interface web, plus sympa, qui est encore en beta</p>
</li>
</ul>
<br />

<p>Sur la page Locust, nous définissons :</p>
<ul>
<li><p>le nombre d&#39;utilisateurs à simuler.</p>
</li>
<li><p>le ramp-up par seconde.</p>
</li>
<li><p>la durée du test.</p>
<br /></li>
</ul>
<p>Par exemple, nous pouvons monter à 1000 utilisateurs, envoyer des requêtes pendant 30 secondes, puis arrêter, en spécifiant ces paramètres dans les champs appropriés.</p>
<p>Voici à quoi ressemble l&#39;écran de configuration de notre test de charge.</p>
<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!QVUq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1ce7b92-6212-4abb-b736-f4363eb60e50_592x1019.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731026291-d1ce7b92-6212-4abb-b736-f4363eb60e50_592x1019.png)</a></p>
<p>J&#39;ai configuré le test pour évaluer notre route avec 1 000 utilisateurs, sans spécifier de délai entre les requêtes. Ci-dessous, les résultats.<br><a href="https://substackcdn.com/image/fetch/$s_!Xc0O!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37da85b5-d45a-43cc-855b-ee9986256cd0_1624x611.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731026886-37da85b5-d45a-43cc-855b-ee9986256cd0_1624x611.png)</a></p>
<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!XgPL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fd56995-fdb6-4639-b463-9794507a622f_1570x1355.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731027267-7fd56995-fdb6-4639-b463-9794507a622f_1570x1355.png)</a></p>
<p>Les résultats ne montrent <strong>aucune erreur</strong> sur nos routes, des <strong>temps de réponse constants</strong> , et pour 1 000 utilisateurs simultanés, nous atteignons un peu plus de <strong>20 000 requêtes par seconde</strong>.</p>
<p>Vous pouvez consulter ces résultats dans les tableaux ou graphiques, télécharger les données, consulter les logs, et voir le nombre de requêtes traitées par chaque worker.</p>
<br />

<p>Maintenant, passons à quelque chose de plus concrêt.</p>
<br />

<h2>Une API protégée par du &quot;JWT&quot;</h2>
<p>Je vais simuler une API REST utilisant un JWT. Pour simplifier, je considérerai qu&#39;un header avec un Bearer token est valide pour l&#39;authentification.</p>
<p>Voici le code de notre &quot;API REST&quot; :<br><a href="https://substackcdn.com/image/fetch/$s_!vhQA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa034628e-681b-43ba-bc9b-526047452dd2_1636x1302.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731027634-a034628e-681b-43ba-bc9b-526047452dd2_1636x1302.png)</a></p>
<p>Nous disposons de trois routes :</p>
<ol>
<li><p>Le login qui renvoie un token.</p>
</li>
<li><p>Deux routes identiques simulant une ressource protégée.</p>
</li>
</ol>
<p>Dans notre test Locust :<br><a href="https://substackcdn.com/image/fetch/$s_!MlCJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F060356b0-9128-45b1-8e0a-6013b8066a6c_1636x930.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731028070-060356b0-9128-45b1-8e0a-6013b8066a6c_1636x930.png)</a></p>
<p>La méthode <code>on_start</code> est utilisée pour définir une action spécifique au moment de la création d&#39;un nouvel utilisateur. Par exemple, chaque nouvel utilisateur envoie une requête sur <code>/login</code> pour récupérer un token, nécessaire pour s&#39;authentifier sur les autres routes.</p>
<p>Nous avons ensuite <strong>deux autres tâches</strong> envoyant des requêtes sur les routes protégées. Nous avons décoré notre deuxième route avec <code>@task(3)</code>. Cela nous sert à donner <strong>un</strong> <strong>poids</strong> , c&#39;est à dire que cette route a <strong>trois fois plus de chances d&#39;être appelée</strong> que la première. Car pour rappel, Locust regarde toutes les méthodes décorées de <code>@task</code> puis <strong>choisit de manière random</strong>.</p>
<p>L&#39;attribut <code>wait_time</code> de la classe définit un <strong>intervalle aléatoire</strong> entre 1 et 5 secondes entre chaque tâche, afin de <strong>simuler un comportement utilisateur plus réaliste</strong>.</p>
<br />

<h2>Conclusion</h2>
<p>En conclusion, je voudrai souligner que Locust offre des possibilités <strong>plus avancées</strong> :</p>
<ul>
<li><p>envoyer des requêtes depuis plusieurs machines pour simuler <strong>des millions d&#39;users</strong>.</p>
</li>
<li><p>Bien que nous ayons testé une API REST dans cet article, Locust permet de tester <strong>plusieurs protocoles</strong> en utilisant différents clients. Plus d&#39;informations sur ces clients sont disponibles <a href="https://github.com/SvenskaSpel/locust-plugins#users">ici</a>.</p>
</li>
</ul>
<br />

<p>Finalement, il est important de noter que j&#39;ai exécuté le test de charge et l&#39;API <strong>sur la même machine par simplicité</strong>, mais cela a clairement influencé les résultats.</p>
<p>Cela permet tout de même de détecter des bugs potentiels et d&#39;avoir une vue globale. Pour pousser au maximum l&#39;expérience, il faudrait exécuter le load testing sur une machine distincte.</p>
<br />

<hr>
<p>Si vous appréciez la newsletter StuffAndCode, vous pouvez vous inscrire afin de ne manquer aucun article !</p>
<p>🐍 Et recevez en PLUS, un <strong>article exclusif</strong> sur comment profiler n&#39;importe quel type de code Python.</p>
<p><a href="https://blog.stuffandcode.com/subscribe?">Abonnez-vous maintenant</a>  </p>
<hr>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Accélérer nos tests Python avec la parallélisation]]></title>
            <link>https://stuffandcode.writizzy.blog/p/accelerer-nos-tests-python-avec-de</link>
            <guid>https://stuffandcode.writizzy.blog/p/accelerer-nos-tests-python-avec-de</guid>
            <pubDate>Tue, 27 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Pourquoi ? Comment ? Faut-il toujours paralleliser nos tests ?]]></description>
            <content:encoded><![CDATA[<p><a href="https://substackcdn.com/image/fetch/$s_!i4hx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F169b4886-a4ab-416f-899b-b83a72a7ea69_1024x1024.webp">![Create an image showcasing the concept of parallelization in Python programming with a clear emphasis on database interactions. Visualize a central Python programming language logo, with multiple parallel lines extending from it, each representing a thread of execution. These lines should branch off into stylized icons of databases and servers, indicating the connection and interaction with various data sources. The lines of parallel execution should be interconnected, forming a network that symbolizes efficient data processing and exchange. The background should be a digital landscape, filled with elements like circuit patterns, binary code, and digital waveforms, reflecting a sophisticated programming environment. The overall color scheme should incorporate Python's blue and yellow, creating a vibrant and modern look that highlights the theme of advanced computing in Python.](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731028627-169b4886-a4ab-416f-899b-b83a72a7ea69_1024x1024.webp "Create an image showcasing the concept of parallelization in Python programming with a clear emphasis on database interactions. Visualize a central Python programming language logo, with multiple parallel lines extending from it, each representing a thread of execution. These lines should branch off into stylized icons of databases and servers, indicating the connection and interaction with various data sources. The lines of parallel execution should be interconnected, forming a network that symbolizes efficient data processing and exchange. The background should be a digital landscape, filled with elements like circuit patterns, binary code, and digital waveforms, reflecting a sophisticated programming environment. The overall color scheme should incorporate Python's blue and yellow, creating a vibrant and modern look that highlights the theme of advanced computing in Python.")</a></p>
<br />

<p>Dans le développement, la mise en place de tests est une étape incontournable.</p>
<p>Qu&#39;ils soient unitaires, fonctionnels ou end-to-end, les tests garantissent la qualité et la fiabilité du code. Cependant, à mesure que notre codebase s&#39;élargit, le nombre de tests augmente, entraînant une <strong>augmentation significative du temps de test</strong>.</p>
<p>Cette situation a <strong>deux conséquences</strong> principales :</p>
<ul>
<li><p>une perte de temps directe pour le développeur.</p>
</li>
<li><p>dans le cas de pipelines de CI, des <strong>coûts accrus</strong> liés au temps d&#39;exécution des tests.</p>
</li>
</ul>
<br />

<p>⚠️ Dans cet article, nous allons utiliser SQLlite en tant que base de données, mais cela fonctionne de la même manière si vous utilisez une autre base de données tels Postgres ou MySQL ou autre.   </p>
<p>Il suffit de changer l&#39;URL lors de la création de l&#39;engine SQLAlchemy. Par exemple :<br><a href="https://substackcdn.com/image/fetch/$s_!mHmq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3efb6712-23d9-4b90-bf32-04cdea49a0f1_1550x334.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731029062-3efb6712-23d9-4b90-bf32-04cdea49a0f1_1550x334.png)</a></p>
<h2><strong>Accélérer les tests</strong></h2>
<p>Plusieurs stratégies peuvent être adoptées.</p>
<p>Par l&#39;optimisation des tests eux-mêmes, en observant ce qui prend du temps, peut-être même revoir le scope des fixtures etc... Mais cette approche a plusieurs limites, dont la première : si le code à tester est long, malgré l&#39;optimisation des tests, il y aura peu de différence.</p>
<p>La méthode la plus simple et la plus efficace semble être la <strong>parallélisation des tests</strong>. L&#39;idée est de lancer plusieurs tests simultanément au lieu de les exécuter les uns aprés les autres.</p>
<br />

<h2><strong>Parallélisation avec Pytest-xdist</strong></h2>
<p><a href="https://github.com/pytest-dev/pytest-xdist">Pytest-xdist</a> est un outil Python qui facilite cette parallélisation. L&#39;utilisation de cet outil est simple : si vous disposez de 32 CPU, vous pouvez théoriquement exécuter jusqu&#39;à 32 tests <strong>simultanément</strong>.</p>
<p>Cette capacité à exécuter des tests en parallèle peut grandement améliorer le temps nécessaire pour lancer votre suite de tests.</p>
<p>De plus, la mise en place est très facile :</p>
<pre><code>&gt;&gt; pip install pytest-xdist

&gt;&gt; pytest -n auto
</code></pre>
<p>La commande <code>pytest -n auto </code>va détecter automatiquement votre nombre de CPU afin de pouvoir créer le nombre approprié de workers ,et démarrer les tests sur ceux-ci.</p>
<br />

<h2><strong>Les side-effects et la parallélisation</strong></h2>
<p>Cependant, la parallélisation soulève un défi de taille : la <strong>gestion des side effects</strong>. Certains tests peuvent modifier des ressources externes, comme des fichiers ou des bases de données, polluant ainsi les l&#39;environnements pour d&#39;autres tests.</p>
<p>Par exemple, un test créant des instances dans une base de données peut laisser des traces qui affecteront les tests suivants.</p>
<p>C&#39;est un problème connu : lorsqu&#39;on execute des tests qui se servent d&#39;une base de données, <strong>nous devons nettoyer cette base de données entre chaque test</strong>.</p>
<br />

<h2><strong>Nettoyer sa base de donnée après chaque test</strong></h2>
<p>Pour remédier à cela, deux approches principales sont souvent utilisées :</p>
<ul>
<li><p>l&#39;utilisation de <strong>rollbacks</strong> en fin de test</p>
</li>
<li><p>la <strong>suppression de la base de données</strong> après chaque test.</p>
<br /></li>
</ul>
<p>Afin d&#39;illustrer nos exemples, nous allons partir du model SQLAlchemy que nous désirons tester :<br><a href="https://substackcdn.com/image/fetch/$s_!2jIf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1b789a0-efac-4696-ad18-0b2d95f2c7b6_1450x856.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731029594-d1b789a0-efac-4696-ad18-0b2d95f2c7b6_1450x856.png)</a></p>
<br />

<h3>1) Tests sans nettoyage des données</h3>
<p>Nous avons deux tests : un qui crée un Book et qui vérifie qu&#39;il existe bien un seul Book; un second test qui crée 5 Books et qui vérifie qu&#39;il en existe 5. Ces tests sont simples mais ils vont permettre d&#39;illustrer nos propos.<br><a href="https://substackcdn.com/image/fetch/$s_!gj0d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a35071d-c2c6-402a-9c7f-114ccbb560d1_1636x1414.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731030386-4a35071d-c2c6-402a-9c7f-114ccbb560d1_1636x1414.png)</a></p>
<p>Si j&#39;execute de manière séquentielle (l&#39;un aprés l&#39;autre) mes tests, je vais d&#39;abord créer 1 Book . Le test va passer. Mais lors du deuxieme test, je vais créer 5 books supplémentaires, sans avoir nettoyé ma base de données, en supprimant le book de l&#39;ancien test. Par conséquent, au lieu d&#39;avoir 5 books j&#39;en aurai 6.</p>
<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!-hqc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e26753d-2750-4bfb-809b-382e389bf069_1256x538.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731030771-2e26753d-2750-4bfb-809b-382e389bf069_1256x538.png)</a></p>
<p>Donc, il nous faut un moyen de remettre notre database en état entre chaque test pour éviter les pollutions.</p>
<br />

<h3>2.1) Utilisation de rollback à la fin du test</h3>
<p>Afin d&#39;éviter la pollution de test, une solution souvent préconisée est de réaliser un &quot;rollback&quot; à la fin de notre test, de sorte que toutes les actions réalisées dans la transaction courante soient annulées.</p>
<br />


<p>On peut donc ajouter ce comportement directement à notre fixture.</p>
<p>Essayons ce code ci :<br><a href="https://substackcdn.com/image/fetch/$s_!V6ig!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f903cfd-9c25-4520-b06c-eb741af6008c_2912x2186.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731031489-5f903cfd-9c25-4520-b06c-eb741af6008c_2912x2186.png)</a></p>
<p>Voici les résultats de ces tests :<br><a href="https://substackcdn.com/image/fetch/$s_!XkIX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6833dd86-f4c1-4122-91ce-c082e754791c_1259x491.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731031963-6833dd86-f4c1-4122-91ce-c082e754791c_1259x491.png)</a></p>
<p>Comme vous pouvez le constater, les tests ne passent pas. Pourquoi ?</p>
<p>Tout simplement parce que, même si nous faisons un &quot;rollback&quot; à la fin du test dans notre test, apparaissent des commits pour seeder nos tests, et donc ces changements sont persistés en base de données au moment du commit.</p>
<p>Ainsi, dès que le test fait un commit, le rollback n&#39;aura plus d&#39;effet car les résultats auront déjà été persisté en base de données.</p>
<br />

<h2>2.2) Un hack pour rollback</h2>
<p>Il existe effectivement un &quot;hack&quot; pour rollback entre chaque test.</p>
<p>Mais ceci dépasse le scope de cet article. Je vous renvoie à l&#39;article qui l&#39;aborde en <a href="https://dev.to/jbrocher/fastapi-testing-a-database-5ao5">cliquant ici</a>.</p>
<p>Pour plus de détails, vous pouvez directement vous rendre sur la page de SQLAlchemy qui détaille son fonctionnement en suivant <a href="https://docs.sqlalchemy.org/en/14/orm/session_transaction.html#joining-a-session-into-an-external-transaction-such-as-for-test-suites">ce lien</a>.</p>
<br />

<p>Si vous souhaitez simplement mettre en place le rollback dans vos tests, je vous transmets ci-dessous la fixture telle qu&#39;elle est préconisée dans l&#39;article :<br><a href="https://substackcdn.com/image/fetch/$s_!dH_e!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50667c44-806b-43c4-8385-1f834fde40c2_1636x670.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731032685-50667c44-806b-43c4-8385-1f834fde40c2_1636x670.png)</a></p>
<br />

<hr>
<p>Si vous appréciez la newsletter StuffAndCode, vous pouvez vous inscrire afin de ne manquer aucun article !</p>
<p>🐍 Et recevez en PLUS, un <strong>article exclusif</strong> sur comment profiler n&#39;importe quel type de code Python.</p>
<p><a href="https://blog.stuffandcode.com/subscribe?">Abonnez-vous maintenant</a>  </p>
<hr>
<br />

<ol start="3">
<li>Drop la base de données</li>
</ol>
<hr>
<p>Pourquoi ne pas tout simplement créer et supprimer la base de données lors de chaque test ? Ca nous permettrait d&#39;être sûr d&#39;avoir un environnement propre !</p>
<p>Nous pouvons mettre en place ce mécanisme comme vous pouvez le voir ci-dessous.<br><a href="https://substackcdn.com/image/fetch/$s_!DriX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf315aa6-e919-48ec-b04c-9f569bdf6495_2912x2848.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731033241-bf315aa6-e919-48ec-b04c-9f569bdf6495_2912x2848.png)</a><br><a href="https://substackcdn.com/image/fetch/$s_!LoRB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac21c531-875e-4d5f-b176-9c05b34a74c6_1253x202.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731033539-ac21c531-875e-4d5f-b176-9c05b34a74c6_1253x202.png)</a></p>
<p>Nos tests passent, c&#39;est bien. Mais à cette approche, incombent certains problemes.</p>
<br />

<h2>3.1) Les problématiques de cette approche</h2>
<p>Supprimer et recréer la database pour chaque test prend du temps, et cela peut rallonger considérablement le temps d&#39;execution de vos tests.</p>
<p>Si vous parallelisez vos tests, cela veut également dire que lorsqu&#39;un test va terminer, il va modifier les données et polluer un autre test, également drop la base de données, pendant que les autres tests finissent de s&#39;executer. Ce qui va générer des erreurs:<br><a href="https://substackcdn.com/image/fetch/$s_!Ou79!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56b9f339-7724-4df8-83cf-cb007849d098_1255x439.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731034139-56b9f339-7724-4df8-83cf-cb007849d098_1255x439.png)</a></p>
<br />

<h2><strong>3.2) La Solution : Créer une base de données par worker</strong></h2>
<p>Lors de l&#39;utilisation de <a href="https://github.com/pytest-dev/pytest-xdist">Pytest-xdist</a>, un nouveau problème émerge : si chaque test parallélisé tente de modifier la même base de données, <strong>des conflits</strong> surviendront inévitablement.</p>
<p>La solution réside dans la modification de notre fixture de base de données pour créer <strong>une base distincte pour chaque worker</strong> . Ainsi, chaque worker opère sur sa propre base de données, et évite les conflits tout en bénéficiant d**&#39;un véritable parallélisme**.</p>
<p>Pour ce faire, pytest-xdist ajoute une <strong>variable d&#39;environnement</strong> &quot;<code>PYTEST_XDIST_WORKER</code>&quot; qui attribue une valeur pour chaque worker. Par exemple, si nous avons 10 workers, chaque worker aura une variable <code>PYTEST_XDIST_WORKER </code>qui vaudra 0, 1, 2 ... 9.</p>
<p>Nous pouvons donc créer une base de données pour chaque worker en nous servant de cette variable d&#39;environnement, résultant en un environnement isolé pour chaque worker.<br><a href="https://substackcdn.com/image/fetch/$s_!MzzW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bcedac8-0074-4cb8-bfad-8a175c7369af_2912x2318.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731034541-5bcedac8-0074-4cb8-bfad-8a175c7369af_2912x2318.png)</a></p>
<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!T0GB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36d7e6a5-5555-482f-b317-257f3c6491f0_1249x178.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731034899-36d7e6a5-5555-482f-b317-257f3c6491f0_1249x178.png)</a></p>
<p>Cela fonctionne !</p>
<br />


<p>Cependant, vous pouvez noter que le temps est tout de même passé de +3s contre 0.23s pour la version séquentielle. La raison ? <strong>Démarrer les tests sur plusieurs CPU prend du temp</strong> s !  </p>
<p>Donc si vos tests sont déjà trés rapides de manière séquentielle, il est peut-être plus pertinent d&#39;éviter de mettre en place la parallélisation.</p>
<p>A l&#39;inverse, si vos tests sont plus longs, le gain de temps peut-être vraiment très intéressant.</p>
<br />

<h2>Quizz Time</h2>
<ul>
<li><p>Quelle librairie utilisons-nous pour paralleliser nos tests ?</p>
</li>
<li><p>Dans quel cas est-il préférable de ne pas paralleliser les tests ?</p>
</li>
<li><p>Quels sont les avantages et inconvénients de drop la database entre chaque test ?</p>
</li>
</ul>
<br />

<hr>
<p>Si vous appréciez la newsletter StuffAndCode, vous pouvez vous inscrire afin de ne manquer aucun article !</p>
<p>🐍 Et recevez en PLUS, un <strong>article exclusif</strong> sur comment profiler n&#39;importe quel type de code Python.</p>
<p><a href="https://blog.stuffandcode.com/subscribe?">Abonnez-vous maintenant</a>  </p>
<hr>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment débugger un programme stuck en production]]></title>
            <link>https://stuffandcode.writizzy.blog/p/comment-debugger-un-programme-stuck</link>
            <guid>https://stuffandcode.writizzy.blog/p/comment-debugger-un-programme-stuck</guid>
            <pubDate>Tue, 13 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Apprenez à trouver pourquoi votre service Python est bloqué sans avoir besoin de le redémarrer ! ]]></description>
            <content:encoded><![CDATA[<p><a href="https://substackcdn.com/image/fetch/$s_!SsaX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1480bbf5-a177-49d6-be61-7e9e9a606346_1024x1024.webp">![A cartoon-style illustration showing a detective with a magnifying glass closely examining a Python program. The Python program is represented as a flowing stream of code, with the detective scrutinizing it for bugs or issues. The detective wears a classic detective hat and coat, and the magnifying glass prominently features the Python logo. The scene conveys a sense of investigation and analysis, highlighting the theme of debugging a program.](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731057071-1480bbf5-a177-49d6-be61-7e9e9a606346_1024x1024.webp "A cartoon-style illustration showing a detective with a magnifying glass closely examining a Python program. The Python program is represented as a flowing stream of code, with the detective scrutinizing it for bugs or issues. The detective wears a classic detective hat and coat, and the magnifying glass prominently features the Python logo. The scene conveys a sense of investigation and analysis, highlighting the theme of debugging a program.")</a></p>
<br />

<p>C&#39;est une belle journée d&#39;été, tout est calme; un <strong>peu trop calme peut-être</strong>.</p>
<p>Notre système de monitoring ne signale aucune erreur, alors que d&#39;ordinaire, nous recevons quelques alertes Sentry par jour. Mais là, c&#39;est <strong>silence radio</strong>, et c&#39;est suspect.</p>
<p>En examinant nos données, nous nous rendons compte qu&#39;il ne se passe rien depuis plusieurs heures. Pourtant, notre programme est configuré pour redémarrer automatiquement en cas de crash.</p>
<p>Nous nous précipitons sur la machine et exécutons un <code>docker ps</code> pour vérifier si les conteneurs fonctionnent toujours. Oui, ils sont opérationnels.</p>
<p>Alors, que se passe-t-il ? Pourquoi l&#39;activité semble-t-elle arrêtée ?</p>
<br />

<p><strong>Let&#39;s debug this sh*t.</strong></p>
<h2>Welcome to py-spy</h2>
<br />

<blockquote>
<p><em>py-spy is a sampling profiler for Python programs. It lets you visualize what your Python program is spending time on without restarting the program or modifying the code in any way. py-spy is extremely low overhead: it is written in Rust for speed and doesn&#39;t run in the same process as the profiled Python program. This means py-spy is safe to use against production Python code.</em></p>
<p>Tiré de <a href="https://github.com/benfred/py-spy">https://github.com/benfred/py-spy</a></p>
</blockquote>
<br />

<h2>Les points fort de py-spy</h2>
<p>La librairie permet de réaliser plusieurs actions intéressantes :</p>
<ul>
<li><p><strong>Profiling de code</strong> : C&#39;est un <a href="https://blog.stuffandcode.com/p/les-differents-types-de-profilers">statistical profiler</a>.</p>
</li>
<li><p><strong>Capacité à se brancher directement</strong> sur un processus en cours d&#39;exécution sans nécessiter de modification du code, ou de redémarrage de l&#39;instance.</p>
</li>
<li><p><strong>Prise en charge du multiprocessing</strong> : Gestion efficace des environnements utilisant le multiprocessing, les workers gunicorn, etc...</p>
</li>
</ul>
<br />

<h1>Notre premier script à debug</h1>
<p>Voici notre premier coupable : un script se retrouve stuck dans une boucle infinie et qui exécute <code>time.sleep </code>sans cesse.</p>
<p>Nous allons exécuter ce script sur <strong>notre host,</strong> et observer quelles informations émergent.<br><a href="https://substackcdn.com/image/fetch/$s_!cyoW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd35db49e-144d-47ce-941d-876027f32c71_1608x1616.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731057471-d35db49e-144d-47ce-941d-876027f32c71_1608x1616.png)</a></p>
<p>Après l&#39;avoir lancé, nous récupérons le <strong>PID</strong> de notre processus, une information cruciale pour établir la connexion avec <code>py-spy</code>.<br><a href="https://substackcdn.com/image/fetch/$s_!d_-8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febede1f8-987c-49d3-a01a-7a0d3e317619_1588x55.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731057944-ebede1f8-987c-49d3-a01a-7a0d3e317619_1588x55.png)</a></p>
<p><strong>py-spy</strong> offre actuellement <strong>trois commandes principales</strong> pour recueillir les informations sur notre processus.</p>
<p>Explorons-les !</p>
<ol>
<li>py-spy dump</li>
</ol>
<hr>
<p><code>py-spy</code> offre la possibilité d&#39;afficher la <strong>call stack</strong> actuelle de chaque thread Python avec la commande <code>dump</code>.</p>
<p>Nous avons également la faculté d&#39;ajouter un flag ---<code>locals</code> afin de voir les variables et leurs valeurs actuelles.</p>
<br />

<p><code>py-spy dump --pid 70300</code><br><a href="https://substackcdn.com/image/fetch/$s_!1273!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea4a3870-0aea-4302-9038-2f0f7e919b93_809x156.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731058217-ea4a3870-0aea-4302-9038-2f0f7e919b93_809x156.png)</a></p>
<p>Dans cet exemple, nous observons que le blocage se situe à la ligne 7 et correspond effectivement à notre instruction <code>time.sleep</code>.</p>
<br />

<ol start="2">
<li>py-spy record</li>
</ol>
<hr>
<p>Nous avons aussi la possibilité de nous connecter au processus, et de réaliser un <strong>enregistrement</strong> (record) sur une durée plus étendue.</p>
<p>Lorsque nous estimons avoir collecté suffisamment de données, un simple appui sur <code>Ctrl + C</code> interrompt le profiling. Cette action génère un <strong>flamegraph</strong>, un visuel qui illustre où le code consacre la majorité de son temps d&#39;exécution.</p>
<p>Exemple de commande pour lancer le record :</p>
<p><code>py-spy record ---pid 8773</code><br><a href="https://substackcdn.com/image/fetch/$s_!h5ot!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d565638-c5f5-4ea0-9c54-fb59551f35b4_1078x263.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731058981-2d565638-c5f5-4ea0-9c54-fb59551f35b4_1078x263.png)</a></p>
<p>Le flamegraph se lit de haut en bas.</p>
<p>Dans notre cas, il confirme effectivement que la ligne 7 ( notre fameux <code>time.sleep</code> ) monopolise le temps d&#39;exécution. Sans surprise !</p>
<p>Si vous le souhaitez ,vous pouvez visualiser ces résultats autrement; avec un speedscope par exemple, le <a href="https://github.com/jlfwong/speedscope">lien est ici</a>.</p>
<br />

<hr>
<p>Si vous appréciez la newsletter StuffAndCode, vous pouvez vous inscrire afin de ne manquer aucun article !</p>
<p>🐍 Et recevez en PLUS, un <strong>article exclusif</strong> sur comment profiler n&#39;importe quel type de code Python.</p>
<p><a href="https://blog.stuffandcode.com/subscribe?">Abonnez-vous maintenant</a>  </p>
<hr>
<br />

<ol start="3">
<li>py-spy top</li>
</ol>
<hr>
<p>py-spy propose la commande <code>top</code> afin de suivre le déroulement de notre programme. Nous pouvons lire les fonctions appelées, le temps d&#39;execution etc ...</p>
<p>Et c&#39;est<strong>en temps réel</strong> ! Donc nul besoin d&#39;être à l&#39;aveugle comme sur la commande <code>record</code>, ou d&#39;avoir juste un point dans le temps avec la commande <code>dump</code>. Ici nous visualisons directement ce qu&#39;il se passe dans notre programme.<br><a href="https://substackcdn.com/image/fetch/$s_!y35f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4be19340-a4ab-4be4-b9f4-55fac763fc5a_725x197.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731059382-4be19340-a4ab-4be4-b9f4-55fac763fc5a_725x197.png)</a></p>
<br />

<p>Cependant, nous pouvons constater que nous ne voyons pas exactement le numéro de la ligne executée par le programme.</p>
<br />

<h1>Un cas concret</h1>
<p>Nous sommes en 2k24, nos services tournent dans des <strong>containers</strong> et le code est légèrement plus compliqué qu&#39;une boucle while avec un sleep. On va essayer de randomiser tout cela afin de simuler une anomalie, sans qu&#39;elle produise un blocage complet.</p>
<br />

<p>Nous allons exécuter ce code dans un conteneur <strong>Docker</strong> utilisant Python 3.11, et rechercher où notre code consacre le plus de temps.<br><a href="https://substackcdn.com/image/fetch/$s_!GcXX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F526bb9b5-1ff8-4def-a4d3-e89cbd8f6a11_1820x1976.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731059818-526bb9b5-1ff8-4def-a4d3-e89cbd8f6a11_1820x1976.png)</a></p>
<br />

<ol>
<li>Debugger depuis l&#39;host</li>
</ol>
<hr>
<p>Notre code tourne dans un container <strong>Docker</strong> , mais rien ne nous empêche de le <strong>debugger depuis notre host</strong> .   </p>
<p>Pour cela rien de plus simple. Nous cherchons le PID de notre processus Python qui tourne dans Docker, et nous lançons les mêmes commandes que nous avons vues précedemment.</p>
<br />


<p>Il est fort probable que vous deviez exécuter la commande avec les <strong>droits</strong> <strong>root</strong> pour mener les observations.<br><a href="https://substackcdn.com/image/fetch/$s_!Naqa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20f64129-ea5e-4455-b1c6-6c6cdbce23eb_1806x651.gif">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731060120-20f64129-ea5e-4455-b1c6-6c6cdbce23eb_1806x651.gif)</a></p>
<p>De la même manière, nous pouvons faire un <strong>record</strong> de programme, toujours pour mettre en évidence les actions, et analyser le flamegraph.<br><a href="https://substackcdn.com/image/fetch/$s_!5D5a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec25d892-8074-4ad2-b929-90b8bad6cc60_1102x198.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731060434-ec25d892-8074-4ad2-b929-90b8bad6cc60_1102x198.png)</a></p>
<br />

<ol start="2">
<li>Debugger depuis Docker</li>
</ol>
<hr>
<br />

<p>Envisageons que vous ne <strong>vouliez/puissiez installer py-spy sur votre machine host</strong>de production. Peut-être n&#39;avez-vous pas de version Python compatible avec ?</p>
<p>Pas de soucis ! Nous pouvons debugger à l&#39;aide de <strong>Docker</strong>.</p>
<br />

<h3>Scénario 1 : se servir du container de notre programme</h3>
<p><strong>Py-spy</strong> est reconnu pour être <strong>low-overhead</strong>. Donc il aura un faible impact sur les performances de notre programme, et prendra peu de ressources de notre container.</p>
<p>Ainsi, nous pouvons imaginer ce scénario :</p>
<ul>
<li><p>Entrer dans le container avec la commande <code>docker exec -ti &lt;container_name&gt; bash</code></p>
</li>
<li><p>Installer py-spy : <code>pip install py-spy</code></p>
</li>
<li><p>Trouver le PID de notre programme</p>
</li>
<li><p>Lancer le debug avec la commande <code>py-spy top</code> par exemple</p>
</li>
</ul>
<br />

<p>Nous avons un plan, testons le :<br><a href="https://substackcdn.com/image/fetch/$s_!8ygm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64c3e700-a59f-49e5-95df-de81e1bd7009_547x75.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731060875-64c3e700-a59f-49e5-95df-de81e1bd7009_547x75.png)</a></p>
<br />

<p><strong>WTF ?</strong> Nous avons une &quot;<strong>Operation not permitted</strong>&quot; alors que nous sommes root ?</p>
<br />

<p><strong>py-spy</strong> offre de la documentation à ce sujet que vous pouvez retrouver <a href="https://github.com/benfred/py-spy?tab=readme-ov-file#how-do-i-run-py-spy-in-docker">ici</a> .</p>
<p>On nous dit :</p>
<blockquote>
<p><em>Running py-spy inside of a docker container will also usually bring up a <strong>permissions denied error even when running as root.</strong></em></p>
<p><em>This error is caused by docker restricting the process_vm_readv system call we are using. This can be overridden by setting</em> <code>--cap-add SYS_PTRACE</code><em>when starting the docker container.</em></p>
<p><em>Alternatively you can edit the docker-compose yaml file</em></p>
<pre><code>`your_service:
   cap_add:
     - SYS_PTRACE
`
</code></pre>
<p><em>Note that <strong>you&#39;ll need to restart the docker container</strong> in order for this setting to take effect.</em></p>
</blockquote>
<br />

<p>Donc, il est explicitement écrit dit que si l&#39;on désire debugger notre programme dans <strong>Docker</strong> , il faut<strong>ajouter des capabilitie</strong> s et <strong>redémarrer notre container</strong>.</p>
<p>Ce n&#39;est vraiment pas incroyable, vu que potentiellement le bug se produit trés rarement, et ignorons quand il va se reproduire.   </p>
<p>Aussi, devoir faire des modifications de docker-compose juste pour çela, ce n&#39;est pas fou.</p>
<br />

<p>✨Soyez serein,<strong>nous avons une autre solution ! ✨</strong></p>
<br />

<h3>Scénario 2: Debug depuis un autre container Docker</h3>
<br />

<p>Pourquoi pas <strong>démarrer un autre container</strong> en lui ajoutant la capability nécessaire pour profiler le code ?   </p>
<p>Dans ce cas, nous devons partager le namespace du container que nous voulons debugger dans la commande afin que notre container de debug puisse cibler le processus à debugger.   </p>
<p>Nous pouvons utiliser la commande suivante :</p>
<br />

<p><code>docker run --cap-add=SYS_PTRACE --pid=container:&lt;container_name&gt; -it python:3.11 /bin/bash</code>  </p>
<p>Une fois a l&#39;intérieur du container, il suffit d&#39;installer <strong>py-spy</strong> et de lancer nos commandes.</p>
<p><code>py-spy top --pid 1</code></p>
<br />

<p>B<strong>ingo</strong> ! Nous voyons l&#39;activité de notre autre container !<br><a href="https://substackcdn.com/image/fetch/$s_!7Uhu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6434687-4728-4bca-84fd-a80af92c8f77_706x249.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731061126-d6434687-4728-4bca-84fd-a80af92c8f77_706x249.png)</a></p>
<br />

<h1>Choses à noter :</h1>
<ul>
<li><p><strong>Limitation de Version Python</strong> : Actuellement, il est impossible de debug des programmes Python avec une version supérieure à 3.11 en utilisant <code>py-spy</code>, car il ne supporte pas encore ces versions.</p>
<br />
</li>
<li><p><strong>Fonctionnalités Étendues de py-spy</strong> : Les capacités de <code>py-spy</code> vont bien au-delà de ce qui a été présenté ici. L&#39;outil offre une multitude d&#39;options pour le debug, notamment la prise en charge du multiprocessing, la possibilité de trier les instructions qui retiennent le Global Interpreter Lock (GIL), ainsi que l&#39;observation des processus IDLE.</p>
<br />
</li>
<li><p><strong>Interprétation des Résultats</strong> : en plus de flamegraph, <code>py-spy</code> propose également d&#39;autres visualisations et outils d&#39;analyse.</p>
</li>
</ul>
<br />

<hr>
<p>Si vous appréciez la newsletter StuffAndCode, vous pouvez vous inscrire afin de ne manquer aucun article !</p>
<p>🐍 Et recevez en PLUS, un <strong>article exclusif</strong> sur comment profiler n&#39;importe quel type de code Python.</p>
<p><a href="https://blog.stuffandcode.com/subscribe?">Abonnez-vous maintenant</a>  </p>
<hr>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Optimisez la vitesse et la mémoire de votre code Python avec __slots__]]></title>
            <link>https://stuffandcode.writizzy.blog/p/optimisez-la-vitesse-et-la-memoire</link>
            <guid>https://stuffandcode.writizzy.blog/p/optimisez-la-vitesse-et-la-memoire</guid>
            <pubDate>Tue, 30 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Qu'est-ce que slots ? ========================= TL;DR: slots en Python est utilisé pour limiter les attributs qui peuvent être définis dans une classe. slots est un attribut de classe qui contient gén...]]></description>
            <content:encoded><![CDATA[<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!RH5Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58a4bc80-7080-4511-bd4d-0535072fc2e1_1024x1024.webp">![A vibrant digital illustration featuring the Python logo moving at high speed. The logo is depicted as if it's racing forward, with dynamic speed lines and motion blur effects surrounding it, symbolizing rapid performance and efficiency in Python programming. The background is a sleek, digital landscape, suggesting a high-tech environment, and further emphasizing the theme of speed and advanced coding capabilities in Python.](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731052713-58a4bc80-7080-4511-bd4d-0535072fc2e1_1024x1024.webp "A vibrant digital illustration featuring the Python logo moving at high speed. The logo is depicted as if it's racing forward, with dynamic speed lines and motion blur effects surrounding it, symbolizing rapid performance and efficiency in Python programming. The background is a sleek, digital landscape, suggesting a high-tech environment, and further emphasizing the theme of speed and advanced coding capabilities in Python.")</a></p>
<br />

<h1>Qu&#39;est-ce que <strong>slots</strong> ?</h1>
<br />

<p><code>TL;DR: __slots__</code> en Python est utilisé pour limiter les attributs qui peuvent être définis dans une classe.</p>
<br />

<p><code>__slots__ </code>est un <strong>attribut de classe</strong> qui contient généralement des strings.</p>
<p>Lorsque __<code>slots__</code> est défini dans une classe, les instances de cette classe ne peuvent avoir que les attributs spécifiés dans __<code>slots__</code>.</p>
<p>L&#39;utilisation de __<code>slots__</code> peut être utile pour économiser de l&#39;espace mémoire en limitant le nombre d&#39;attributs d&#39;une instance.</p>
<p>Par exemple, si une classe n&#39;a besoin que de quelques attributs et que nous savons que nous n&#39;aurons pas besoin d&#39;ajouter des attributs de manière dynamique, il peut être avantageux de les spécifier dans __<code>slots__</code> .</p>
<br />

<h2>Une classe normale</h2>
<p><a href="https://substackcdn.com/image/fetch/$s_!G3FZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08904d78-7147-4013-aa43-8250dd50dbb1_1864x928.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731053470-08904d78-7147-4013-aa43-8250dd50dbb1_1864x928.png)</a></p>
<br />


<p>Nous pouvons accéder à l&#39;attribut <code>__dict__</code> et également assigner un nouvel attribut dynamiquement sans soucis.</p>
<br />

<h2>Une classe avec <strong>slots</strong></h2>
<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!JqEA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc5f4aff-d691-47e9-952b-95652a1d5523_1864x1256.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731053936-fc5f4aff-d691-47e9-952b-95652a1d5523_1864x1256.png)</a></p>
<br />


<p>Ici, nous avons défini <code>__slots__</code> avec un tuple qui contient une string &quot;<strong>x</strong> &quot;, ce qui signifie que nos instances ne pourront avoir qu&#39;un seul attribut qui s&#39;appelle <strong>x</strong> . Avoir d&#39;autres attributs est impossible.  </p>
<p>Par exemple, en regardant la ligne 9, si l&#39;on essaie d&#39;assigner un autre attribut comme ce que nous avons fait dans l&#39;autre classe nous allons obtenir une <strong>AttributeError</strong>.</p>
<p>De la même manière, comme nous l&#39;avons expliqué, l&#39;attribut <code>__dict__</code> n&#39;existe pas dans ce contexte et génère donc un <strong>AttributeError</strong>.</p>
<br />

<p>Si vous utilisez des dataclasses, vous pouvez également utiliser <code>__slots__</code> facilement, et ceci aura le même effet que précédemment.<br><a href="https://substackcdn.com/image/fetch/$s_!dVFR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F533d2de4-9028-4cc6-87e4-2a8469b4196c_1344x260.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731054561-533d2de4-9028-4cc6-87e4-2a8469b4196c_1344x260.png)</a></p>
<br />

<hr>
<p>Si vous appréciez la newsletter StuffAndCode, vous pouvez vous inscrire afin de ne manquer aucun article !</p>
<p>🐍 Et recevez en PLUS, un <strong>article exclusif</strong> sur comment profiler n&#39;importe quel type de code Python.</p>
<p><a href="https://blog.stuffandcode.com/subscribe?">Abonnez-vous maintenant</a>  </p>
<hr>
<h1>Mesurons les performances</h1>
<p>Nous allons comparer deux scripts qui font la même chose.</p>
<p>La seule différence est que l&#39;attribut &quot;<strong>x</strong> &quot; sera défini dans le <code>__slots__</code> du deuxième script.</p>
<br />

<p>Voici notre premier script :<br><a href="https://substackcdn.com/image/fetch/$s_!Hg1e!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48f2ba6a-dc10-4f18-9756-116e66c7972e_1864x1256.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731055090-48f2ba6a-dc10-4f18-9756-116e66c7972e_1864x1256.png)</a></p>
<br />

<p>Et voici notre second script :<br><a href="https://substackcdn.com/image/fetch/$s_!x5jM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb75015bb-a41d-49ec-8dfc-33eb5312181c_1864x1256.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731055484-b75015bb-a41d-49ec-8dfc-33eb5312181c_1864x1256.png)</a></p>
<br />

<p>Si l&#39;on décide de profiler avec <a href="https://blog.stuffandcode.com/p/les-differents-types-de-profilers">cProfile</a>nos scripts, nous obtenons ces résultats :</p>
<br />

<p><a href="https://substackcdn.com/image/fetch/$s_!ui7m!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd67fa7ee-4afd-468b-9972-5d85507b1dd6_2318x1578.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731055891-d67fa7ee-4afd-468b-9972-5d85507b1dd6_2318x1578.png)</a></p>
<br />

<p>Nous pouvons constater un gain de performance de 1.24x plus rapide ou 23.53% plus rapide en spécifiant le <code>__slots__</code>.</p>
<br />

<h1>Mesurons la mémoire</h1>
<p>Nous l&#39;avons dit, un des intérets de <code>__slots__</code> est surtout lorsque nous avons beaucoup d&#39;instances de cet objet là.</p>
<p>Nous avons vu que c&#39;était déjà bien plus rapide, mais qu&#39;en est t-il de la <strong>mémoire</strong> que nous pouvons économiser ?</p>
<br />

<p>Pour ceci nous allons mesurer avec un profiler de mémoire appelé &quot;<a href="https://github.com/pythonspeed/filprofiler">filprofiler</a>&quot;.</p>
<br />

<p>Voici les résultats pour la version sans <code>__slots__</code><br><a href="https://substackcdn.com/image/fetch/$s_!BHfY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa705a8af-ede1-474d-b97d-a8cfb5d1c4e1_1124x347.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731056330-a705a8af-ede1-474d-b97d-a8cfb5d1c4e1_1124x347.png)</a></p>
<p>Et voici les résultats avec <code>__slots__</code><br><a href="https://substackcdn.com/image/fetch/$s_!D6NO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66056dca-849d-4bce-9f49-b4d42f8cf4a2_1121x346.png">![](https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731056645-66056dca-849d-4bce-9f49-b4d42f8cf4a2_1121x346.png)</a></p>
<br />


<p>Le résultat est flagrant, nous avons donc une consommation de mémoire 1.82 fois moindre, soit une réduction de 54.8%, ce qui est assez énorme.</p>
<br />

<p>Il faut cependant se rappeler que dans ce cas précis nous avons 1 million d&#39;instances. Ce qui peut potentiellement vous arriver en fonction de votre cas d&#39;usage.</p>
<p>Si jamais vous avez peu d&#39;instances, vous ne verrez pas un gain de performance aussi élevé que ce que nous avons obtenu.</p>
<br />

<p>Vous pouvez trouver plus de documentation <a href="https://wiki.python.org/moin/UsingSlots">ici</a>.</p>
<h1>Quizz Time</h1>
<p>Allez, c&#39;est parti pour quelques petites questions :</p>
<ul>
<li><p>Quels sont les intérêts d&#39;utiliser <code>__slots__</code> ?</p>
</li>
<li><p>Que met-on dans l&#39;attribut de classe <code>__slots__</code> ?</p>
</li>
<li><p>Quand pouvons-nous profiter au maximum de l&#39;utilisation de <code>__slots__</code> ?</p>
</li>
<li><p>Qu&#39;avons nous utilisé pour profiler la mémoire ?</p>
</li>
</ul>
<br />

<hr>
<p>Si vous appréciez la newsletter StuffAndCode, vous pouvez vous inscrire afin de ne manquer aucun article !</p>
<p>🐍 Et recevez en PLUS, un <strong>article exclusif</strong> sur comment profiler n&#39;importe quel type de code Python.</p>
<p><a href="https://blog.stuffandcode.com/subscribe?">Abonnez-vous maintenant</a>  </p>
<hr>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment j'ai accéléré de 22x la vitesse de mon script Python]]></title>
            <link>https://stuffandcode.writizzy.blog/p/comment-jai-accelere-de-22x-la-vitesse</link>
            <guid>https://stuffandcode.writizzy.blog/p/comment-jai-accelere-de-22x-la-vitesse</guid>
            <pubDate>Tue, 23 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Cet article est le premier d'une série. Nous avons augmenté de 22x les performances de notre script Python, ce qui est honorable, mais peut-être pourrons nous aller plus loin ? Le problème ===========...]]></description>
            <content:encoded><![CDATA[<p><em>Cet article est le premier d&#39;une série. Nous avons augmenté de 22x les performances de notre script Python, ce qui est honorable, mais peut-être pourrons nous aller plus loin ?</em><br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731068591-6fec7bb6-b1ff-4331-b671-2523661dc4ba_1024x1024.webp" alt="" /></p>
<h1>Le problème</h1>
<p>J&#39;ai voulu écrire un script qui me permet de détecter quels sont <strong>les pixels les plus fréquents sur un ensemble d&#39;images</strong>. Est-ce le blanc, le noir, d&#39;autres ?</p>
<p>Lorsque nous travaillons sur un petit volume d&#39;image, ce n&#39;est pas un souci, Mais lorsque le volume d&#39;images augmente, par exemple pour des dizaines d&#39;images, le temps de traitement devient un enjeu significatif.</p>
<p>Nous allons voir dans cet article comment à partir d&#39;un script le plus simple possible mais peu performant, nous pouvons arriver à un script aux performances bien plus élevées.</p>
<ol>
<li><h1>La version naïve</h1>
</li>
</ol>
<p>Partons du principe que nous voulons <strong>télécharger 200 images,</strong> et les analyser pour compter les pixels les plus fréquemment rencontrés. Nous pourrons donc identifier les couleurs dominantes.</p>
<p><em>Note : Pour garantir une constance dans les résultats lors des benchmarks, j&#39;ai choisi <strong>d&#39;héberger les images localement</strong>.</em></p>
<p><em>De plus, j&#39;ai mis en place <strong>un délai artificiel de 100ms</strong> pour simuler une connexion à un site distant. Cette simulation nous aide à mieux représenter les conditions réelles d&#39;une application en ligne.</em></p>
<p><em>Note : La performance de chaque script a été évaluée dans un environnement contrôlé, sur la même machine et dans des conditions aussi similaires que possible.</em></p>
<p><em>Pour cela, j&#39;ai développé un script, qui e</em>xécute chaque script à tester 5 fois* et mesure le temps d&#39;exécution grâce à la fonction <strong>time.perf_counter</strong>.*</p>
<p><em>Nous calculons ainsi la moyenne (mean) et l&#39;écart-type (stdev) des temps d&#39;exécution pour obtenir une évaluation précise des performances.</em></p>
<p><em>Ces données nous permettent de comparer les scripts de manière plus nuancée et détaillée.</em></p>
<p><em>Aussi, nous mettons en avant le temps d&#39;accélération par rapport à la version initiale du script. C&#39;est pourquoi la première version, étant notre point de référence, présente un facteur d&#39;accélération (<strong>speedup</strong>) de 1x.</em></p>
<p><em>Un speedup  1 signifie donc une dégradation des performances par rapport au script initial.</em></p>
<p>Voici notre script initial :</p>
<p><em><strong>Je vous invite à regarder les extraits de code qui vous seront montrés tout au long de cet article et d&#39;essayer d&#39;observer quelles sont les différences entre chaque version. Cela vous donnera une meilleure compréhension globale et vous habituera aux syntaxes utilisées.</strong></em><br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731068990-439040f1-ea53-447c-931a-ff8108032c1c_2436x3612.png" alt="" /></p>
<p>Ce script est certainement la manière la plus naïve d&#39;écrire une solution pour ce problème. On itère sur chacune des URL, on récupère l&#39;image (sans la stocker), et une fois toutes les images acquises, on itère dessus pour comptabiliser les pixels et enrichir un dictionnaire. Ensuite, nous utilisons l&#39;objet <strong>Counter</strong> du module <strong>collections</strong> afin d&#39;obtenir les pixels les plus courants.</p>
<p>Lorsque nous mesurons le temps d&#39;exécution de ce script nous constatons :</p>
<p><code>v1: Mean = 59.17s, Stdev = 9.09s, Speedup = 1.00x</code> </p>
<p>Ce temps semble assez long (<strong>pratiquement 1 minute</strong> pour traiter 200 images), tentons d&#39;optimiser cela.</p>
<ol start="2">
<li><h1>La version asynchrone</h1>
</li>
</ol>
<p>Nous avons vu que la première étape consiste à télécharger des images en masse; c&#39;est typiquement une <strong>problématique IO,</strong> et les images sont indépendantes les unes des autres. C&#39;est un bon candidat afin d&#39;ajouter de la concurrence.</p>
<p>Nous allons continuer à utiliser le module <strong>httpx,</strong> mais nous allons l&#39;utiliser en mode asynchrone avec le module <strong>asyncio</strong> afin de pouvoir envoyer plusieurs requêtes en même temps.</p>
<p>On peut facilement exécuter les 200 coroutines en simultanées grâce à <strong>asyncio.gather</strong> et récupérer tous les résultats d&#39;un coup.</p>
<p><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731069352-22f88cc5-0d32-4c82-8d5c-f787d4569895_2436x3744.png" alt="" /></p>
<p>Voici les résultats pour ce script :</p>
<p><code>tutov2: Mean = 38.85s, Stdev = 0.27s, Speedup = 1.52x</code>  </p>
<p>Sans trop de surprises, <strong>nous avons gagné 20 secondes</strong>, car il y a 200 images qui répondent avec un délai d&#39;au moins 100ms. Voici un bon début.</p>
<p>Voyons voir si nous pouvons mieux faire.</p>
<ol start="3">
<li><h1>Utilisation de ProcessPoolExecutor</h1>
</li>
</ol>
<p>Nous avons réussi à implémenter de la concurrence afin de pouvoir réduire le temps de téléchargement des images. Pouvons_nous faire mieux en rajoutant du <strong>parallélisme</strong> dans notre programme ?</p>
<p>Lorsque nous parcourons nos pixels et les comptons, c&#39;est une problématique de calcul. Donc <strong>CPU</strong> et non plus IO. Cette fois <strong>asyncio</strong> ne nous sauvera pas.  </p>
<p>De la même manière le module <strong>threading</strong> est inadapté aux problématique CPU à cause du <strong>GIL (Global Interpreter Lock)</strong>, qui, pour rappel, empêche plusieurs instructions Python de s&#39;exécuter en même temps au sein d&#39;un même Process.</p>
<p>Ainsi nous allons utiliser le module <strong>concurrent.futures,</strong> et l&#39;excellent<strong>ProcessPoolExecutor</strong> qui nous permet de mettre en place du <strong>multiprocessing</strong> facilement et d&#39;ajouter du parallélisme à notre programme.</p>
<hr>
<p>Si vous appréciez la newsletter StuffAndCode, vous pouvez vous inscrire afin de ne manquer aucun article !</p>
<p>🐍 Et recevez en PLUS, un <strong>article exclusif</strong> sur comment profiler n&#39;importe quel type de code Python.</p>
<p><a href="https://blog.stuffandcode.com/subscribe?">Abonnez-vous maintenant</a>  </p>
<hr>
<p>Merci de lire StuffAndCode Newsletter ! Abonnez-vous gratuitement pour recevoir de nouveaux posts et soutenir mon travail.  </p>
<p><em>Note : Lorsqu&#39;on démarre un process Python, il y a un <strong>main Thread</strong> qui s&#39;exécute et le <strong>GIL</strong> est présent dans chacun des processes. Si nous démarrons plusieurs processes avec un seul Thread, chacun aura son propre GIL. Ainsi, ils ne se gêneront pas ,et nous obtiendrons du vrai parallélisme.
Cependant, démarrer des processes est plus couteux que de démarrer des Threads. De même concernant le transfert des données entre processes.</em></p>
<p>Voici une implémentation de &quot;comment paralléliser le comptage des pixels sur plusieurs processeurs&quot;.</p>
<p>Nous avons également remplacé le système de dictionnaire pour compter directement avec le Counter.</p>
<p><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731069805-203af94b-1912-4cb6-8fe5-699e103ceb64_2404x3940.png" alt="" /></p>
<p>Voici les résultats pour cette version.</p>
<p><code>tutov3: Mean = 12.01s, Stdev = 0.68s, Speedup = 4.93x</code>  </p>
<p>On peut noter une amélioration significative du temps d&#39;exécution.</p>
<p>Nous obtenons <strong>12 secondes</strong> contre 59 secondes lors de la première version.  </p>
<p>En cas de doute quant à la pertinence de <strong>ProcessPoolExecutor</strong> ou de <strong>ThreadPoolExecutor</strong> , sachez que vous pouvez le changer facilement, puis relancer le programme afin de mesurer. Dans mon cas le programme <strong>avec le ThreadPoolExecutor</strong> prenait 18 secondes au lieu de 12 secondes.</p>
<p>Ce qui signifie que l&#39;ajout du ProcessPoolExecutor est bénéfique.</p>
<ol start="4">
<li><h1>Amélioration de notre Counter</h1>
</li>
</ol>
<p>Okay, maintenant qu&#39;on a parallélisé nos téléchargements et nos traitements, quelles améliorations sont possibles ? C&#39;est là qu&#39;il devient intéressant d&#39; utiliser des <strong>outils de profiling</strong> , comme détaillé dans <a href="https://blog.stuffandcode.com/p/les-differents-types-de-profilers">cet article</a>.</p>
<p>Si on profile le code précédent (de la partie 3), on remarque que le Counter occupe une part significative du temps de traitement.<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731070307-e431f4ba-6a44-4be3-bb85-0a9f290f008d_1083x446.png" alt="" /></p>
<p><em>Note : La durée totale de 14.122s ne correspond pas exactement au temps précédent de 12.01s car, dans ce cas, nous avons profilé une seule exécution du code. De plus, le fait de profiler <strong>ajoute de l&#39;overhead</strong> donc du temps supplémentaire (chose qui n&#39;est pas faite lorsque nous mesurons uniquement le temps d&#39;exécution).</em></p>
<p>Lorsque nous utilisons notre objet <strong>Counter</strong> pour compter et récupérer les pixels les plus courants, nous stockons les pixels sous forme de <strong>tuple de trois int, RGB</strong> . Ce qui n&#39;est pas optimal. Counter fonctionne bien mieux avec des valeurs numériques et plus simples.   </p>
<p>C&#39;est pour cela que nous allons ajouter une étape de conversion pour <strong>transformer un tuple sous forme (R, G, B) en un entier</strong> qui représente cette même couleur.</p>
<p>Pour ce faire, nous allons créer une fonction <strong>rgb_to_int</strong> `` qui prend en paramètre un tuple d&#39;int retournant un entier équivalent à ce tuple.   </p>
<p>Pour ceux qui s&#39;intéressent aux détails techniques de la conversion de RGB à un entier, voici une explication. Il est important de noter que, dans notre optimisation, <strong>nous remplaçons les tuples RGB dans notre Counter par de simples entiers.</strong></p>
<h2>4.1) Transformation d&#39;un tuple RGB en entier</h2>
<p>Laissez-moi vous montrer comment une petite modification peut engendrer une grande différence.</p>
<p>On va prendre l&#39;exemple de la couleur rose représentée par le code RGB (<strong>255, 0, 255</strong>).</p>
<pre><code>Conversion de RGB à Int :

RGB : (255, 0, 255)
En binaire, cela donne : R = 11111111, G = 00000000, B = 11111111

Ensuite, on décale ces valeurs :
    R décalé de 16 bits (&lt;&lt; 16) devient 111111110000000000000000
    G décalé de 8 bits (&lt;&lt; 8) reste 0000000000000000
    B reste inchangé : 11111111

On combine ces valeurs pour obtenir : 111111110000000011111111 en binaire
Ce qui donne en entier : 16711935
</code></pre>
<p>Pour simplifier, on prend chaque composante de couleur, et on la décale pour former un seul nombre. Ce nombre unique représente notre couleur originale. Cela ressemble à la manière dont on gère les permissions sur Linux : Read (4), Write (2), Execute (1). Lorsqu&#39;on veut toutes les permissions, on utilise le chiffre 7, qui est la somme de 4, 2 et 1.</p>
<p>Le principe du décalage en binaire, comme dans l&#39;opération  1  2 , signifie que l&#39;on prend le nombre 1, le convertit en binaire (0001), et décale ses bits de deux places vers la gauche. Ainsi, 0001 devient 0100, ce qui équivaut à 4 en décimal.</p>
<p>Le code qui correspond à cette opération est celui-ci :<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731070732-61567af1-b133-4556-969e-fcb774ea7a2d_2108x532.png" alt="" /></p>
<h2>4.2) Transformation de l&#39;entier vers le code RGB</h2>
<p>Lorsque nous voulons afficher les résultats, nous cherchons à retrouver le code RGB initial. Donc, il nous faut un moyen <strong>d&#39;inverser le calcul</strong> que nous avons fait. Voici comment cela fonctionne:</p>
<p>Le <strong>masquage binaire</strong> est une opération qui permet d&#39;isoler certains bits dans un nombre binaire. Dans le cas de la fonction <strong>int_to_rgb</strong> , le masque  <strong>0xFF</strong> est utilisé pour extraire les 8 bits les plus bas d&#39;un entier. Le masque <strong>0xFF est de l&#39;hexadecimal</strong> et est équivalent à <strong>11111111 en binaire</strong>. Ce qui signifie qu&#39;il conserve les 8 bits avec lesquels il est comparé et met tous les autres à zéro.</p>
<pre><code>Entier : 16711935
Binaire : 111111110000000011111111

Extraction de Rouge (R) :
  Décalage : &gt;&gt; 16 donne 11111111
  Masque : 11111111 &amp; 11111111 = 11111111
  Résultat : 255 (en décimal)

Extraction de Vert (G) :
  Décalage : &gt;&gt; 8 donne 0000000011111111
  Masque : 00000000 &amp; 11111111 = 00000000
  Résultat : 0 (en décimal)

Extraction de Bleu (B) :
  Pas de décalage
  Masque : 11111111 &amp; 11111111 = 11111111
  Résultat : 255 (en décimal)

RGB : (255, 0, 255)
</code></pre>
<p>Lorsque nous faisons du développement web, ou autre, nous ne sommes pas souvent confrontés à cette syntaxe. Aussi un peu de pratique peut être bénéfique. Le mieux est de démarrer un interpréteur Python et de faire quelques tests.</p>
<p>Le code représentant l&#39;opération de transformation d&#39;entier vers RGB est celui-ci :<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731071038-d65256c8-c920-4c19-9efd-cbc2f3e900d0_2108x600.png" alt="" /></p>
<p>Fin de la parenthèse.</p>
<p>Voyons le code complet.</p>
<p><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731071500-6019980e-4fb2-4c0a-baa1-a08dcc63eea7_2860x5248.png" alt="" /></p>
<p>Voici le résultat pour ce script :</p>
<p><code>v4: Mean = 7.80s, Stdev = 0.61s, Speedup = 7.58x</code>  </p>
<p>Wow, le temps est bien réduit rien qu&#39;avec cette modification.</p>
<p>Je n&#39;aurai pas pensé que <strong>le type de données utilisées par Counter</strong> pourrait avoir une influence aussi grande.</p>
<ol start="5">
<li><h1>Traitement par batch</h1>
</li>
</ol>
<p>Nous l&#39;avons évoqué précédemment, nous avons besoin de plusieurs processes afin de paralléliser les calculs, car sinon cela prendrait trop de temps sur un seul processeur.  </p>
<p>Cependant, le fait de compter les pixels n&#39;est pas si long que ça. Démarrer un nouveau process et faire transiter les data est coûteux.   </p>
<p>Ne serait_il pas possible de faire quelque chose à ce niveau ?</p>
<p>Pourquoi ne pas traiter plusieurs images d&#39;un coup, puis mettre à jour un <strong>Counter</strong> pour un <strong>batch d&#39;images ?</strong></p>
<p>La mécanique reste la même, nous spawnons toujours autant de processes (les Process ne sont créés qu&#39;une fois et <strong>réutilisés</strong> grâce au <strong>ProcessPoolExecutor</strong> ), nous allons traiter plusieurs images d&#39;un coup. Donc <strong>réduire le nombre de réutilisations</strong> de chaque process et aussi <strong>réduire le nombre de données</strong> qui doivent passer du process au main process pour avoir les résultats finaux.</p>
<p>Car au lieu d&#39;envoyer 200 (nombre d&#39;images) fois des Counter, nous allons en envoyer plus que 20 (pour des batchs de 10 images). Ce qui évite la procédure de pickling et unpickling qui consomme également du temps.</p>
<p><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731071958-f00ccda9-0812-46eb-9e53-d5d8365a0a5f_2988x5316.png" alt="" /></p>
<p>Voici les résultats pour cette version :</p>
<p><code>v5: Mean = 2.67s, Stdev = 0.03s, Speedup = 22.20x</code></p>
<p><strong>2.67s pour télécharger et traiter les 200 images, contre 59s à la base, c&#39;est pas trop mal.</strong></p>
<p>Nous avons donc accompli encore un joli pas en avant.</p>
<p>Le choix du nombre 10 pour les batches est <strong>arbitraire</strong>. Il est conseillé d&#39;expérimenter avec différentes tailles de batch pour trouver le nombre le plus optimal, en augmentant ou diminuant cette valeur.</p>
<p>Voici le profiling.</p>
<p>Comme précédemment, les résultats sont différents en raison de l&#39;overhead du profiling. Nous pouvons donc voir qu&#39;il y a du temps passé dans le <strong>multiprocessing</strong> et également que nous passons encore pas mal de temps dans le <code>Counter.update</code>.</p>
<p>Nous verrons si nous pouvons améliorer ces performances dans un prochain article !<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731072469-b7b2dbff-b799-4be9-99c4-b8f6f7a3ef28_1117x570.png" alt="" /></p>
<h1>Les résultats</h1>
<p>Si on compare les résultats de toutes nos versions, nous obtenons ceci :</p>
<p><code>Results: </code><br><code>v1: Mean = 59.17s, Stdev = 9.09s, Speedup = 1.00x</code><br><code>v2: Mean = 38.85s, Stdev = 0.27s, Speedup = 1.52x</code><br><code>v3: Mean = 12.01s, Stdev = 0.68s, Speedup = 4.93x</code><br><code>v4: Mean = 7.80s, Stdev = 0.61s, Speedup = 7.58x</code><br> <code>v5: Mean = 2.67s, Stdev = 0.03s, Speedup = 22.20x</code>  </p>
<h1>Les prochaines étapes ?</h1>
<p>Il existe d&#39;autres optimisations que nous pourrions explorer.</p>
<p>Par exemple, envisager d&#39;utiliser des bibliothèques comme <strong>Numpy</strong> ou <strong>Polars</strong> , qui exécutent du code en <strong>C</strong> et <strong>Rust</strong> respectivement. Ces outils sont spécialement optimisés pour le traitement efficace de données volumineuses.</p>
<p>Nous pourrions aussi choisir d&#39;exécuter notre code sur une autre implémentation de Python que <strong>CPython</strong> (qui est l&#39;implémentation standard de Python). Des alternatives comme <strong>PyPy</strong> ou <strong>IronPython</strong> peuvent offrir des avantages en terme de performances pour certains types de tâches.</p>
<p>Une autre stratégie consiste à écrire certaines parties critiques de notre code en C, <strong>Cython</strong> ou <strong>Rust</strong>.</p>
<p>Et bien sûr, il y a sûrement d&#39;autres méthodes d&#39;optimisation à découvrir et à expérimenter.</p>
<p>Mais ça, nous le garderons pour une prochaine fois !</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment profiler et optimiser n'importe quel type de code Python]]></title>
            <link>https://stuffandcode.writizzy.blog/p/comment-profiler-et-optimiser-nimporte</link>
            <guid>https://stuffandcode.writizzy.blog/p/comment-profiler-et-optimiser-nimporte</guid>
            <pubDate>Fri, 19 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Une fonction, un script, une API, une application web Django ? ]]></description>
            <content:encoded><![CDATA[<p><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731040306-bb13e6b3-6cc5-4424-bb54-2b0224492ada_1792x1024.webp" alt="" title="The image is designed for an article about profiling various types of software in Python, symbolizing the concept of providing 'all the keys' to the readers. The central element is a large, intricate key ring holding a diverse array of keys. Each key is uniquely designed to represent different aspects of software profiling: one key looks like a Python script, another resembles a function, a third key is shaped like an API, and another like a web application. The background features a digital landscape with Python code snippets and profiling graphics (graphs, timers) floating around, suggesting the idea of unlocking the potential of software through profiling. The scene should be colorful, engaging, and informative, illustrating the concept of giving readers the essential tools and knowledge for software profiling in Python." /></p>
<p>Python est polyvalent, flexible et nous permet de réaliser beaucoup de choses.</p>
<p>Cependant, parfois notre code est lent et aurait bien besoin d&#39;être optimisé !</p>
<p>Dans ce guide, nous allons voir des méthodes pour profiler les cas les plus fréquents dans le monde réel :</p>
<ul>
<li>Une fonction Python</li>
<li>Un script Python</li>
<li>Une API Flask  FastAPI</li>
<li>Une route Django</li>
</ul>
<p><strong>Le profiling est une étape essentielle à l&#39;optimisation de vos performances.</strong></p>
<p><strong>Sans profiling vous risquez de faire des optimisations inutiles, voire dans le pire des cas, nuire aux performances.</strong></p>
<p>Le code fourni a été pensé afin d&#39;être réutilisable et adaptable à vos besoins.</p>
<p>Sans plus attendre, analysons notre script à profiler.</p>
<p>*Note : Il existe des dizaines de méthodes pour profiler et mesurer les performances : le module <strong>timeit</strong> , la librairie <strong>py-spy</strong> , la commande Linux **perf...*<em>Ici, je ne vous parlerai que de ceux que je trouve les plus performants et qui apportent le plus d&#39;informations avec le minimum d&#39;efforts.</em></p>
<h1>Notre point de départ</h1>
<p>Prenons comme point de départ un script de base qui traite une liste de <strong>446 901 villes</strong> , extraites d&#39;un fichier texte nommé <em>cities.txt</em>.</p>
<p>Notre défi ! <strong>Compter les villes dont les noms contiennent plus de X voyelles.</strong></p>
<p>Ce script, <strong>intentionnellement non optimisé</strong>, va nous servir de cobaye. Il illustre une situation courante en développement :</p>
<ul>
<li>on écrit d&#39;abord un code qui fonctionne.</li>
<li>on mesure les performances.</li>
<li>on l&#39;optimise pour améliorer ses performances si besoin.</li>
</ul>
<p>Mais ici, nous irons plus loin.</p>
<p>Non seulement, nous profilerons ce script, mais également nous l&#39;adapterons pour le profiler dans les divers environnements cités précédemment (script, API, Django ...).</p>
<p>Notre objectif : comprendre comment visualiser les problèmes de performances en fonction de la situation . Bien que <strong>nous ne réaliserons pas l&#39;optimisation</strong> elle-même, cette analyse fournira un cadre solide afin d&#39;effectuer vos propres améliorations et mesurer l&#39;impact sur les performances.</p>
<p>Voici notre code :<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731041349-7cf630ba-70cc-4176-a7ec-8f97016890e8_1478x1732.png" alt="" /></p>
<ol>
<li><h1>Une fonction</h1>
</li>
</ol>
<p>Ici nous voulons analyser la fonction <code>find_cities_with_vowel_count</code> afin de mesurer le temps qu&#39;elle prend, et les endroits chronophages.</p>
<h2>1.1) Un décorateur pour mesurer le temps</h2>
<p>Afin d&#39;estimer le temps pris par la fonction, nous pouvons créer un <strong>décorateur</strong> que nous appliquerons sur les fonctions qui nous intéressent, et avoir une idée du <strong>temps d&#39;execution.</strong> L&#39;avantage est d&#39;avoir un <strong>court overhead,</strong> donc de ne pas trop fausser les résultats.</p>
<p>Voici une implémentation possible de ce décorateur :<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731041769-619271fb-9d9b-4aa9-8d38-055e1f964890_1478x780.png" alt="" /></p>
<p>Ensuite, nous pouvons l&#39;ajouter à notre fonction pour pouvoir mesurer le temps :<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731042274-7f00c450-4664-4269-b1a7-5269649f57a7_1478x372.png" alt="" /></p>
<p>Lorsque nous exécutons notre programme normalement, nous avons ceci qui s&#39;affiche dans la console :</p>
<pre><code>92768
Elapsed time: 0.3522 seconds
</code></pre>
<p>Okay, donc cela traduit que dans notre liste, nous avons 92 768 villes ayant 5 ou plus de 5 voyelles. Et nous pouvons constater que notre script a pris <strong>0.3522 secondes</strong> pour s&#39;exécuter.</p>
<p>Mais, les indications sont trop maigres pour nous permettre de trouver les endroits chronophages.  </p>
<h2>1.2) Un décorateur pour profiler</h2>
<p>Dans cet article, nous avons recours à <strong>cProfile</strong> , un <a href="https://blog.stuffandcode.com/p/les-differents-types-de-profilers">deterministic profiler</a> intégré à Python.</p>
<p><strong>cProfile</strong> est un module de Python, donc nous pouvons l&#39;utiliser directement dans notre script, nous pouvons donc réaliser un décorateur.</p>
<blockquote>
<p><strong>Un rapide rappel sur la manière de lire les résultats de cProfile</strong>:</p>
<ul>
<li><p><strong>Nombre d&#39;appels (ncalls) :</strong> cProfile affiche le nombre total d&#39;appels pour chaque fonction sous la colonne &#39;ncalls&#39;. Cela inclut les appels directs et récursifs.</p>
</li>
<li><p><strong>Appels récursifs (ncalls) :</strong> Les appels récursifs sont également inclus dans &#39;ncalls&#39;. Les appels récursifs sont souvent indiqués sous une forme &#39;3/1&#39;, où le premier nombre est le nombre total d&#39;appels et le second le nombre d&#39;appels récursifs distincts.</p>
</li>
<li><p><strong>Temps total par fonction (cumtime) :</strong> Sous la colonne &#39;cumtime&#39;, cProfile indique le temps cumulatif passé dans la fonction et toutes les fonctions qu&#39;elle appelle.</p>
</li>
<li><p><strong>Temps propre par fonction (tottime) :</strong> Le temps propre, indiqué dans la colonne &#39;tottime&#39;, représente le temps passé uniquement dans la fonction elle-même, excluant le temps passé dans les fonctions appelées.</p>
<p>Tiré de <a href="https://blog.stuffandcode.com/p/les-differents-types-de-profilers">Les différents types de profilers</a></p>
</li>
</ul>
</blockquote>
<p>Voici un exemple :<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731043153-ed53e0cd-627a-416d-af57-8cdbd06664c8_1478x1042.png" alt="" /></p>
<p>Ce décorateur fonctionne sur le même principe que le précédent.</p>
<p>Il commence un profiling au début de l&#39;exécution de la fonction, et arrête ce profiling aprés que la fonction ait terminé. Les résultats sont ensuite affichés en utilisant le module <strong>pstats</strong>.</p>
<p>Après avoir décoré notre fonction avec ce décorateur et exécuté notre code, nous observons plusieurs éléments clés :<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731043486-1774941b-df02-44e7-ab9b-26bfd5fb8aa9_1478x372.png" alt="" /></p>
<p><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731044185-a1d16043-6f10-4a7c-91e7-5c4291a25680_1752x968.png" alt="" /></p>
<p>Nous pouvons voir :</p>
<p>Le temps d&#39;exécution passe de 0.3522 secondes à 2.10 secondes, soit presque un <strong>facteur de 6</strong>.</p>
<p>Bien que cet <strong>overhead</strong> soit important, il fournit des informations précieuses :   </p>
<p>Par exemple, la méthode <code>str.lower</code> est appelée 19 002 850 fois, prenant 0.909 secondes -- presque la <strong>moitié de notre temps total</strong> . De plus, la fonction <code>count_vowels</code> est appelée 446 902 fois (une fois par ville) et prend 1.102 secondes.</p>
<p><strong>Ces deux fonctions représentent donc la majorité du temps d&#39;exécution.</strong></p>
<p>Ces observations indiquent clairement que pour optimiser notre script, nous devons réduire le nombre d&#39;appels à <code>.lower(),</code> et examiner de près le code de <code>count_vowels</code>.  </p>
<ol start="2">
<li><h1>Un script</h1>
</li>
</ol>
<p>Lorsqu&#39;il s&#39;agit de mesurer le temps d&#39;exécution total d&#39;un script Python, plusieurs méthodes sont à notre disposition.</p>
<h2>2.1) Utiliser la command Linux &quot;time&quot;</h2>
<p>La méthode la plus simple consiste à utiliser la commande Linux <code>time</code>. Cette approche offre un mesure rapide à mettre en place, avec un très <strong>faible overhead</strong>.</p>
<p>Avec <code>time</code>, nous obtenons des informations basiques, mais utiles. Par exemple, nous pouvons voir que le script prend 0.367 secondes pour s&#39;exécuter, ce qui est similaire à ce que nous avons mesuré précédemment avec <code>time.perf_counter</code>.</p>
<p>De plus, l&#39;indication de 99% d&#39;utilisation du CPU suggère que notre programme consacre presque tout son temps à des opérations de calcul. Pour plus de détails sur la commande <code>time</code>, vous pouvez consulter l&#39;article dédié aux <a href="https://blog.stuffandcode.com/p/les-differents-types-de-profilers">différents types de profilers</a>.<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731044554-1c419ea7-f56b-4a3c-8f69-123e7b0051e1_1478x260.png" alt="" /></p>
<p>Bien que ces informations soient utiles, elles n&#39;apportent pas suffisamment de détails pour une mettre en place des optimisations.</p>
<h2>2.2) Utiliser cProfile en CLI</h2>
<p>Pour obtenir des informations plus détaillées, nous pouvons utiliser <strong>cProfile</strong> en ligne de commande. Cette méthode permet de profiler le script et d&#39;en examiner les résultats de manière approfondie. Pour ce faire, il suffit d&#39;exécuter la commande suivante :<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731044975-15ac0d82-26e7-4f6b-9c94-3287b33f1360_1752x1116.png" alt="" /></p>
<p>Si vous voulez visualiser les résultats d&#39;une manière un peu plus friendly, vous pouvez utiliser une librairie comme <strong><a href="https://github.com/jiffyclub/snakeviz">SnakeViz</a></strong> :</p>
<pre><code>&gt;&gt;&gt; pip install snakeviz

&gt;&gt;&gt; python -m cProfile -o result.cprof profile_script.py

&gt;&gt;&gt; snakeviz result.cprof
</code></pre>
<p><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731045475-15c33ea3-23c1-45b4-a410-cb2ed34252bb_2257x791.gif" alt="" /></p>
<h2>2.3) Utiliser PyInstrument</h2>
<p>Nous pouvons également utiliser un statistical profiler comme <a href="https://github.com/joerick/pyinstrument">PyInstrument</a>pour mesurer le script en CLI.<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731046518-a272fac8-5643-4f81-98ff-db2e3205928b_2726x1302.png" alt="" /></p>
<p>Il est également possible d&#39;avoir un rendu différent si on le souhaite, par exemple, en utilisant <strong><a href="https://github.com/jlfwong/speedscope">speedscope</a></strong>.</p>
<pre><code>&gt;&gt;&gt; pip install pyinstrument

&gt;&gt;&gt; npm install -g speedscope

&gt;&gt;&gt; pyinstrument -r speedscope -o profile.speedscope.json profile_script.py

&gt;&gt;&gt; speedscope profile.speedscope.json
</code></pre>
<p><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731046877-09cf961a-5cc4-4dfd-a528-dc52420b9b6b_2560x1142.png" alt="" /></p>
<p>De la même manière que <strong><a href="https://github.com/jiffyclub/snakeviz">SnakeViz</a></strong>, c&#39;est un rendu plus intéractif. Il est possible de naviguer dans les différentes vues afin de visualiser le déroulement de notre programme.</p>
<p>Nous pouvons filtrer, réorganiser, exporter, voir les call stacks. Bref, c&#39;est un outil assez complet.</p>
<ol start="3">
<li><h1>Une API</h1>
</li>
</ol>
<p>L&#39;un des cas les plus courants est le profiling d&#39;applications web.</p>
<p>Examinons trois scénarios fréquents : le profiling de routes dans <strong>Flask</strong> , <strong>FastAPI</strong> et <strong>Django</strong>.</p>
<p>Nous allons mettre en place des <strong>solutions génériques que vous pourrez réutiliser facilement.</strong></p>
<h2>3.1) Flask</h2>
<p>Prenons l&#39;exemple d&#39;une application <strong>Flask</strong> basique.</p>
<p>Nous avons configuré un serveur Flask avec une route simple vers &quot;/&quot;, qui exécute notre code et renvoie le résultat.</p>
<p>En utilisant <strong>Flask</strong> , il est possible <strong>d&#39;executer du code juste avant qu&#39;une requête HTTP ne soit traitée</strong> , et également <strong>après qu&#39;elle ait été traitée</strong>.</p>
<p>Voyez-vous les possibilités ? 🧐<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731047297-7b5053d2-eecb-4414-80d5-68f9437969d1_1176x1190.png" alt="" /></p>
<p>Finalement nous nous retrouvons à faire quasiment la même chose qu&#39;avec le <strong>décorateur</strong> que l&#39;on a vu plus haut !</p>
<p>Avant de rentrer dans notre route, nous démarrons le profiler et nous le stoppons une fois l&#39;opération terminée.</p>
<p>Le véritable avantage ici, c&#39;est la capacité de choisir quand profiler notre code. Comment ? Simplement en ajoutant un <strong>query parameter</strong> <code>?profile</code> dans l&#39;URL.</p>
<p>Cette approche est très pratique car elle ne nécessite aucun changement dans les routes existantes ou l&#39;ajout de décorateurs supplémentaires.</p>
<p><strong>Accès standard à l&#39;application :</strong></p>
<ul>
<li><strong>URL</strong> : <a href="http://127.0.0.1:5000/">http://127.0.0.1:5000/</a></li>
<li><strong>Résultat</strong> : &quot;OK&quot;, affichage normal sans profiling</li>
</ul>
<p><strong>Accès avec profiling activé :</strong></p>
<ul>
<li><strong>URL</strong> : <a href="http://127.0.0.1:5000/?profile">http://127.0.0.1:5000/?profile</a></li>
<li><strong>Résultat</strong> : le résultat du profiling est généré et renvoyé</li>
</ul>
<p><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731047775-19fc9444-3ed8-4c67-8ac1-26f910acd82c_1259x434.png" alt="" /></p>
<p>Vous pouvez évidemment modifier ce code pour utiliser d&#39;autres types de profilers plutôt que PyInstrument.</p>
<h2>3.2) FastAPI</h2>
<p>De la même manière que Flask, nous allons utiliser le concept de <strong>middleware sur FastAPI</strong>.</p>
<p>C&#39;est du code qui est executé pour chaque requête.   </p>
<p>Nous allons vérifier s&#39;il y a un paramètre&quot;<strong>profile&quot;</strong> dans l&#39;URL. Si oui, nous renvoyons le résultat du profiling. Dans le cas contraire, nous renvoyons le résultat normal.<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731048335-b955557b-4313-4179-8c5a-b5d3fc5c8d30_1176x818.png" alt="" /></p>
<p>Et hop là :<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731048917-2922cf6d-6e2f-4ea1-b5d2-2fcdbad65d42_1215x352.png" alt="" /></p>
<p>Comme pour les autres cas, nous utilisons <strong>PyInstrument</strong> dans cet exemple, mais vous pouvez utiliser un autre profiler sans soucis.</p>
<h2>3.3) Django</h2>
<p>Si vous utilisez Django, l&#39;intégration de PyInstrument pour le profiling requiert seulement quelques étapes simples :</p>
<ol>
<li><strong>Installation de PyInstrument</strong> : Commencez par installer PyInstrument dans votre projet.</li>
<li><strong>Configuration du Middleware</strong> : Il suffit d&#39;<strong>ajouter un middleware</strong> en modifiant la variable <code>MIDDLEWARE</code> dans vos paramètres Django.</li>
</ol>
<p>Cette modification se résume à l&#39;ajout d&#39;une seule ligne dans la configuration.<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731049418-ae0f70c8-ed0c-4607-8451-d5a07c6f1f6a_1344x372.png" alt="" /></p>
<p>Quand le <strong>middleware</strong> de PyInstrument est configuré, il suffit d&#39;ajouter un <strong>query parameter</strong> &quot;<strong>profile</strong> <code>&quot;</code> à vos URL pour activer le profiling.</p>
<p>Voici comment cela fonctionne en pratique :</p>
<ul>
<li><strong>URL Standard</strong> : Accéder à <code>http://127.0.0.1:5000/example</code> renverra le résultat normal</li>
<li><strong>URL avec Profiling</strong> : Accéder à <code>http://127.0.0.1:5000/example?profile=1</code> les résultats du profiling seront renvoyés pour cette route.</li>
</ul>
<p>Vous pouvez également ajouter de la configuration pour activer ou désactiver ce comportement en production.</p>
<h1>Conclusion</h1>
<p>Le profiling peut s&#39;appliquer à presque tout en Python: scripts, fonctions, API, applications web.</p>
<p>Les exemples de code fournis sont conçus pour être directement <strong>réutilisables</strong> et <strong>adaptables</strong>à différents types de profilers selon les besoins.</p>
<p>Les concepts de base sont similaires, rendant ces techniques transposables à d&#39;autres frameworks web.</p>
<h1>Liens utiles</h1>
<ul>
<li><a href="https://blog.stuffandcode.com/p/les-differents-types-de-profilers">Les différents types de profiler</a></li>
<li><a href="https://docs.python.org/3/library/profile.html">Le deterministic profiler**cProfile**</a> <a href="https://docs.python.org/3/library/profile.html">et**pstats**</a></li>
<li><a href="https://github.com/joerick/pyinstrument">Le statistical profiler PyInstrument</a></li>
<li>Outils de profiling pour <strong>Django</strong> :<ul>
<li><a href="https://github.com/jazzband/django-silk">Django Silk</a></li>
<li><a href="https://github.com/jazzband/django-debug-toolbar">Django Debug Toolbar</a></li>
</ul>
</li>
<li>Les outils de visualisation:<ul>
<li><a href="https://github.com/jiffyclub/snakeviz">SnakeViz</a></li>
<li><a href="https://github.com/jlfwong/speedscope">Speedscope</a></li>
<li><a href="https://github.com/jrfonseca/gprof2dot">gprof2dot</a></li>
</ul>
</li>
</ul>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Les différents types de profilers et les mesures de temps]]></title>
            <link>https://stuffandcode.writizzy.blog/p/les-differents-types-de-profilers</link>
            <guid>https://stuffandcode.writizzy.blog/p/les-differents-types-de-profilers</guid>
            <pubDate>Tue, 16 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[1) Les types de profilers ========================= Il existe deux types de profiling couramment utilisés en Python : le Deterministic Profiling et le Statistical Profiling. StuffAndCode Newsletter es...]]></description>
            <content:encoded><![CDATA[<p><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731061706-42a134f2-a924-4aca-bb77-811cd8914ddf_1024x1024.webp" alt="A vibrant and imaginative illustration of a Python profiler in action. The image shows a large, stylized magnifying glass hovering over lines of Python code, illuminating the code beneath. Inside the magnifying area, the code is transformed into a dynamic 3D visualization, with peaks and valleys indicating the performance of different sections of the code. The background is filled with digital motifs and Python logos, emphasizing the technological and coding theme. This representation captures the essence of a profiler analyzing and optimizing Python code." title="A vibrant and imaginative illustration of a Python profiler in action. The image shows a large, stylized magnifying glass hovering over lines of Python code, illuminating the code beneath. Inside the magnifying area, the code is transformed into a dynamic 3D visualization, with peaks and valleys indicating the performance of different sections of the code. The background is filled with digital motifs and Python logos, emphasizing the technological and coding theme. This representation captures the essence of a profiler analyzing and optimizing Python code." /></p>
<ol>
<li><h1>Les types de profilers</h1>
</li>
</ol>
<p>Il existe deux types de profiling couramment utilisés en Python : le <strong>Deterministic Profiling</strong> et le <strong>Statistical Profiling</strong>.<br>StuffAndCode Newsletter est une publication soutenue par ses lecteurs. Pour recevoir de nouveaux posts et soutenir mon travail, envisagez de devenir un abonné gratuit ou payant.  </p>
<p>Note : <em>Ici, on ne parle pas de profiling de mémoire mais uniquement de profiling de performances.</em></p>
<h2><strong>1.1) Deterministic Profilers</strong></h2>
<p><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731062229-9a6e3259-403a-434d-b475-97fec18ffa2b_1456x691.gif" alt="" /></p>
<p>Les profilers déterministes, tels que <strong>cProfile</strong>, tracent et mesurent tous les appels de toutes les fonctions. Cette approche fournit des données détaillées sur chaque fonction appelée.</p>
<p>Nous pouvons avoir plusieurs informations grâce à ce type de profiling :</p>
<ul>
<li><strong>Nombre d&#39;appels (ncalls) :</strong> cProfile affiche le nombre total d&#39;appels pour chaque fonction sous la colonne &#39;ncalls&#39;. Cela inclut les appels directs et récursifs.</li>
<li><strong>Temps total par fonction (cumtime) :</strong> Sous la colonne &#39;cumtime&#39;, cProfile indique le temps cumulatif passé dans la fonction et toutes les fonctions qu&#39;elle appelle.</li>
<li><strong>Temps propre par fonction (tottime) :</strong> Le temps propre, indiqué dans la colonne &#39;tottime&#39;, représente le temps passé uniquement dans la fonction elle-même, excluant le temps passé dans les fonctions appelées.</li>
<li><strong>Appels récursifs (ncalls) :</strong> Les appels récursifs sont également inclus dans &#39;ncalls&#39;. Les appels récursifs sont souvent indiqués sous une forme &#39;3/1&#39;, où le premier nombre est le nombre total d&#39;appels et le second le nombre d&#39;appels récursifs distincts.</li>
<li><strong>Ordre d&#39;appel :</strong> L&#39;ordre d&#39;appel n&#39;est pas directement visible dans les rapports standards de cProfile, mais des outils de visualisation tel que SnakeViz peuvent être utilisés pour voir l&#39;ordre d&#39;appel des fonctions.</li>
</ul>
<p>Bien que le profiler puisse introduire un certain <strong>overhead</strong>, il reste utile pour l&#39;identification des bottlenecks dans les programmes. Nous combinons souvent cProfile avec d&#39;autres outils, comme des outils de visualisation pour interpréter les résultats si jamais le profiling est complexe.</p>
<h2>L&#39;overhead, quèsaco ?</h2>
<blockquote>
<p><em>L&#39;overhead, en profiling, est le coût supplémentaire en termes de performance que votre programme doit supporter lorsqu&#39;il est analysé par un profiler.</em></p>
<p><em>Prenons un exemple concret : si vous utilisez un profiler déterministe comme cProfile en Python, il va enregistrer des détails sur chaque appel de fonction et chaque opération dans votre programme.</em></p>
<p><em>Bien que cette information soit précieuse pour l&#39;optimisation,<strong>le processus d&#39;enregistrement lui-même nécessite des ressources supplémentaires</strong> - utilisation supplémentaire du processeur, consommation de mémoire, etc.</em>&gt;
<em>Cela signifie que votre programme, lorsqu&#39;il est profilé, va probablement <strong>s&#39;exécuter plus lentement</strong> et utiliser plus de ressources qu&#39;en temps normal.</em>&gt;
<em>Ce ralentissement et cette consommation accrue sont ce qu&#39;on appelle <strong>l&#39;overhead</strong> . Il est important de tenir compte de cet overhead lors de l&#39;analyse des résultats du profiler, car <strong>il peut fausser la perception des performances</strong> réelles du programme.</em>&gt;
<em>En bref, il est important de prendre en compte que l&#39;overhead représente le coût en performance dû au processus de profiling lui-même.</em></p>
</blockquote>
<h2><strong>1.2) Statistical Profilers</strong></h2>
<p>Les profilers statistiques, comme <strong>py-spy</strong> ou <strong>pyinstrument</strong> , adoptent une approche différente. Plutôt que d&#39;enregistrer chaque appel de fonction, ils enregistrent la <strong>call stack à intervalles réguliers</strong> . Cette méthode génère <strong>moins d&#39;overhead</strong>, ce qui est avantageux pour les applications en production où minimiser l&#39;impact sur les performances est crucial.</p>
<p>La call stack est une structure de données qui permet de stocker des informations sur les instructions de notre programme qui sont en cours d&#39;execution.   </p>
<p>Plus d&#39;informations <a href="https://en.wikipedia.org/wiki/Call_stack">ici</a><br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731062682-4e0442d1-a6f7-4f9c-976f-cbdbbf48cc73_2800x2000.png" alt="" /></p>
<p>Bien que les profilers statistiques fournissent une vue d&#39;ensemble utile, ils peuvent parfois manquer de certains détails fins que les profilers déterministes révèlent.</p>
<p>Ils sont particulièrement adaptés pour obtenir un aperçu global des performances d&#39;une application, et pour identifier les zones où le temps est principalement consacré.</p>
<ol start="2">
<li><h1>Les mesures de temps</h1>
</li>
</ol>
<h2><strong>2.1) CPU Time</strong></h2>
<p>Le CPU time correspond au temps durant lequel le <strong>CPU est occupé</strong> à exécuter du code. Par exemple, si vous avez une fonction qui envoie une requête à un site web et attend la réponse pour renvoyer le HTML, la majeure partie du temps sera passée en attente de la réponse du site, et non dans l&#39;exécution de votre code proprement dit. Ainsi, le CPU time enregistré sera relativement faible.</p>
<h2><strong>2.2) Wall Clock Time</strong></h2>
<p>Le wall clock time, quant à lui, se mesure comme si on le faisait en <strong>regardant une montre</strong>. Il est particulièrement utile pour évaluer les performances d&#39;applications qui interagissent beaucoup avec d&#39;autres services, ou qui effectuent des appels à des API. Cette mesure permet de déterminer si la lenteur de l&#39;application est due à votre code ou à l&#39;attente de réponses externes.</p>
<h2><strong>Exemple : Mesurer le CPU time et le Wall clock time d&#39;un script Python</strong></h2>
<p>Prenons un script Python basique, <code>dummy.py</code>, qui télécharge un gros fichier :<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731066218-ce682a80-8704-43d0-8593-b962c52b4596_2988x1056.png" alt="" /></p>
<p>Nous pouvons mesurer le CPU time et le Wall clock time avec la commande Linux <code>time</code>.<br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731066527-1a1a6edb-3026-4651-a702-d1d525a46c02_2988x600.png" alt="" /></p>
<p>Ici, <strong>real</strong> représente le temps total écoulé depuis le lancement jusqu&#39;à la fin de l&#39;exécution du script (Wall clock time).</p>
<p><strong>User</strong> est le CPU time utilisé par le processus utilisateur (User CPU time).</p>
<p><strong>Sys</strong> indique le CPU time utilisé par le système pour des tâches au niveau du kernel, telles que l&#39;écriture de fichiers ou l&#39;allocation de mémoire. Plus de détail sur cette réponse de StackOverflow :</p>
<p><a href="https://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1">Explication real, user, sys de la commande time</a></p>
<p>Dans cet exemple, la majorité du temps est consacrée à a<strong>ttendre la réponse du serveur</strong>(I/O), plutôt qu&#39;à l&#39;utilisation du CPU. Sur35 secondes environ d&#39;exécution du script, seulement 2.6 secondes environ (user + sys) sont consacrées à des tâches CPU. Cela indique que pour optimiser ce code, il faudrait se concentrer principalement sur la partie réseau.</p>
<h2>Les résultats</h2>
<p>Nous allons profiler un script très simple afin de montrer les résultats que nous pouvons obtenir avec <strong>cProfile</strong> et <strong>pyinstrument</strong> , pour présenter un cas concret et expliquer comment interpréter les résultats.   </p>
<p>Voici le code en question, une boucle avec des prints et un <code>time.sleep</code>.</p>
<p>Quelque chose de simple mais qui va pouvoir illustrer nos propos.</p>
<p><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731067285-5b0825a7-bf9d-4564-ae66-c1cbd17006b7_1608x1384.png" alt="" /></p>
<h2>cProfile</h2>
<p>Afin de profiler notre programme, nous pouvons utiliser la commande suivante :</p>
<p><code>python -m cProfile -s cumtime some_script.py</code><br><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731067875-f9b169ff-a576-40ca-bfb7-126066264053_2910x866.png" alt="" /></p>
<p>Nous pouvons voir que le script prend 1 seconde, donc ceci est un <strong>wall clock time</strong> ainsi que les endroits qui prennent le plus de temps. Par exemple, nous pouvons voir que <code>time.sleep</code> est la méthode qui prend le plus de temps (cf. la colonne <code>tottime</code>).</p>
<p>Aussi, nous pouvons observer que la fonction <code>print</code> a été appelée 10 fois. Nous pouvons changer l&#39;ordre de cet output en fonction de ce que nous voulons examiner.</p>
<p>Nous pouvons également utiliser des outils de visualisation afin d&#39;avoir un rendu plus graphique, mais ce n&#39;est pas l&#39;objet de cet article.</p>
<h2>PyInstrument</h2>
<p>Passons maintenant à pyinstrument. Pour l&#39;installer, vous pouvez executer la commande :</p>
<p><code>pip install pyinstrument</code> </p>
<p>et puis commencer le profiling :</p>
<p><code>pyinstrument some_script.py</code></p>
<p><img src="https://writizzy.b-cdn.net/blogs/e0b1e9d1-8856-4916-ad9c-947818f70572/media/1782731068203-253ea006-6ff1-4639-b1dc-a9f9cc32d5ae_2904x818.png" alt="" /></p>
<p>Sur ce screenshot, nous pouvons constater plusieurs choses :</p>
<ul>
<li>tout d&#39;abord, nous avons la durée qui est le <strong>wall clock time</strong> ainsi que le <strong>CPU time</strong>.</li>
<li>Nous pouvons également remarquer que le CPU time est vraiment très court comparé au wall clock time, ce qui signifie que le <strong>CPU a été en attente</strong> pendant la quasi-totalité du programme.</li>
</ul>
<p>Enfin, nous pouvons visualiser les méthodes qui prennent le plus de temps sous forme d&#39;arbre, c&#39;est assez pratique. Il peut y avoir plusieurs branches qui indiquent ce qui prend le plus de temps et permettent de savoir l&#39;origine de l&#39;appel.</p>
<h1>Quizz Time</h1>
<p>Je trouve qu&#39;il est toujours pertinent de se remémorer ce qu&#39;on vient de lire, pour vérifier si l&#39;on a vraiment retenu quelque chose.</p>
<p>Voici quelques questions :</p>
<ul>
<li>Quels sont les différents types de profilers ?</li>
<li>Quelles sont leurs différences ?</li>
<li>Pouvez-vous nommer un profiler de chaque type ?</li>
<li>Quelle est la différence entre wall clock time et CPU time ?</li>
</ul>
<p>Si vous avez tout juste, vous avez compris l&#39;essentiel de cet article, bien joué !</p>
<hr>
<p>Si vous appréciez la newsletter StuffAndCode, vous pouvez vous inscrire afin de ne manquer aucun article !</p>
<p>🐍 Et recevez en PLUS, un <strong>article exclusif</strong> sur comment profiler n&#39;importe quel type de code Python.</p>
<p><a href="https://blog.stuffandcode.com/subscribe?">Abonnez-vous maintenant</a>  </p>
<hr>
<p>StuffAndCode Newsletter est une publication soutenue par ses lecteurs. Pour recevoir de nouveaux posts et soutenir mon travail, envisagez de devenir un abonné gratuit ou payant.  </p>
]]></content:encoded>
        </item>
    </channel>
</rss>