<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: John Rush</title>
    <description>The latest articles on DEV Community by John Rush (@johnrushwrs).</description>
    <link>https://dev.to/johnrushwrs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4087314%2F58d1dd32-34a2-4d01-befe-83082c5270d9.jpeg</url>
      <title>DEV Community: John Rush</title>
      <link>https://dev.to/johnrushwrs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johnrushwrs"/>
    <language>en</language>
    <item>
      <title>An unlikely experiment</title>
      <dc:creator>John Rush</dc:creator>
      <pubDate>Sat, 22 Aug 2026 23:13:55 +0000</pubDate>
      <link>https://dev.to/johnrushwrs/an-unlikely-experiment-1ogp</link>
      <guid>https://dev.to/johnrushwrs/an-unlikely-experiment-1ogp</guid>
      <description>&lt;p&gt;There are so many great places to learn about out there on the internet, and one of those great places is the legendary Linux Kernel. If you stumble around some of that codebase (even if you don't understand much) you might see an &lt;a href="https://github.com/search?q=repo%3Atorvalds%2Flinux+unlikely&amp;amp;type=code" rel="noopener noreferrer"&gt;interesting pattern&lt;/a&gt; littered throughout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unlikely&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;condition_that_does_not_happen_much&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// do thing&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// do more common thing&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is this mysterious &lt;code&gt;unlikely&lt;/code&gt;? Why do we need to tell the computer that something is &lt;code&gt;unlikely&lt;/code&gt;, and what does that even do?&lt;/p&gt;

&lt;p&gt;It turns out that this simple statement can end up having a huge benefit to performance (or detriment, if used improperly). Let's see if we can understand this a bit, so that we can in turn improve our own programs :)&lt;/p&gt;

&lt;h2&gt;
  
  
  What is &lt;code&gt;unlikely&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;The definition of &lt;code&gt;unlikely&lt;/code&gt; is actually a simple macro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define unlikely(e) __builtin_expect(!!(e), 0)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes in an operand &lt;code&gt;e&lt;/code&gt; which is passed to &lt;code&gt;__builtin_expect&lt;/code&gt; along with &lt;code&gt;0&lt;/code&gt;. &lt;code&gt;__builtin_expect&lt;/code&gt; is a special function that just annotates an expression to tell the compiler that it should "expect" the value of the expression to be equal to the second value which in this case is zero.&lt;/p&gt;

&lt;p&gt;What we are effectively doing here is giving the compiler some &lt;a href="https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005fexpect" rel="noopener noreferrer"&gt;apriori&lt;/a&gt; information about what branch of the program is likely or unlikely to be taken. With this information it can prime the CPU to start with the correct probabilities for &lt;a href="https://en.wikipedia.org/wiki/Branch_predictor" rel="noopener noreferrer"&gt;branch prediction&lt;/a&gt; and improve performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay that was a lot, can you give an example?
&lt;/h2&gt;

&lt;p&gt;Sure I can! Let's great a simple example where we have two identical loops, except one will use the &lt;code&gt;unlikely&lt;/code&gt; macro and the other uses the opposing &lt;code&gt;likely&lt;/code&gt; macro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;unlikely_simple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;num_runs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;num_runs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unlikely&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;likely_simple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;num_runs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;num_runs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;likely&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In these simple loops, we take the &lt;code&gt;if&lt;/code&gt; branch 0.1% of the time and the &lt;code&gt;else&lt;/code&gt; branch 99.9% of the time. Naturally the actually more "likely" path here is the &lt;code&gt;else&lt;/code&gt; branch, but we can trick the compiler to thinking the opposite with our choice of macro.&lt;/p&gt;

&lt;p&gt;Let's observe the performance of these two loops, and also modify that &lt;code&gt;1000&lt;/code&gt; to be different values so we can see the effect of different percentages on the likely and unlikely loops:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;percent chance to take first branch&lt;/th&gt;
&lt;th&gt;average run time (micro seconds)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;unlikely_simple&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;48,899&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unlikely_simple&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;51,736&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unlikely_simple&lt;/td&gt;
&lt;td&gt;1%&lt;/td&gt;
&lt;td&gt;58,496&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unlikely_simple&lt;/td&gt;
&lt;td&gt;.1%&lt;/td&gt;
&lt;td&gt;53,637&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;likely_simple&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;48,643&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;likely_simple&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;75,979&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;likely_simple&lt;/td&gt;
&lt;td&gt;1%&lt;/td&gt;
&lt;td&gt;79,594&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;likely_simple&lt;/td&gt;
&lt;td&gt;.1%&lt;/td&gt;
&lt;td&gt;72,342&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;From the data we can see that as we decrease the percent of values that will take the first branch, the &lt;code&gt;likely_simple&lt;/code&gt; compiler hint becomes less and less accurate. We hinted to the compiler that we should optimize towards the first branch, but real execution often goes to the second branch - leading to worse performance.&lt;/p&gt;

&lt;p&gt;Okay so we've seen some data that shows our hint is having an effect on how the loop is processed. But why is that? The answer is in the assembly:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Assembly generated using the command: &lt;code&gt;gcc -O2 unlikely.c -o bin/unlikely&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Likely loop Assembly&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000000100000460 &amp;lt;_likely_simple&amp;gt;:
... 
# start of the loop
100000488: d343fd2d     lsr     x13, x9, #3
10000048c: 9bcb7dad     umulh   x13, x13, x11
100000490: d344fdad     lsr     x13, x13, #4
100000494: 9b0c29ad     madd    x13, x13, x12, x10
100000498: f10005bf     cmp     x13, #0x1

# branching check. if it's equal, go to else at "4b8"
10000049c: 540000e0     b.eq    0x1000004b8 &amp;lt;_likely_simple+0x58&amp;gt;

# the if branch. perform total += 2
1000004a0: 91000908     add     x8, x8, #0x2

# loop condition
1000004a4: 9100054a     add     x10, x10, #0x1
1000004a8: 91000529     add     x9, x9, #0x1
1000004ac: f1000400     subs    x0, x0, #0x1

# loop re-enter and loop exit checks here
1000004b0: 54fffec1     b.ne    0x100000488 &amp;lt;_likely_simple+0x28&amp;gt;
1000004b4: 14000004     b       0x1000004c4 &amp;lt;_likely_simple+0x64&amp;gt;

# the else branch. perform total += i
1000004b8: 8b0a0108     add     x8, x8, x10

# loop exit
1000004bc: 17fffffa     b       0x1000004a4 &amp;lt;_likely_simple+0x44&amp;gt;
1000004c0: d2800008     mov     x8, #0x0                ; =0
1000004c4: aa0803e0     mov     x0, x8
1000004c8: d65f03c0     ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Unlikely loop Assembly&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00000001000004cc &amp;lt;_unlikely_simple&amp;gt;:
...
# start of the loop
1000004f4: d343fd2d     lsr     x13, x9, #3
1000004f8: 9bcb7dad     umulh   x13, x13, x11
1000004fc: d344fdad     lsr     x13, x13, #4
100000500: 9b0c29ad     madd    x13, x13, x12, x10
100000504: f10005bf     cmp     x13, #0x1

# branching check. if it's NOT equal, go to if at "524"
100000508: 540000e1     b.ne    0x100000524 &amp;lt;_unlikely_simple+0x58&amp;gt;

# the else branch. perform total += i
10000050c: 8b0a0108     add     x8, x8, x10

# loop condition
100000510: 9100054a     add     x10, x10, #0x1
100000514: 91000529     add     x9, x9, #0x1
100000518: f1000400     subs    x0, x0, #0x1

# loop re-enter and loop exit checks here
10000051c: 54fffec1     b.ne    0x1000004f4 &amp;lt;_unlikely_simple+0x28&amp;gt;
100000520: 14000004     b       0x100000530 &amp;lt;_unlikely_simple+0x64&amp;gt;

# the if branch. perform total += 2
100000524: 91000908     add     x8, x8, #0x2
100000528: 17fffffa     b       0x100000510 &amp;lt;_unlikely_simple+0x44&amp;gt;
10000052c: d2800008     mov     x8, #0x0                ; =0
100000530: aa0803e0     mov     x0, x8
100000534: d65f03c0     ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lots of assembly, but the key part is the order of the branches in the likely loop versus the unlikely loop. In the unlikely loop, we have the &lt;code&gt;total += i + 1&lt;/code&gt; assembly immediately following the if condition check and a branch to the &lt;code&gt;total += 2&lt;/code&gt; assembly. The compiler uses the opposite ordering and branching in the likely loop, favoring to have the &lt;code&gt;total += 2&lt;/code&gt; assembly first and and the &lt;code&gt;total += i + 1&lt;/code&gt; second. &lt;/p&gt;

&lt;p&gt;This ordering is important to the execution of the loop because the CPU will generally &lt;em&gt;pipeline&lt;/em&gt; instructions, meaning that it will prefetch and pre-emptively execute several instructions after the currently executing one. The pre-fetch happens in the order of the instructions in memory, hence why the order matters. This explains why in our experiment we would see varying performance for the two functions. One of them would go against that pipelining and cause the CPU work to be wasted, and the other was aligned with it and lead to better performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There are lots of little gems like this in open source repos that we can all learn from. While &lt;code&gt;unlikely&lt;/code&gt; and &lt;code&gt;likely&lt;/code&gt; touch upon several low level concepts that &lt;em&gt;likely&lt;/em&gt; won't matter for the average program, it is always good to have this knowledge in your backpocket. You never know when this type of code might 2X your performance!&lt;/p&gt;

</description>
      <category>c</category>
      <category>programming</category>
      <category>linux</category>
      <category>performance</category>
    </item>
    <item>
      <title>What's an event loop anyways?</title>
      <dc:creator>John Rush</dc:creator>
      <pubDate>Fri, 21 Aug 2026 00:09:32 +0000</pubDate>
      <link>https://dev.to/johnrushwrs/whats-an-event-loop-anyways-1n6o</link>
      <guid>https://dev.to/johnrushwrs/whats-an-event-loop-anyways-1n6o</guid>
      <description>&lt;p&gt;Event loops are a paradigm for processing events different than your typical single-threaded or multi-threaded application. Your request gets broken down into async "events" that are executed in a loop to improve performance and minimize synchronization across threads. It is famously used by &lt;a href="https://nodejs.org/en" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; as the backbone of their event processing and also by several other technologies like &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; and &lt;a href="https://nginx.org/" rel="noopener noreferrer"&gt;Nginx&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this article I'll explain the reason for why this paradigm was created and what it tries to optimize. By the end you'll come out a little wiser, and know more than just "don't block the event loop" :).&lt;/p&gt;

&lt;h2&gt;
  
  
  Motiviation - why event loops?
&lt;/h2&gt;

&lt;p&gt;To understand why we need event loops we will explore a simple but key example. Take this straightforward HTTP request code, which sends a request and then tries to read the response from the socket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_http_request_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;socket_fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_socket_for_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;write_res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We do two things in this call - &lt;code&gt;write&lt;/code&gt; data out and &lt;code&gt;read&lt;/code&gt; data in. Both of these actions will end up triggering syscalls through the kernel that write and fetch data. In terms of time spent on the CPU, this is relatively inexpensive; sending out packets takes very little time, and eventually reading the response will also take very little CPU time. The key time lost is from &lt;em&gt;waiting&lt;/em&gt; on the server to respond to us. &lt;code&gt;os.read&lt;/code&gt; will block this thread until the response is available, meaning that the thread cannot be used for any other processing during this time.&lt;/p&gt;

&lt;p&gt;If our service is single-threaded, this means that we can't make any requests in parallel and are stuck waiting on any previous requests to finish. But of course, most services are not single-threaded, so this isn't a huge problem? Let's continue with the example code, imagining that instead we are processing these requests with multiple threads pulling from a &lt;code&gt;request_queue&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RequestQueue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_work&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RequestQueue&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;request_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;send_http_request_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_http_request_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;socket_fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_socket_for_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;write_res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_requests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]]):&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RequestQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;do_work&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright now we're cooking! We have multiple threads processing the list of requests, and we have 10X'ed our throughput, &lt;em&gt;amazing&lt;/em&gt;. But we weren't here to just create yet another multi-threaded setup, so what is the problem here? Well, one major problem is that we are now managing 10 threads, which all have to be synchronized to get work from &lt;code&gt;request_queue&lt;/code&gt;. Synchronization via locks can add a lot of overhead, slowing down our program (so we probably didn't actually get 10x gains here... unlucky). Another problem is that our throughput has a defined upper bound - 10 requests. We could solve that by bumping the number of threads. Let's say I want 10k requests as my throughput - I'll just increase it to 10k and things will be fine, right? Not quite. Aside from the increased sync overhead, we also have some additional issues that come up (as the saying goes, there is no such thing as a free lunch..):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extra memory to manage each thread (linear scaling)&lt;/li&gt;
&lt;li&gt;Coordination with every other thread -&amp;gt; latency (quadratic scaling)&lt;/li&gt;
&lt;li&gt;More context switches for the CPU. These are not free and can be a drag on latency at high concurrency.&lt;/li&gt;
&lt;li&gt;More threads == more CPU cache line thrashing!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We didn't have all these problems with our single-threaded program, so maybe we can tackle this problem in a different way. Let's see if we can get back that single-thread magic &lt;em&gt;and&lt;/em&gt; start processing in parallel!&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter the event loop
&lt;/h2&gt;

&lt;p&gt;As you might have guessed, our event loop will be single threaded. So how are we going to avoid the blocking call that we saw earlier? Two things - a paradigm shift and a bit of help from the kernel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paradigm shift: working with Events
&lt;/h3&gt;

&lt;p&gt;Now that we can't call &lt;code&gt;read&lt;/code&gt; directly in our user code, we need to instead return something to express our &lt;em&gt;intent&lt;/em&gt; to read as well as a way to continue executing our code. Let's call that "something" an &lt;code&gt;Event&lt;/code&gt;. Concretely, an event describes the &lt;em&gt;intent&lt;/em&gt; to do a syscall like &lt;code&gt;read&lt;/code&gt; or &lt;code&gt;write&lt;/code&gt; and a pointer to the code that should handle the result of that syscall. Putting that into code, the &lt;code&gt;Event&lt;/code&gt; class would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;READ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;WRITE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;write&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;file_descriptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;
    &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;
    &lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking it down, our event class contains:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;file_descriptor&lt;/code&gt; to say which fd we want to perform the action on&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;action&lt;/code&gt; that we intend to execute&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;continuation&lt;/code&gt; that describes what to call with the result of the event&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;input&lt;/code&gt; for the action&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user_data&lt;/code&gt; to allow passing along extra data we need to &lt;code&gt;continuation&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;result&lt;/code&gt; which will be read in the continuation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With this class we have created a bit of &lt;a href="https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering" rel="noopener noreferrer"&gt;indirection&lt;/a&gt; between event processing and the start of processing. This enables us to defer execution of &lt;code&gt;read&lt;/code&gt; until we are confident that there is actually data available to be read, which we will touch on in the next section. Before we get there though, we need to modify our code to actually talk in terms of events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_read_finish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;request_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_data&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_http_request_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;socket_fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_socket_for_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;write_res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;create_read_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_read_finish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we create an &lt;code&gt;Event&lt;/code&gt; to express our intent to &lt;code&gt;read&lt;/code&gt;, provide a pointer to our continuation, and some additional data we care about. Our code looks very different and has some extra boilerplate, but we are now in a position to execute the reads at the right time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Waiting for data: the &lt;code&gt;select&lt;/code&gt; syscall
&lt;/h3&gt;

&lt;p&gt;Here's where the kernel comes in to help us build our event loop. The kernel provides a syscall called &lt;code&gt;select&lt;/code&gt; which allows for asking to be notified when a &lt;em&gt;set&lt;/em&gt; of file descriptors are ready for reading or writing. The key here is that &lt;code&gt;select&lt;/code&gt; allows for waiting on multiple descriptors at the same time, enabling us to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;gather a list of events that we are waiting on data for (i.e. need to call &lt;code&gt;read&lt;/code&gt; with)&lt;/li&gt;
&lt;li&gt;start executing again when &lt;em&gt;any&lt;/em&gt; of those events is ready for processing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the crux of event-based processing. We gather together as many pending events that we can, and then process them once they are ready. Rather than blocking for one single event to finish, we can continously make progress and add to the list of events that we are deferring until the data is available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;With our code returning &lt;code&gt;Event&lt;/code&gt; objects and knowledge of the &lt;code&gt;select&lt;/code&gt; syscall, we can now build our "async" event loop. The basic flow is this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Gather all pending events from previous continuations&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;select&lt;/code&gt; on the list of associated &lt;code&gt;file_descriptor&lt;/code&gt;s&lt;/li&gt;
&lt;li&gt;Execute the list of events associated with the list of &lt;code&gt;file_descriptor&lt;/code&gt;s returned by &lt;code&gt;select&lt;/code&gt; (read/write)&lt;/li&gt;
&lt;li&gt;Execute any &lt;code&gt;continuation&lt;/code&gt;s from the events in the previous step&lt;/li&gt;
&lt;li&gt;Save any new events &lt;/li&gt;
&lt;li&gt;Loop&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that flow in python would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;READ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# execute read...
&lt;/span&gt;    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WRITE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# execute write...
&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;event_loop&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;event_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pending_event_fds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_descriptor&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;event_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
        &lt;span class="n"&gt;select_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pending_event_fds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;select_result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event_store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;event_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="nf"&gt;execute_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;new_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;continuation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;event_store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;new_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_descriptor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_event&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In an actual implementation we would need to add some initial events to get the loop started, for example by adding an event that periodically calls &lt;code&gt;accept&lt;/code&gt; to accept new connections and start processing requests. After that we would just loop and execute the current events that we have.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick performance comparison
&lt;/h2&gt;

&lt;p&gt;At this point we have two small implementations, one of multi-threaded event processing and another for event loop based processing. Let's put them to the test with a quick profile.&lt;/p&gt;

&lt;p&gt;We will test both implementations with three separate workloads:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;high concurrency IO requests&lt;/li&gt;
&lt;li&gt;high concurrency high CPU requests&lt;/li&gt;
&lt;li&gt;high concurrency contentious requests (set a key in a collection)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In these workloads we expect the event loop to generally perform better in IO / contention contexts, where a thread pool will usually do better when there is majority CPU work as it can utilize multiple CPU cores. &lt;/p&gt;

&lt;h3&gt;
  
  
  IO Experiment
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;throughput&lt;/th&gt;
&lt;th&gt;latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg5bhpgcjj6d8xs5hurj9.png" alt="throughput_io" width="800" height="503"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmuphn5aq6cjjr0p1eeb6.png" alt="latency_io" width="800" height="1123"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the IO experiment we can see that the event loop matches the maximum request rate reached by the 256 thread thread pool. That is pretty amazing results for only using a single thread! In the chart on the right we can also see the P50, P95, and P99 latencies compared with each thread pool configuration. The latency improves for the thread pool as more threads are added, but generally it always underperforms the event loop (especially when tasks are longer).&lt;/p&gt;

&lt;h3&gt;
  
  
  CPU Experiment
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;throughput&lt;/th&gt;
&lt;th&gt;latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2sdgx56wry4u5lnm6zec.png" alt="throughput_cpu" width="800" height="508"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffj9sk3hsa16932wz5pr0.png" alt="latency_cpu" width="800" height="1123"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the CPU experiment the event loop struggle a lot. In this case I can't event plot all of the throughput values for the different work lengths because the latency grows insanely fast. Thread pool definitely wins here by being able to chop through that work efficiently, and generally more threads is better up to ~256. It is probably better to stick to a lower number of threads when you start getting diminishing returns, as threads add memory overhead and context switch costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contention Experiment (setting a key in a shared dictionary)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;throughput&lt;/th&gt;
&lt;th&gt;latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxvelkdrk21m3v4tvfg1y.png" alt="image" width="799" height="497"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa97nrduo7y9bk3gvu5u9.png" alt="latency_image" width="800" height="513"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The contention workload is another place where the event loop shines. This time around I just kept the # of threads static at 256, as modifying a single collection is a simple piece of work. We can see that as the request rate grows, lock contention becomes the dominating factor and the throughput of the thread pool stabilizes at ~25k requests/second. The event loop reaches 140k requests/second before leveling off. Naturally we see that the latency is much better with the event loop as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of event loops
&lt;/h2&gt;

&lt;p&gt;Event loops have some unique performance characteristics that give them a unique advantage over the traditional thread pool for certain workflows. The primary advantages are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Great performance on IO workloads&lt;/li&gt;
&lt;li&gt;Single-threaded code means no need for synchronization and locking&lt;/li&gt;
&lt;li&gt;Simpler programming model due to no synchronization&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Drawbacks of event loops
&lt;/h2&gt;

&lt;p&gt;The achilles heel of the event loop is obviously the ability to block the loop with heavy CPU work. There are also some other disadvantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Less clear programming model. Now we have to code in &lt;code&gt;Event&lt;/code&gt;s, which is less ergonomic than normal declarative programming. &lt;/li&gt;
&lt;li&gt;Not all types of code can be converted to an &lt;code&gt;Event&lt;/code&gt;, meaning it does not work well for all workloads (e.g. GPU interaction)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It is worth noting though that &lt;code&gt;Event&lt;/code&gt; style programming can be made better. The commonly supported &lt;code&gt;async&lt;/code&gt; model is another representation of &lt;code&gt;Event&lt;/code&gt; style programming, where we use the preprocessor / compiler to generate the indirect code for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We explored how to create a simple event loop and did some profiling. Event loops are a great illustration of the tradeoffs that can be taken when it comes to different workloads, so keep it in mind for your next project!&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>performance</category>
      <category>python</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
