<?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: Prachi Jha</title>
    <description>The latest articles on DEV Community by Prachi Jha (@prachi_awesome_jha).</description>
    <link>https://dev.to/prachi_awesome_jha</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%2F2734390%2F910b1e10-3045-47eb-9a96-5ef021fd1811.jpeg</url>
      <title>DEV Community: Prachi Jha</title>
      <link>https://dev.to/prachi_awesome_jha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/prachi_awesome_jha"/>
    <language>en</language>
    <item>
      <title>My RAM is Basically... Sand?</title>
      <dc:creator>Prachi Jha</dc:creator>
      <pubDate>Mon, 15 Jun 2026 20:15:49 +0000</pubDate>
      <link>https://dev.to/prachi_awesome_jha/my-ram-is-basically-sand-el3</link>
      <guid>https://dev.to/prachi_awesome_jha/my-ram-is-basically-sand-el3</guid>
      <description>&lt;p&gt;I'm a CSE student who learned what SRAM is today. While trying to figure that out, I accidentally traced a path from U-Boot all the way down to sand. This is that path.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What is an SRAM?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;SRAM (Static RAM) is a fast form of memory commonly found inside CPU caches and embedded systems. During boot, a Boot ROM may load a secondary bootloader such as SPL into SRAM before larger DRAM has been initialized.&lt;/p&gt;

&lt;p&gt;Okay, so how is it different from Dynamic RAM?&lt;/p&gt;

&lt;p&gt;Both are volatile memories. However, other than the size and location, SRAM is way faster and more expensive than DRAM. Why? Because SRAM is made of 6 transistors while a DRAM is made of one transistor and one capacitor, materials that are way cheaper. &lt;/p&gt;

&lt;p&gt;Now being a CSE student, transistors were one of those vague words that I keep hearing engineers toss around but had never really looked into what they are or how they actually function. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What is a transistor?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Basically, an electrical switch. Which means based on the electric power that we provide it, we can turn the switch 'on' or 'off'. Well, while that is great, why does one SRAM require &lt;em&gt;six&lt;/em&gt; of these electrical switches instead of like... one? They could just turn it on and off going from there. &lt;/p&gt;

&lt;p&gt;Turns out, one bit in SRAM is represented by something called a &lt;em&gt;latch&lt;/em&gt;. The latch is how the SRAM &lt;em&gt;remembers&lt;/em&gt; values like 0 or 1. &lt;/p&gt;

&lt;p&gt;The idea is that memory needs to hold a value, not just pass one through. A latch does this by having two inverters argue with each other until one wins.&lt;/p&gt;

&lt;p&gt;A latch comprises of two 'NOT' gates, connecting two electronic nodes, A and B. Each NOT gate is connected so that its output feeds the input of the other, creating a feedback loop.&lt;/p&gt;

&lt;p&gt;An electronic node is part of an electrical component which has the same voltage. So in the practical sense, A and B are wires. &lt;/p&gt;

&lt;p&gt;Since both are connected via NOT gates,there are only two valid states: A=0, B=1 and A=1, B=0.&lt;/p&gt;

&lt;p&gt;That brings us to the question: what exactly does A=0 or B=1 mean if they are literally wires?&lt;/p&gt;

&lt;p&gt;As it turns out, A=0 or B=1 does not literally mean that the voltage is exactly 0 or 1 volts. Instead, digital circuits define ranges of voltages that are interpreted as logical 0 and logical 1. Whether a node is considered high or low depends on threshold voltages built into the circuit. &lt;/p&gt;

&lt;p&gt;But then, wouldn't the two NOT gates need some comparison mechanism to be able to determine if they are at a voltage higher than the threshold? &lt;/p&gt;

&lt;p&gt;For that we need to understand how these NOT gates are made.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What makes up a NOT gate?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Transistors.&lt;/p&gt;

&lt;p&gt;There are two types of transistors that are used for one NOT gate. &lt;/p&gt;

&lt;p&gt;PMOS and NMOS. Both of these are types of MOSFETs. &lt;/p&gt;

&lt;p&gt;PMOS is a p-channel MOSFET, while NMOS is a n-channel MOSFET. What they mean will make sense soon enough. Now, take a look at the diagram below. That is how these two transistors comprise of one NOT gate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ujncmcxxhlfeezupxny.png" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2F8ujncmcxxhlfeezupxny.png" alt="CMOS inverter diagram" width="408" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the voltage is &lt;em&gt;low&lt;/em&gt;, the PMOS 'switches on' while NMOS remains 'off'. Which means current flows from the voltage source to the output. Thus, B is &lt;em&gt;1&lt;/em&gt;.&lt;br&gt;
When the voltage is &lt;em&gt;high&lt;/em&gt;, i.e. A=1, NMOS 'switches on' while PMOS remains off. The voltage from the source, which here is GND, flows to the output. Thus B becomes 0. &lt;/p&gt;

&lt;p&gt;This architecture for creating a NOT gate is called a CMOS inverter, where CMOS stands for &lt;em&gt;Complementary Metal Oxide Semiconductor&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That makes 4 transistors, 2 for each NOT gate. The other two are &lt;em&gt;access transistors&lt;/em&gt; : they act like controlled doorways for the latch.&lt;/p&gt;

&lt;p&gt;I keep saying when voltage is high, or low, but how does any transistor, whether it be PMOS or NMOS, sense it?&lt;/p&gt;

&lt;p&gt;To understand this, we need to go a layer deeper and look into the terminals that make up these transistors.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The three terminals of a MOSFET&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A terminal is an end point through which an electrical component connects to the rest of the circuit. A MOSFET has three of those. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Source : terminal from which charge carriers enter the channel.&lt;/li&gt;
&lt;li&gt;Drain : terminal through which charge carriers leave the channel.&lt;/li&gt;
&lt;li&gt;Gate : what generates the electric field&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, here's the interesting part. The gate is never in contact with the circuit. However, the input, A, is connected to both the gates of the NMOS as well as the PMOS for a particular CMOS inverter. &lt;/p&gt;

&lt;p&gt;This gate uses the voltage to generate an electronic field that allows the movement of electrons or holes from one side to the other.&lt;/p&gt;

&lt;p&gt;Once this starts happening, the space that was previously occupied by the transistor becomes a conductive region. This region is now called a &lt;em&gt;channel&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Now, why does the gate work the way it does? Why does a higher voltage at the gate cause NMOS to open up, while a lower voltage causes PMOS to open up?&lt;/p&gt;

&lt;p&gt;For that we need to understand how NMOS and PMOS transistors are made.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Doped Silicon&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;That means we take a semiconductor called Silicon and intentionally introduce impurities in it. Depending on the type of impurity introduced, it becomes either n-type or p-type. &lt;/p&gt;

&lt;p&gt;N-type is when we add Phosphorus, which has 5 valence electrons, to Silicon which has only 4. So there is one extra electron, that is only weakly bound and can move through the material, increasing conductivity.&lt;/p&gt;

&lt;p&gt;P-type is when we add Boron, which has only 3 valence electrons. So only three of four bonds of Silicon are formed, which leaves an absence of a electron, that we call a &lt;em&gt;hole&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A silicon is doped at the correct places with the correct impurities to create an NMOS or PMOS transistor.&lt;/p&gt;

&lt;p&gt;An NMOS consists of:&lt;br&gt;
N+ Source&lt;br&gt;
P-type Body&lt;br&gt;
N+ Drain&lt;/p&gt;

&lt;p&gt;Which looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fen9bzu9cpzii7k48joit.png" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Fen9bzu9cpzii7k48joit.png" alt="NMOS" width="648" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, imagine putting a positively charged plate above this silicon.&lt;br&gt;
The gate doesn't touch the silicon, it sits above an insulating oxide layer.&lt;/p&gt;

&lt;p&gt;The electrons currently can't flow because there's a p-type body between two N+ source and drain. However, when the gate exerts positive electric field, the electrons get attracted and start gathering at the surface, i.e. above the p-type body.&lt;/p&gt;

&lt;p&gt;We started with N+ Source --- P Body --- N+ Drain&lt;br&gt;
Now we have created N+ Source --- N-type channel --- N+ Drain.&lt;br&gt;
That is N-N-N all the way across. Now the electrons have a path.&lt;/p&gt;

&lt;p&gt;This layer is called the &lt;em&gt;inversion layer&lt;/em&gt; since we successfully turned a p-type body into an n-type channel.&lt;/p&gt;

&lt;p&gt;This is also why MOSFETs are Metal Oxide Semiconductor &lt;em&gt;Field Effect Transistors&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Similarly, for a PMOS, this is how the Silicon is doped.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8lzddiva07gyqendhitl.png" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2F8lzddiva07gyqendhitl.png" alt="PMOS" width="487" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, coming back to our original question.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How does a NOT gate compare voltages between two wires?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It doesn't.&lt;/p&gt;

&lt;p&gt;Each CMOS inverter only observes the voltage at its own input node. If the input is high, the NMOS transistor turns on while the PMOS turns off, pulling the output toward ground. If the input is low, the PMOS turns on while the NMOS turns off, pulling the output toward the supply voltage.&lt;/p&gt;

&lt;p&gt;How does it determine this 'high' or 'low' though?&lt;/p&gt;

&lt;p&gt;For an NMOS transistor, a conducting channel forms only when the voltage applied to the gate exceeds a certain value known as the threshold voltage (Vth). Below this threshold, the electric field is too weak to attract enough electrons to create a channel, and the transistor remains effectively off. Above it, a conductive path forms between the source and drain, allowing current to flow. &lt;/p&gt;

&lt;p&gt;Digital logic is built on top of this analog behavior. Engineers define ranges of voltages that are guaranteed to be interpreted as logical 0 and logical 1. For example, in a system with a 1 V supply, voltages near 0 V may be treated as a logical 0, while voltages near 1 V are treated as a logical 1. &lt;/p&gt;

&lt;p&gt;Values in between form a transition region where the circuit's behavior is less certain. The role of a CMOS inverter is to push voltages away from this ambiguous region and toward one of the stable extremes. In this sense, digital computation emerges from analog physics: the silicon only ever sees electric fields and voltages, while the familiar 0s and 1s arise from carefully chosen thresholds and voltage ranges.&lt;/p&gt;

&lt;p&gt;Before we move on, there's one more thing worth sitting with: what happens when the latch can't decide?&lt;/p&gt;

&lt;p&gt;For example, instead of one node being near 1 V and the other near 0 V, what if both A and B were sitting at roughly 0.5 V? &lt;/p&gt;

&lt;p&gt;This turns out to be a real phenomenon called &lt;strong&gt;metastability&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;In this situation, neither inverter sees a clearly high or clearly low input. Both are hovering around the threshold region where the transistors are only partially on. As a result, the feedback loop has not yet "made up its mind" about which stable state it should settle into. &lt;/p&gt;

&lt;p&gt;However, this balanced state is extremely fragile. Even the tiniest disturbance, like a bit of electrical noise, a temperature fluctuation, or a few stray electrons moving around, will make one node slightly higher than the other. Once that happens, the cross-coupled inverters amplify the difference. &lt;/p&gt;

&lt;p&gt;If A becomes even a little higher than B, one inverter starts pushing B lower, which causes the other inverter to push A higher, and the gap rapidly grows until the cell settles into either (A = 1, B = 0) or (A = 0, B = 1). &lt;/p&gt;

&lt;p&gt;Metastability is therefore best thought of as a temporary state where the circuit has not yet decided which of its two stable states to occupy.&lt;/p&gt;

&lt;p&gt;This is the knife-edge that a memory cell balances on. And it's made of Silicon.&lt;/p&gt;

&lt;p&gt;It makes you wonder how disastrous it would be if we were to ever run out of Silicon.&lt;/p&gt;

&lt;p&gt;Fortunately for us, Silicon is the second most abundant element on Earth, since it is literally found in &lt;em&gt;sand&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This sand is doped, using which we get transistors, which we use to create NOT gates, which is used to create latches, and six transistors and two wires represent one &lt;em&gt;bit&lt;/em&gt; of SRAM.&lt;/p&gt;

&lt;p&gt;A bunch of these bits together represent our executable machine code.&lt;br&gt;
Before the linker ran, it was relocatable machine code.&lt;br&gt;
Before the assembler ran, it was assembly.&lt;br&gt;
Before the compiler ran, it was the code you actually typed.&lt;br&gt;
Sand, all the way up.&lt;/p&gt;

&lt;p&gt;It is difficult not to be impressed by the fact that modern computing ultimately depends on engineers taking purified sand, introducing carefully chosen impurities into regions measured in nanometers, and somehow arranging the result into a machine capable of running an operating system.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>ram</category>
      <category>silicon</category>
      <category>transistors</category>
    </item>
    <item>
      <title>Why Copy Something That's About to Die?</title>
      <dc:creator>Prachi Jha</dc:creator>
      <pubDate>Wed, 10 Jun 2026 19:44:11 +0000</pubDate>
      <link>https://dev.to/prachi_awesome_jha/why-copy-something-thats-about-to-die-n7c</link>
      <guid>https://dev.to/prachi_awesome_jha/why-copy-something-thats-about-to-die-n7c</guid>
      <description>&lt;p&gt;I know the title could be a metaphor for a &lt;em&gt;billion&lt;/em&gt; things in programming, so bear with me for a minute as I build this up to &lt;em&gt;rvalue references&lt;/em&gt; in cpp.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it started
&lt;/h2&gt;

&lt;p&gt;I was writing a recursive palindrome checker and hit an Out Of Memory error while submitting my solution. &lt;/p&gt;

&lt;p&gt;Someone (GPT) suggested changing&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bool check(string s, int left, int right)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bool check(const string&amp;amp; s, int left, int right)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;to avoid copying the string on every recursive call. &lt;/p&gt;

&lt;p&gt;Now, while I understood that &lt;code&gt;string&amp;amp; s&lt;/code&gt; created a reference to the variable that holds my string, and &lt;code&gt;const&lt;/code&gt; ensured that we didn't alter the value later, I didn't really understand how that worked as a function argument. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why References?
&lt;/h2&gt;

&lt;p&gt;Typically, defining:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;void foo(string s){...}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and then calling:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;foo(name);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;creates a copy. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;name --&amp;gt; "Prachi"&lt;/em&gt;&lt;br&gt;
&lt;em&gt;s --&amp;gt; "Prachi"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These are two separate strings. However, with:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;void foo(string&amp;amp; s){...}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;calling:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;foo(name);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;creates a reference for &lt;em&gt;name&lt;/em&gt;. Here, s becomes another name for &lt;em&gt;name&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;References don't create a new object at all, and are thus able to avoid copying.&lt;/p&gt;

&lt;p&gt;This however brings us to the question, what would happen if I were to write:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;foo("Prachi");&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here, "Prachi" is not an object. So what does s &lt;em&gt;refer&lt;/em&gt; to now?&lt;/p&gt;

&lt;p&gt;As expected, &lt;/p&gt;

&lt;p&gt;&lt;code&gt;void foo(string&amp;amp; s){...}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;would not work with:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;foo("Prachi");&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;However, quite unexpectedly, if foo is defined as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;void foo(const string&amp;amp; s){...}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;foo("Prachi")&lt;/em&gt; would totally work.&lt;/p&gt;

&lt;p&gt;And the only difference is that of a &lt;code&gt;const&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So bringing us to the obvious question in the next part-&lt;/p&gt;
&lt;h2&gt;
  
  
  How. Doesn't the Temporary Get Destroyed?
&lt;/h2&gt;

&lt;p&gt;Normally, temporary objects don't stick around for very long.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Prachi"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;creates a temporary &lt;code&gt;std::string&lt;/code&gt; object that is destroyed at the end of the statement.&lt;/p&gt;

&lt;p&gt;This means that if C++ simply allowed references to bind to temporaries, we could end up with a reference pointing to an object that no longer exists.&lt;/p&gt;

&lt;p&gt;To avoid that, C++ makes a special exception for &lt;code&gt;const T&amp;amp;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Prachi"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without any special rules, the execution might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create temporary string("Prachi")
↓
Bind s to it
↓
Destroy temporary
↓
Use s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which would leave &lt;code&gt;s&lt;/code&gt; referring to an object that no longer exists.&lt;/p&gt;

&lt;p&gt;Instead, C++ extends the lifetime of the temporary so that it survives for as long as the reference does.&lt;/p&gt;

&lt;p&gt;Conceptually, the compiler behaves more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create temporary string("Prachi")
↓
Bind s to it
↓
Keep temporary alive
↓
Use s safely
↓
Destroy temporary when s goes out of scope
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule is known as temporary lifetime extension.&lt;/p&gt;

&lt;p&gt;Which brings us to another question:&lt;br&gt;
Why not just do the same for every reference instead of just &lt;em&gt;const T&amp;amp;&lt;/em&gt;?&lt;/p&gt;
&lt;h2&gt;
  
  
  Why such bias with non-constants?
&lt;/h2&gt;

&lt;p&gt;Imagine cpp allowed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;scream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;"!!!"&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;and then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;scream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Prachi"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;by extending a temporary's lifetime.&lt;/p&gt;

&lt;p&gt;What would happen?&lt;/p&gt;

&lt;p&gt;A temporary gets created, say temp.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;temp = "Prachi"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It gets bound to s.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;string&amp;amp; s = temp;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then the function runs, edits &lt;em&gt;s&lt;/em&gt; and thus temp, so now temp becomes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;temp = "Prachi!!!"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then the function exits. The temporary is destroyed. &lt;/p&gt;

&lt;p&gt;The weird thing is that the modification was technically valid. the string was successfully modified. But nobody will ever see the result. The object would immediately disappear afterward.&lt;/p&gt;

&lt;p&gt;Instead if we did:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Prachi"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;scream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, &lt;em&gt;name&lt;/em&gt; is a concrete object. And thus the modification would survive.&lt;/p&gt;

&lt;p&gt;C++ could have extended temporary lifetimes for all references. Instead, it chose to reserve non-const references for "real" objects whose state can meaningfully be modified and observed later. A temporary object is, by definition, about to disappear, making modification largely pointless.&lt;/p&gt;

&lt;p&gt;But what if we did want to do something with the temporary?&lt;/p&gt;

&lt;p&gt;Let's consider another example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;makeName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Prachi"&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;s&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;Here, when s is returned, &lt;/p&gt;

&lt;p&gt;&lt;code&gt;string name = makeName();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;all of its characters are copied to a new string. &lt;/p&gt;

&lt;p&gt;Why do that when s is about to get destroyed anyway?&lt;/p&gt;

&lt;h2&gt;
  
  
  Move Instead of Copy
&lt;/h2&gt;

&lt;p&gt;In the above example, when s is returned, we have two options. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1. Copy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When s is copied to the return value, the entire buffer is duplicated.&lt;/p&gt;

&lt;p&gt;For a small string, that's not a big deal. But imagine a vector containing millions of elements. Copying becomes expensive very quickly.&lt;/p&gt;

&lt;p&gt;And here's the thing:&lt;/p&gt;

&lt;p&gt;Immediately after the return statement executes, s is destroyed.&lt;/p&gt;

&lt;p&gt;We spent time and memory duplicating data from an object that was about to disappear anyway.&lt;/p&gt;

&lt;p&gt;Option 2: Move&lt;/p&gt;

&lt;p&gt;Instead of duplicating the contents, we transfer ownership of the buffer.&lt;/p&gt;

&lt;p&gt;No characters are copied.&lt;/p&gt;

&lt;p&gt;No new buffer is allocated.&lt;/p&gt;

&lt;p&gt;The return value simply takes ownership of the memory that s was already using.&lt;/p&gt;

&lt;p&gt;When s is eventually destroyed, it has nothing left to clean up.&lt;/p&gt;

&lt;p&gt;This is the core idea behind move semantics.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;p&gt;How do we copy this object efficiently?&lt;/p&gt;

&lt;p&gt;C++ asks:&lt;/p&gt;

&lt;p&gt;Does this object still need its resources?&lt;/p&gt;

&lt;p&gt;If the answer is no, we can simply transfer ownership instead of copying!&lt;/p&gt;

&lt;p&gt;Thus for the first time, having temporaries becomes an optimization opportunity. But for this, we would have to correctly identify which are the temporaries that are about to get destroyed.&lt;/p&gt;

&lt;p&gt;Basically, how do we know if an object is safe to steal resources from?&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Rvalue References
&lt;/h2&gt;

&lt;p&gt;Before this point, I only knew about one kind of reference:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;string&amp;amp; ref = name;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;which can only bind to a named object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Prachi"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// Valid&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Prachi"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Error&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C++11 introduced a second kind of reference:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;string&amp;amp;&amp;amp; ref = string("Prachi");&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Unlike ordinary references, rvalue references specifically allowed to bind to temporary objects: an object that is about to be destroyed and whose resources can potentially be reused.&lt;/p&gt;

&lt;p&gt;While &lt;strong&gt;Lvalue&lt;/strong&gt; refers to an object with an identity that can be referred to later, &lt;strong&gt;Rvalues&lt;/strong&gt; are temporaries.&lt;/p&gt;

&lt;p&gt;An rvalue reference is simply a reference that is allowed to bind to these temporary objects.&lt;/p&gt;

&lt;p&gt;Once the language syntax made this distinction possible, &lt;em&gt;move semantics&lt;/em&gt; came into picture.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Move Semantics&lt;/strong&gt;&lt;/em&gt; is the idea that rather than creating a brand-new copy of an object's resources, we "move" those resources from one object to another.&lt;/p&gt;

&lt;p&gt;The moved-from object remains valid, but no longer owns the resource it previously held.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Thought Rvalue References Were Redundant
&lt;/h2&gt;

&lt;p&gt;When I first encountered rvalue references, I was thinking exclusively about function arguments.&lt;/p&gt;

&lt;p&gt;From that perspective, they seemed almost pointless.&lt;/p&gt;

&lt;p&gt;After all, I had already learned that:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const string&amp;amp; s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;avoids copies.&lt;/p&gt;

&lt;p&gt;It can bind to temporaries.&lt;/p&gt;

&lt;p&gt;It extends the temporary's lifetime.&lt;/p&gt;

&lt;p&gt;So what exactly was left for:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;string&amp;amp;&amp;amp; s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;to do?&lt;/p&gt;

&lt;p&gt;For nearly an hour, I kept looking at rvalue references through that lens and couldn't understand why the feature existed at all.&lt;/p&gt;

&lt;p&gt;The breakthrough came when I stopped thinking about function arguments and started thinking about ownership.&lt;/p&gt;

&lt;p&gt;The goal was never to create "another kind of reference."&lt;/p&gt;

&lt;p&gt;The goal was to give the language a way to recognize objects that were about to disappear and make use of that fact.&lt;/p&gt;

&lt;p&gt;Instead of treating temporary objects as a nuisance, C++ turns them into an optimization opportunity.&lt;/p&gt;

&lt;p&gt;Once I realized that, the entire design suddenly clicked.&lt;/p&gt;

&lt;p&gt;Rvalue references weren't solving a parameter-passing problem.&lt;/p&gt;

&lt;p&gt;They were solving an ownership problem.&lt;/p&gt;

&lt;p&gt;And move semantics was the elegant consequence of that solution.&lt;/p&gt;

&lt;p&gt;Interestingly, while reading further, I learned that one of the key contributors behind rvalue references and move semantics in C++11 was Howard Hinnant.&lt;/p&gt;

&lt;p&gt;So if by some miracle this article ever reaches him:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What started as an Out Of Memory error in a recursive palindrome checker somehow turned into one of my favorite language-design rabbit holes.&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>beginners</category>
      <category>software</category>
      <category>learning</category>
    </item>
    <item>
      <title>What a Switch Tells the Compiler that If-Else Doesn't</title>
      <dc:creator>Prachi Jha</dc:creator>
      <pubDate>Mon, 08 Jun 2026 06:46:39 +0000</pubDate>
      <link>https://dev.to/prachi_awesome_jha/what-a-switch-tells-the-compiler-that-if-else-doesnt-39hn</link>
      <guid>https://dev.to/prachi_awesome_jha/what-a-switch-tells-the-compiler-that-if-else-doesnt-39hn</guid>
      <description>&lt;p&gt;My first reaction to switch-case in 11th grade was a single word: why.&lt;/p&gt;

&lt;p&gt;The if-else conditional had a perfectly easy, perfectly understandable syntax. And as far as my understanding went, they pretty much did the same thing.&lt;/p&gt;

&lt;p&gt;It was not until I studied compilers and systems during my undergraduate that I realized switch statements are not merely an alternate syntax for if-else chains. They communicate additional information to the compiler, which can enable optimizations that may not be as obvious from an equivalent if-else sequence.&lt;br&gt;
How so? &lt;/p&gt;

&lt;p&gt;For the longest time, I believed that switch-case does exactly what an if-else does. We have a few conditions, we pick one of the cases, execute it, and come back. So it didn't really make sense to me about why we would need a &lt;em&gt;break&lt;/em&gt; after every case. &lt;/p&gt;

&lt;p&gt;Unless you write it for every case until you reach &lt;em&gt;default&lt;/em&gt;, it would execute every code block for every single case written below the case executed too!&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nod&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"Yes"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"No"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;default:&lt;/span&gt;
        &lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"Invalid"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A selection of nod = 1 would print YesNoInvalid instead of just yes. Which is why we use &lt;em&gt;breaks&lt;/em&gt; to exit the conditional block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nod&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"Yes"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"No"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;default:&lt;/span&gt;
        &lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"Invalid"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither is it efficient, nor is it a popular use case. The fallthrough makes more sense once you think of cases as jump destinations, not conditions. My mistake was assuming that switch-case is just if-else with a different syntax, and functionality probably remains the same. &lt;/p&gt;

&lt;p&gt;However, a switch exposes a much lower-level control flow model than if-else.&lt;/p&gt;

&lt;p&gt;If you squint carefully, the structure of a switch looks remarkably similar to low-level control flow. Rather than describing a sequence of conditions, it describes a dispatch from one value to one of several possible destinations. Thinking about a switch this way makes its fallthrough behavior much easier to understand.&lt;/p&gt;

&lt;p&gt;For if-else, you describe the logic. The compiler constructs the control flow. For switch-case, you partially describe the control flow itself. The compiler gets much more structure to work with. &lt;/p&gt;

&lt;p&gt;That said, it carries more information to the compiler, in a way, than the typical if-else. &lt;/p&gt;

&lt;p&gt;In the above example, for if-else, the compiler knows:&lt;br&gt;
Condition 1: nod == 1&lt;br&gt;
Condition 2: nod == 2&lt;br&gt;
Condition 3: otherwise&lt;/p&gt;

&lt;p&gt;To realize this is a dispatch problem, the compiler has to analyze the conditions and notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same variable&lt;/li&gt;
&lt;li&gt;Compared to constants&lt;/li&gt;
&lt;li&gt;Mutually exclusive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now look at the switch example. We are basically telling the compiler that there is &lt;em&gt;one&lt;/em&gt; controlling expression - 'nod'. The compiler can immediately construct:&lt;/p&gt;
&lt;h2&gt;
  
  
  Value    Destination
&lt;/h2&gt;

&lt;p&gt;1        case1&lt;br&gt;
2        case2&lt;br&gt;
3        case3&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;without doing any analysis. The source code is already expressing a dispatch relationship between values and destinations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A switch ensures that there is a single expression compared against constant cases to select one destination. It is essentially carrying metadata about the coder's intent.&lt;/p&gt;

&lt;p&gt;A switch statement describes a dispatch problem, which allows the compiler to implement it using various optimization techniques. &lt;/p&gt;

&lt;p&gt;While experimenting, I repeatedly ran into a different problem: GCC kept deleting my switch statements entirely. In one example, the compiler noticed that every case returned the same value as the case label itself and rewrote the logic into a much simpler form.&lt;/p&gt;

&lt;p&gt;In one experiment, I wrote:&lt;/p&gt;

&lt;p&gt;case 1: return 1;&lt;br&gt;
case 2000: return 2000;&lt;/p&gt;

&lt;p&gt;expecting to study switch lowering. Instead, GCC noticed that the returned value was identical to the matched case value and generated a much simpler implementation. The switch effectively disappeared.&lt;/p&gt;

&lt;p&gt;This was an important lesson: by the time we inspect assembly, we are often looking at the result of many optimization passes rather than a direct translation of the source code.&lt;/p&gt;

&lt;p&gt;In fact, a switch statement does not necessarily survive long enough to be lowered at all. If the compiler can prove something stronger about the program, it may simplify or eliminate the switch entirely.&lt;/p&gt;

&lt;p&gt;However, when a switch does survive to the lowering stage, the compiler still has several possible ways to implement it. This process, known as switch lowering, involves choosing a concrete control-flow strategy for the multiway branch.&lt;/p&gt;

&lt;p&gt;A few common choices used by compilers are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compare Chain
Here
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;3&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;becomes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;&lt;span class="nf"&gt;cmp&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nf"&gt;je&lt;/span&gt; &lt;span class="nv"&gt;case1&lt;/span&gt;

&lt;span class="nf"&gt;cmp&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="nf"&gt;je&lt;/span&gt; &lt;span class="nv"&gt;case2&lt;/span&gt;

&lt;span class="nf"&gt;cmp&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="nf"&gt;je&lt;/span&gt; &lt;span class="nv"&gt;case3&lt;/span&gt;

&lt;span class="nf"&gt;jmp&lt;/span&gt; &lt;span class="nv"&gt;default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good when there are only a few cases, or when the case values are sparse.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Jump Table&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For denser values or larger numbers of cases, the compiler may generate a jump table. It however consumes memory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;index = x - 1

table:
0 -&amp;gt; case1
1 -&amp;gt; case2
2 -&amp;gt; case3
3 -&amp;gt; case4
4 -&amp;gt; case5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Decision Tree
For many sparse values like 100, 200, 500, 800 - the compiler might emit a decision tree equivalent to a binary search tree. It is good for when there are many sparse values constructing a jump table for that would be &lt;em&gt;huge&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A few other optimizations also include bit test, range checks and special transforms. &lt;/p&gt;

&lt;p&gt;Now, how does the compiler know which optimization route to take? Based on heuristics. &lt;/p&gt;

&lt;p&gt;Most compilers look at the number of cases, range and density. &lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
Number of cases = 5&lt;br&gt;
Range = 1..5&lt;br&gt;
Density = 5/5 = 100%&lt;/p&gt;

&lt;p&gt;If the density is high, jump table is probably good.&lt;/p&gt;

&lt;p&gt;Another example:&lt;br&gt;
case 1&lt;br&gt;
case 1000&lt;br&gt;
case 1000000&lt;/p&gt;

&lt;p&gt;Here:&lt;br&gt;
Cases = 3&lt;br&gt;
Range = 1..1000000&lt;br&gt;
Density ≈ 0&lt;/p&gt;

&lt;p&gt;Jump tables would waste memory. Comparisons are probably better.&lt;/p&gt;

&lt;p&gt;When the code compiles, the compiler often doesn't know several details like how often a branch will be executed, what values the user will enter, etc. But it still needs to decide optimizations, and whether to generate a jump table, a decision tree, etc. So it makes an educated guess.&lt;/p&gt;

&lt;p&gt;Each compiler uses their own heuristics. For example, in GCC's source code, depending on the number of cases, their range, density, and even special patterns in the values, GCC may choose a comparison chain, a decision tree, a jump table, or an entirely different transformation. The compiler is not simply translating a switch statement into machine code; it is evaluating multiple possible implementations and selecting the one its heuristics believe will be most efficient.&lt;/p&gt;

&lt;p&gt;Here is an example of different assembly code generated for the same decision problem using switch-case and if-else&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;default:&lt;/span&gt; &lt;span class="k"&gt;return&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The simplified version of the assembly code generated for this is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;&lt;span class="nf"&gt;cmp&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
&lt;span class="nf"&gt;je&lt;/span&gt;  &lt;span class="nv"&gt;case500&lt;/span&gt;

&lt;span class="nf"&gt;jg&lt;/span&gt;  &lt;span class="nv"&gt;right_half&lt;/span&gt;
&lt;span class="nf"&gt;jl&lt;/span&gt;  &lt;span class="nv"&gt;left_half&lt;/span&gt;

&lt;span class="nl"&gt;left_half:&lt;/span&gt;
    &lt;span class="nf"&gt;cmp&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="nf"&gt;je&lt;/span&gt; &lt;span class="nv"&gt;case200&lt;/span&gt;

    &lt;span class="nf"&gt;cmp&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
    &lt;span class="nf"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how the compiler starts by comparing directly to 500 and then jumping in two different directions on the basis of the result. This is a decision tree. &lt;/p&gt;

&lt;p&gt;GCC essentially built&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                500
              /     \
          &amp;lt;500       &amp;gt;500
          /             \
       200             800
      /   \           /   \
    ...   ...      ...   ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let us try the same with if-else using the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&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;x&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="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&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;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&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;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&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;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&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;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&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;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&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;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&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;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&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;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&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;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
                &lt;span class="k"&gt;return&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simplified version of the assembly generated for this is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;&lt;span class="nf"&gt;cmp&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nf"&gt;je&lt;/span&gt; &lt;span class="nv"&gt;case1&lt;/span&gt;

&lt;span class="nf"&gt;cmp&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="nf"&gt;je&lt;/span&gt; &lt;span class="nv"&gt;case100&lt;/span&gt;

&lt;span class="nf"&gt;cmp&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="nf"&gt;je&lt;/span&gt; &lt;span class="nv"&gt;case200&lt;/span&gt;

&lt;span class="nf"&gt;cmp&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
&lt;span class="nf"&gt;je&lt;/span&gt; &lt;span class="nv"&gt;case300&lt;/span&gt;

&lt;span class="nf"&gt;...&lt;/span&gt;

&lt;span class="nf"&gt;cmp&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;
&lt;span class="nf"&gt;je&lt;/span&gt; &lt;span class="nv"&gt;case900&lt;/span&gt;

&lt;span class="nf"&gt;jmp&lt;/span&gt; &lt;span class="nv"&gt;default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this particular experiment, GCC generated a decision tree for the switch version and a linear comparison chain for the if-else version.&lt;/p&gt;

&lt;p&gt;Switch isn't merely alternate syntax for if-else. It communicates additional semantic information to the compiler: that a single value is being dispatched to one of several destinations. That additional structure allows the compiler to apply optimizations such as jump tables, decision trees, bit tests, and other lowering strategies.&lt;/p&gt;

&lt;p&gt;What surprised me most while exploring GCC's implementation was that the question "Is switch faster than if-else?" is actually the wrong question. In many cases, the compiler may generate nearly identical code for both. In other cases, it may completely transform the control flow into a jump table, a decision tree, or something even more specialized. The answer depends on the distribution of case values, the number of branches, target architecture, and compiler heuristics.&lt;/p&gt;

&lt;p&gt;The real advantage of a switch statement is not that it is inherently faster. It is that it expresses intent more clearly. By telling the compiler that we are performing a multiway dispatch on a single value, we give it the information needed to choose the most efficient implementation. As it turns out, a switch statement is not just a language feature - it is a hint to the optimizer.&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>assembly</category>
      <category>compiling</category>
      <category>gcc</category>
    </item>
    <item>
      <title>I Baked a Flutter App Into a Car OS. Here's What Broke and What Didn't.</title>
      <dc:creator>Prachi Jha</dc:creator>
      <pubDate>Sat, 21 Mar 2026 18:20:23 +0000</pubDate>
      <link>https://dev.to/prachi_awesome_jha/i-compiled-a-car-os-from-scratch-the-hard-part-was-one-line-pae</link>
      <guid>https://dev.to/prachi_awesome_jha/i-compiled-a-car-os-from-scratch-the-hard-part-was-one-line-pae</guid>
      <description>&lt;p&gt;&lt;em&gt;This came out of preparing for GSoC 2026 with AGL.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Automotive Grade Linux runs the infotainment systems in production Mazdas and Subarus. It's backed by most major automakers and compiles entirely from source - kernel, C library, every system tool. I spent five days building an AGL image from scratch, wrote a Flutter app, and baked it into the OS. This is what actually happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Scale of the Problem
&lt;/h2&gt;

&lt;p&gt;You can't &lt;code&gt;sudo apt install agl&lt;/code&gt;. AGL is built using Yocto, an industry-standard build system for custom embedded Linux distributions. Yocto doesn't download a pre-built OS. It compiles everything from source: the kernel, the C library, every system tool, the Flutter engine, and the app itself.&lt;/p&gt;

&lt;p&gt;My laptop had neither the compute nor the disk space. I spun up a GCP VM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Machine:&lt;/strong&gt; e2-standard-8 (8 vCPUs, 32 GB RAM)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OS:&lt;/strong&gt; Ubuntu 22.04 LTS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disk:&lt;/strong&gt; 200 GB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First attempt failed overnight at 74%:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WARNING: The free space is running low (0.823GB left)
ERROR: No new tasks can be executed since the disk space monitor action is "STOPTASKS"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yocto needs more than 200 GB. I hit a quota limit trying to expand the disk in Asia, deleted the VM, recreated it in &lt;code&gt;us-central1&lt;/code&gt; with 400 GB, and started over.&lt;/p&gt;

&lt;p&gt;8 hours later, after 12,145 compilation tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tasks Summary: Attempted 12145 tasks of which 0 didn't need to be rerun and all succeeded.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I booted it in QEMU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Automotive Grade Linux 21.90.0 qemux86-64 ttyS0
qemux86-64 login: root
&lt;/span&gt;&lt;span class="gp"&gt;root@qemux86-64:~#&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AGL 21.90.0. Codename: vimba. A virtual car computer, inside a cloud VM, in Iowa.&lt;/p&gt;




&lt;h2&gt;
  
  
  Writing the Flutter App
&lt;/h2&gt;

&lt;p&gt;The app reads &lt;code&gt;/etc/os-release&lt;/code&gt; at runtime to display the AGL version, which means the same binary shows Ubuntu values during local development and AGL values on the actual image - no build flags, no conditionals. The relevant field is &lt;code&gt;PRETTY_NAME&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_loadAglVersion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/etc/os-release'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readAsString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&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="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&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;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'PRETTY_NAME'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_aglVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'='&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&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;break&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsi59wmygr3igxmti13me.png" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Fsi59wmygr3igxmti13me.png" alt="App on local machine" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Flutter app on local machine. For the image: Levi Ackerman from Attack on Titan. The sound button plays an audio clip I will not describe further.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8inxb9t0clm0oq4clvtk.png" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2F8inxb9t0clm0oq4clvtk.png" alt="App on QEMU" width="800" height="1422"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Flutter app on QUEMU&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Baking It In: Yocto Layers and Recipes
&lt;/h2&gt;

&lt;p&gt;Yocto builds from "layers" - folders that each contribute something to the final image. AGL ships with layers for its core system, demo apps, and Flutter engine support. To add my app, I created &lt;code&gt;meta-agl-prachi&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;meta-agl-prachi/
├── conf/
│   └── layer.conf
└── recipes-apps/
    └── agl-quiz-app/
        ├── agl-quiz-app.bb
        └── files/
            └── agl_quiz_app.desktop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.bb&lt;/code&gt; file (a "recipe") tells Yocto: where to fetch the source, how to build it, where to install it. Mine pointed to my GitHub repo and used &lt;code&gt;inherit flutter-app&lt;/code&gt;, a class provided by &lt;code&gt;meta-flutter&lt;/code&gt; that handles all the Flutter-specific build logic.&lt;/p&gt;

&lt;p&gt;Then I added my layer to the build and ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bitbake agl-ivi-demo-flutter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It finished in minutes. Suspiciously fast, only 5 tasks rerun. Yocto had used cached output from the previous build and skipped my layer entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Lockfile Problem
&lt;/h2&gt;

&lt;p&gt;I ran &lt;code&gt;bitbake agl-quiz-app&lt;/code&gt; in isolation to see what was actually failing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: agl-quiz-app-1.0-r0 do_archive_pub_cache:
flutter pub get --enforce-lockfile failed: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error named the failed command. It didn't say where that command came from or how to change it. So I followed the source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find ~/AGL/master/external/meta-flutter &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.bbclass"&lt;/span&gt;
&lt;span class="c"&gt;# → flutter-app.bbclass&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;flutter-app.bbclass
&lt;span class="c"&gt;# → require conf/include/flutter-app.inc&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;flutter-app.inc
&lt;span class="c"&gt;# → require conf/include/common.inc&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;common.inc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three files deep. In &lt;code&gt;common.inc&lt;/code&gt; I found it:&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;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PUBSPEC_IGNORE_LOCKFILE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;pubspec_lock&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="n"&gt;path&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;span class="n"&gt;app_root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pubspec.lock&lt;/span&gt;&lt;span class="sh"&gt;'&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pubspec_lock&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;run_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rm -rf pubspec.lock&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app_root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ...later...
&lt;/span&gt;&lt;span class="nf"&gt;run_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;flutter pub get --enforce-lockfile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app_root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;flutter pub get --enforce-lockfile&lt;/code&gt; requires the lockfile to exactly match resolved dependencies. My lockfile was generated with a slightly different Dart SDK version than the build VM. The fix was a single line in my recipe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;PUBSPEC_IGNORE_LOCKFILE &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line. After two days of debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting the Display Working
&lt;/h2&gt;

&lt;p&gt;QEMU runs headless by default. To see the AGL UI, I exposed its display over VNC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;runqemu qemux86-64 serialstdio slirp &lt;span class="nv"&gt;qemuparams&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-display vnc=:1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I opened port 5901 in GCP's firewall and connected with TigerVNC. First connection: the AGL warning screen, rotated 90 degrees. AGL IVI is designed for portrait car dashboards. One more line in &lt;code&gt;weston.ini&lt;/code&gt; fixed the backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;vnc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmdbngofgbqtyl7y4v0nu.png" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Fmdbngofgbqtyl7y4v0nu.png" alt="AGL warning screen rotated" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Getting my app to render required understanding AGL's Wayland setup. The compositor runs as &lt;code&gt;agl-driver&lt;/code&gt; (uid 1001). Root cannot access &lt;code&gt;agl-driver&lt;/code&gt;'s Wayland socket - not a permissions workaround, just how Wayland works. The socket lives at &lt;code&gt;/run/user/1001/wayland-0&lt;/code&gt; and only the user who started the compositor can connect to it.&lt;/p&gt;

&lt;p&gt;I found the correct environment by reading the existing service file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /usr/lib/systemd/system/flutter-ics-homescreen.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which revealed the exact variables and paths needed. With those:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;su agl-driver &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'
  WAYLAND_DISPLAY=wayland-0
  XDG_RUNTIME_DIR=/run/user/1001/
  LD_PRELOAD=/usr/lib/librive_text.so
  LIBCAMERA_LOG_LEVELS=*:ERROR
  flutter-auto -b /usr/share/flutter/agl_quiz_app/3.38.3/release --xdg-shell-app-id agl_quiz_app'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app launched.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1ug455doxxfw8vqx3yd.png" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Ft1ug455doxxfw8vqx3yd.png" alt="AGL quiz app running in TigerVNC" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AGL 21.90.0 (vimba). The version string - "Automotive Grade Linux 21.90.0 (vimba)", pulled live from &lt;code&gt;/etc/os-release&lt;/code&gt; at runtime. Sound doesn't come through QEMU yet (that requires additional ALSA configuration), but everything else works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;The most useful thing I practiced here wasn't Flutter or Yocto syntax. It was following &lt;code&gt;require&lt;/code&gt; statements until I found the line actually doing the thing. &lt;code&gt;common.inc&lt;/code&gt; wasn't linked from anywhere in the docs. Three files of reading got me there. When something breaks in Yocto, the error names the failed task, and that task is a readable function somewhere in the layer files. Start there and keep reading.&lt;/p&gt;

&lt;p&gt;The structure itself is simpler than it looks: layers are folders, recipes are config files, classes are reusable logic. The surface area is large but not deep.&lt;/p&gt;

&lt;p&gt;The full code and Yocto layer are on GitHub: &lt;a href="https://github.com/PrachiJha-404/agl-flutter-app" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>systems</category>
      <category>opensource</category>
      <category>dart</category>
    </item>
    <item>
      <title>I Made My Code Do Nothing. It Got Slower.</title>
      <dc:creator>Prachi Jha</dc:creator>
      <pubDate>Sat, 07 Feb 2026 12:22:41 +0000</pubDate>
      <link>https://dev.to/prachi_awesome_jha/the-performance-paradox-when-doing-less-work-makes-your-code-slower-398c</link>
      <guid>https://dev.to/prachi_awesome_jha/the-performance-paradox-when-doing-less-work-makes-your-code-slower-398c</guid>
      <description>&lt;p&gt;I stripped my code down to do absolutely nothing. Just count events and move on. It got 8% slower.&lt;/p&gt;

&lt;p&gt;This isn't measurement noise. Over 30 seconds of processing 12+ million events across 5 test runs, the "optimized" version was consistently, measurably slower than the version doing expensive kernel symbol lookups and string formatting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;I built an eBPF tool that monitors TCP packet drops in the Linux kernel. eBPF (Extended Berkeley Packet Filter) lets you hook into kernel functions without modifying kernel source. When a packet is dropped, my eBPF program captures the event (PID, drop reason, kernel function) and sends it to userspace through a ring buffer.&lt;/p&gt;

&lt;p&gt;During stress testing, I flooded my machine with SYN packets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;hping3 &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 80 &lt;span class="nt"&gt;--flood&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The kernel started dropping ~400,000 packets per second. My tool reads each drop event from the ring buffer and processes it.&lt;/p&gt;

&lt;p&gt;I created four benchmark modes to find the bottleneck:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Benchmark&lt;/strong&gt; - Just count events. Zero processing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Busy&lt;/strong&gt; - Do expensive work (symbol lookups, string formatting), then discard the result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File&lt;/strong&gt; - Same expensive work, but write to a file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminal&lt;/strong&gt; - Same expensive work, print to terminal.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each mode ran for 30 seconds. Here's what happened:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Events Read&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12,548,707&lt;/td&gt;
&lt;td&gt;373,828/sec&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Busy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12,432,234&lt;/td&gt;
&lt;td&gt;370,316/sec&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Benchmark&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;11,570,132&lt;/td&gt;
&lt;td&gt;344,616/sec&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Terminal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;653,848&lt;/td&gt;
&lt;td&gt;19,353/sec&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The mode doing &lt;strong&gt;zero work&lt;/strong&gt; came in third.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Paradox
&lt;/h2&gt;

&lt;p&gt;Benchmark Mode should have won. It's literally just incrementing a counter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Benchmark Mode - the "fast" path&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ringbuf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventsRead&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&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;No symbol lookups. No string formatting. No I/O. Just atomic increment and repeat.&lt;/p&gt;

&lt;p&gt;But it lost by 8% to Busy Mode, which does this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Busy Mode&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;EventProcessor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ProcessEventBusy&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;monitorEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventsRead&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Map lookup for drop reason&lt;/span&gt;
    &lt;span class="n"&gt;reasonStr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dropReasons&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;Reason&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;reasonStr&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;reasonStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UNKNOWN(%d)"&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;Reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Binary search through 300k kernel symbols&lt;/span&gt;
    &lt;span class="n"&gt;symbolName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;findNearestSymbol&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;Location&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;symbolName&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;symbolName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0x%x"&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;Location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// String formatting with allocations&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[%s] Drop | PID: %-6d | Reason: %-18s | Function: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"15:04:05"&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;Pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;reasonStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;symbolName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventsPrinted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&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;How does doing more work make code faster?&lt;/p&gt;

&lt;h2&gt;
  
  
  Ruling Out the Obvious
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Memory Allocation?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Benchmark Mode:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total allocated: 303 MB&lt;/li&gt;
&lt;li&gt;GC runs: 14&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Busy Mode:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total allocated: 2,592 MB (8x more)&lt;/li&gt;
&lt;li&gt;GC runs: 75 (5x more)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Busy Mode was doing 5x more garbage collection and still winning. The bottleneck wasn't memory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29qd3xpbpkduovc0vula.png" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2F29qd3xpbpkduovc0vula.png" alt="Benchmark mode output" width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9rvh1ch1vlubh3euni9n.png" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2F9rvh1ch1vlubh3euni9n.png" alt="Busy Mode output" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Smoking Gun: CPU Profiling
&lt;/h2&gt;

&lt;p&gt;I added CPU profiling with &lt;code&gt;pprof&lt;/code&gt; to both modes. The difference was stark.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark Mode (344k events/sec)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpgujkw9f1a7eaxcl4gr1.jpg" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Fpgujkw9f1a7eaxcl4gr1.jpg" alt="pprof benchmark" width="800" height="1130"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unix.EpollWait:     11.12s (76%) ← Blocking on syscalls
ringbuf.Read:       13.61s (93%) ← Total time in read
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;76% of CPU time spent waiting in &lt;code&gt;epoll_wait()&lt;/code&gt;&lt;/strong&gt;, blocked while the kernel writes the next event.&lt;/p&gt;

&lt;h3&gt;
  
  
  Busy Mode (370k events/sec)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zt566yfc2oemtnahjhd.png" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2F3zt566yfc2oemtnahjhd.png" alt="pprof busy mode" width="800" height="597"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ProcessEventBusy:   11.27s (54%) ← Actually doing work
  ├─ fmt.Sprintf:    6.53s (31%)
  ├─ findNearestSymbol: 2.61s (13%)
  └─ mallocgc:       3.19s (15%)

unix.EpollWait:     5.57s (27%) ← Much less waiting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only 27% waiting. The rest was productive work.&lt;/p&gt;

&lt;h3&gt;
  
  
  File Mode (373k events/sec)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecvy9oyj12sq9hk149vy.png" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Fecvy9oyj12sq9hk149vy.png" alt="pprof file mode" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ProcessEvent:         11.66s (56%) ← Work
  ├─ fmt.Fprintf:      4.95s (24%) ← Cheaper than Sprintf!
  ├─ findNearestSymbol: 2.68s (13%)
  └─ mallocgc:         2.64s (13%)
unix.EpollWait:        5.87s (28%) ← Same batching as Busy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;File and Busy have identical syscall overhead (28% waiting). File edges ahead because &lt;code&gt;fmt.Fprintf()&lt;/code&gt; to a buffered file is more efficient than &lt;code&gt;fmt.Sprintf()&lt;/code&gt; creating throwaway strings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Happens: The Syscall Tax
&lt;/h2&gt;

&lt;p&gt;The kernel generates events at ~2.5µs per event (400k/sec).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benchmark Mode&lt;/strong&gt; processes each event in ~1µs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Event arrives → Process (1µs) → ringbuf.Read()
                                    ↓
                            Ring buffer empty!
                                    ↓
                    epoll_wait() blocks (context switch)
                                    ↓
                    Kernel writes next event (1.5µs)
                                    ↓
                        Wake up userspace
                                    ↓
                                 Repeat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; ~400,000 context switches per second, constant blocking.&lt;/p&gt;

&lt;p&gt;Benchmark Mode was &lt;strong&gt;too fast&lt;/strong&gt;. It kept asking for the next event before the kernel had written it, forcing the process to sleep in &lt;code&gt;epoll_wait()&lt;/code&gt; waiting for data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Busy Mode&lt;/strong&gt; processes each event in ~4µs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Event arrives → Process (4µs) → ringbuf.Read()
       ↑                            ↓
       |                    3 events already waiting!
       |                            ↓
       └──────── Kernel wrote more while we were busy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; ~150,000 context switches per second, natural batching.&lt;/p&gt;

&lt;p&gt;By the time userspace calls &lt;code&gt;Read()&lt;/code&gt;, multiple events are already queued in the ring buffer. No blocking needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accidental Batching
&lt;/h2&gt;

&lt;p&gt;Busy Mode's processing time (~4µs per event) accidentally created &lt;strong&gt;natural batching&lt;/strong&gt;. While userspace was busy formatting strings and looking up symbols, the kernel queued multiple events. Each &lt;code&gt;ringbuf.Read()&lt;/code&gt; call pulled several events without blocking.&lt;/p&gt;

&lt;p&gt;Benchmark Mode outpaced its data source and spent most of its time context-switching between userspace and kernel, waiting for the next event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The paradox:&lt;/strong&gt; Removing work made the code too fast, causing it to waste time waiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost of Performance
&lt;/h2&gt;

&lt;p&gt;Here's what's actually expensive in event-driven programming:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cheap:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory allocation (even 8x more)&lt;/li&gt;
&lt;li&gt;Garbage collection (even 5x more)&lt;/li&gt;
&lt;li&gt;CPU work (symbol lookups, string formatting)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expensive:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Context switches (crossing the kernel/userspace boundary)&lt;/li&gt;
&lt;li&gt;Blocking syscalls (&lt;code&gt;epoll_wait&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;poll&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you optimize CPU work to near-zero, you don't eliminate the cost, you just shift it to I/O overhead. If your processing loop is faster than your data source, you end up paying the syscall tax on every single event.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Should Care?
&lt;/h2&gt;

&lt;p&gt;This pattern appears in any high-frequency event processing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network packet processing&lt;/strong&gt; (eBPF, DPDK, raw sockets)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Financial trading systems&lt;/strong&gt; (market data feeds)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log aggregation&lt;/strong&gt; (reading from message queues)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics collection&lt;/strong&gt; (statsd, Prometheus exporters)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Game engines&lt;/strong&gt; (input event processing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; If your event processing is faster than your event arrival rate, you're probably paying unnecessary syscall overhead. Profile your code. If you see &amp;gt;50% time in &lt;code&gt;epoll_wait&lt;/code&gt;/&lt;code&gt;poll&lt;/code&gt;/&lt;code&gt;select&lt;/code&gt;, you're thrashing on syscalls. Batch your reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Fix
&lt;/h2&gt;

&lt;p&gt;The real lesson here isn't "batch your reads better in userspace." The &lt;code&gt;cilium/ebpf&lt;/code&gt; library's &lt;code&gt;ringbuf.Read()&lt;/code&gt; is already reasonably efficient. You're still bound by the poll/epoll cycle regardless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The actual fix is to stop sending 400,000 events to userspace in the first place.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where eBPF's real power comes in: &lt;strong&gt;move the aggregation logic into the kernel.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kernel: Drop packet → Send event to ring buffer
Userspace: Read event → Process → Count
Result: 400,000 context switches/sec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proper approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kernel: Drop packet → Increment counter in BPF map (no userspace trip!)
Userspace: Read aggregated counts once per second
Result: 1 context switch/sec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using eBPF maps, I can aggregate packet drops directly in kernel memory, count drops by kernel function, by IP address, by drop reason; and pull the summary to userspace periodically instead of streaming 400,000 individual events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected improvement:&lt;/strong&gt; 99.9% reduction in context switches (from 400k/sec to ~1/sec).&lt;/p&gt;

&lt;p&gt;This is future work for me. The interesting benchmark results I found were educational. They taught me about syscall overhead and accidental batching, but they also revealed I was solving the problem in the wrong place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;The fastest code isn't always the code doing the least work, it's the code that minimizes expensive operations.&lt;/p&gt;

&lt;p&gt;In this case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Benchmark Mode:&lt;/strong&gt; Optimized CPU work, paid 76% overhead in syscalls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Busy Mode:&lt;/strong&gt; Did 8x more allocation and 5x more GC, reduced syscall overhead to 27%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File Mode:&lt;/strong&gt; Most efficient I/O primitives, same batching benefits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The real enemy wasn't symbol lookups or string formatting. It was calling the kernel 400,000 times per second instead of 150,000 times.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three lessons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Profile before optimizing&lt;/strong&gt; - My intuition said "remove work." The profiler said "syscalls are the bottleneck."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batching beats speed&lt;/strong&gt; - Reading 10 events with 1 syscall is faster than reading 10 events with 10 syscalls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There's a sweet spot&lt;/strong&gt; - Too-fast processing just means waiting for I/O.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The bottleneck is rarely where you think it is.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Environment:&lt;/strong&gt; Ubuntu 24.04, Linux 6.5, Go 1.21 (variance &amp;lt;2% across 5 runs)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full code:&lt;/strong&gt; &lt;a href="https://github.com/PrachiJha-404/ebpf-tcp-monitor" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Profiling implementation:&lt;/strong&gt; &lt;a href="https://github.com/PrachiJha-404/ebpf-tcp-monitor/tree/benchmark/investigation" rel="noopener noreferrer"&gt;&lt;code&gt;benchmark/investigation&lt;/code&gt; branch&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Previous article:&lt;/strong&gt; &lt;a href="https://dev.to/prachi_awesome_jha/my-logs-lied-how-i-used-ebpf-to-find-the-truth-3k87"&gt;My Logs Lied: How I Used eBPF to Find the Truth&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>c</category>
      <category>kernel</category>
      <category>performance</category>
    </item>
    <item>
      <title>My Logs Lied: How I Used eBPF to Find the Truth</title>
      <dc:creator>Prachi Jha</dc:creator>
      <pubDate>Sun, 01 Feb 2026 17:34:24 +0000</pubDate>
      <link>https://dev.to/prachi_awesome_jha/my-logs-lied-how-i-used-ebpf-to-find-the-truth-3k87</link>
      <guid>https://dev.to/prachi_awesome_jha/my-logs-lied-how-i-used-ebpf-to-find-the-truth-3k87</guid>
      <description>&lt;p&gt;A while back, I wrote about the time I &lt;a href="https://dev.to/prachi_awesome_jha/my-go-server-was-so-fast-it-self-ddosd-my-laptop-48i2"&gt;accidentally DDOSed my own laptop&lt;/a&gt; while load testing a Go auction server. 1000 concurrent clients generated connections so fast that the kernel's TCP listen queue overflowed, but silently, dropping packets before they ever reached my application.&lt;/p&gt;

&lt;p&gt;The bug itself had a simple fix. But the experience left me with a harder problem: I had no way to &lt;em&gt;see&lt;/em&gt; it happening in real time. Application logs showed nothing. &lt;code&gt;netstat -s&lt;/code&gt; gave me a system-wide counter. "11,053 listen queue overflows". But not which process, not when, not why.&lt;/p&gt;

&lt;p&gt;So I built a tool to see inside the kernel. This is how it works, what I learned, and one genuinely weird thing I found while benchmarking it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Debugging Network Issues
&lt;/h2&gt;

&lt;p&gt;When a distributed system behaves badly, a database replica lags, a microservice times out, a message queue backs up - the first instinct is to look at application logs. But a whole category of problems lives &lt;em&gt;below&lt;/em&gt; the application, in the kernel's networking stack, invisible to anything your code can observe directly.&lt;/p&gt;

&lt;p&gt;TCP is designed to be resilient. It retransmits. It backs off. It recovers. But when it can't recover, when the listen queue overflows, when a firewall drops a packet, when a checksum fails, it just... drops the packet. No log entry. No error propagated upward. The application sees a timeout, eventually, but the actual cause happened microseconds earlier, deep in kernel code.&lt;/p&gt;

&lt;p&gt;The tools most developers reach for don't help here.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tcpdump&lt;/code&gt; captures packets on the wire, but it's too heavyweight to run continuously in production. It also shows you what &lt;em&gt;arrived&lt;/em&gt;, not what got &lt;em&gt;dropped&lt;/em&gt;. &lt;code&gt;netstat -s&lt;/code&gt; gives you aggregate counters - "11,053 listen queue overflows" - but nothing about which process, when, or why. You're left guessing.&lt;/p&gt;

&lt;p&gt;What I needed was something that could sit right at the point where the kernel drops a packet and report back: who was affected, why did it happen, and exactly where in kernel code did the decision get made.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enter eBPF
&lt;/h2&gt;

&lt;p&gt;eBPF (extended Berkeley Packet Filter) is a technology that lets you run small, sandboxed programs inside the Linux kernel without modifying kernel source code or loading kernel modules (which is a lot more painful). It's been around for years, used by tools like Cilium and Datadog, but it's surprisingly accessible for individual developers once you understand the basics.&lt;/p&gt;

&lt;p&gt;The key insight is that the Linux kernel has built-in attachment points or hooks called &lt;strong&gt;tracepoints&lt;/strong&gt; - stable, documented hooks left by kernel developers for exactly this kind of observability. For packet drops, the relevant tracepoint is &lt;code&gt;skb/kfree_skb&lt;/code&gt;. Every time the kernel frees a socket buffer (which is what happens when a packet is dropped), this tracepoint fires.&lt;/p&gt;

&lt;p&gt;So the plan was straightforward: hook &lt;code&gt;kfree_skb&lt;/code&gt;, capture the information I needed, and get it out to my Go application in real time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building the Monitor
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Kernel Side
&lt;/h3&gt;

&lt;p&gt;The eBPF program itself is surprisingly small — about 30 lines of C:&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;#include&lt;/span&gt; &lt;span class="cpf"&gt;"vmlinux.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;bpf/bpf_helpers.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="k"&gt;struct&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;u32&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// Process context when drop occurred&lt;/span&gt;
    &lt;span class="n"&gt;u32&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// Why the kernel dropped it&lt;/span&gt;
    &lt;span class="n"&gt;u64&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// Instruction pointer — where in kernel code&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;__uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BPF_MAP_TYPE_RINGBUF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;__uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// 64KB ring buffer&lt;/span&gt;
    &lt;span class="n"&gt;__type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="nf"&gt;SEC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".maps"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;SEC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tracepoint/skb/kfree_skb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;trace_tcp_drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;trace_event_raw_kfree_skb&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ctx&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;ctx&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Not a real drop, bail immediately&lt;/span&gt;

    &lt;span class="k"&gt;struct&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;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bpf_ringbuf_reserve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;e&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Ring buffer full — skip silently&lt;/span&gt;

    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bpf_get_current_pid_tgid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;bpf_ringbuf_submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&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="k"&gt;return&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noting here. The filter on line one (&lt;code&gt;reason &amp;lt;= 1&lt;/code&gt;) is critical - &lt;code&gt;kfree_skb&lt;/code&gt; fires for &lt;em&gt;every&lt;/em&gt; packet that gets freed, including ones that completed successfully. Reason 0 (&lt;code&gt;SKB_DROP_REASON_NOT_SPECIFIED&lt;/code&gt;) and reason 1 (&lt;code&gt;SKB_DROP_REASON_NO_REASON&lt;/code&gt;) are normal lifecycle events. The kernel freed the buffer after it was done with the packet, not because something went wrong. We only care about actual drops, so we bail out immediately for everything else. This keeps the overhead minimal even though we're hooking a very hot kernel path.&lt;/p&gt;

&lt;p&gt;The ring buffer is a 64KB circular queue shared between kernel and userspace. When we call &lt;code&gt;bpf_ringbuf_reserve&lt;/code&gt;, we claim space in it. When we call &lt;code&gt;bpf_ringbuf_submit&lt;/code&gt;, the data becomes visible to userspace. If the buffer is full because userspace isn't reading fast enough, &lt;code&gt;reserve&lt;/code&gt; returns NULL and we silently skip that event — no blocking, no spinning. The eBPF verifier enforces this: our program &lt;em&gt;must&lt;/em&gt; terminate quickly, no exceptions.&lt;/p&gt;

&lt;p&gt;Before any of this runs, the kernel's eBPF verifier statically analyzes the program. It proves there are no infinite loops, no unsafe memory accesses, no calls to unapproved functions. If verification fails, the program doesn't load. This is why eBPF is safe to run in production — you literally cannot get dangerous code past the verifier.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Userspace Side
&lt;/h3&gt;

&lt;p&gt;The Go side reads from the ring buffer and turns raw kernel events into something useful.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ringbuf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Events&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="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c"&gt;// Blocks until an event is available&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;monitorEvent&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;unsafe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawSample&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

    &lt;span class="n"&gt;symbolName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;findNearestSymbol&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;Location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reasonStr&lt;/span&gt;  &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dropReasons&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;Reason&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bufferedWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"[%s] Drop | PID: %-6d | Reason: %-18s | Function: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"15:04:05"&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;Pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reasonStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;symbolName&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;The &lt;code&gt;unsafe.Pointer&lt;/code&gt; cast deserves explanation. The ring buffer gives us raw bytes. We know the layout matches our C struct exactly - same fields, same order, same sizes. Rather than parsing the bytes manually (slow, error-prone), we reinterpret them directly as a Go struct. Zero allocation, zero copying. It's the only &lt;code&gt;unsafe&lt;/code&gt; usage in the codebase, and it's justified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symbol resolution&lt;/strong&gt; is where the interesting work happens. The kernel gave us a raw instruction pointer, something like &lt;code&gt;0xffffffff81a2b574&lt;/code&gt;. Meaningless to a human. To translate it, we load &lt;code&gt;/proc/kallsyms&lt;/code&gt; at startup, around 200,000 kernel symbols, sorted by address. Then for each event, we do a binary search to find the function that contains our address, calculate the offset, and produce output like &lt;code&gt;tcp_v4_syn_recv_sock+0x234&lt;/code&gt;. Now you know exactly which kernel function dropped the packet.&lt;/p&gt;

&lt;p&gt;The output is written through a 256KB &lt;code&gt;bufio.Writer&lt;/code&gt;. This matters more than it might seem, and it connects to something I discovered later.&lt;/p&gt;




&lt;h2&gt;
  
  
  What It Looks Like in Practice
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[15:04:23] Drop | PID: 1234 | Reason: TCP_LISTEN_OVERFLOW | Function: tcp_v4_syn_recv_sock+0x234
[15:04:23] Drop | PID: 1234 | Reason: TCP_LISTEN_OVERFLOW | Function: tcp_v4_syn_recv_sock+0x234
[15:04:23] Drop | PID: 5678 | Reason: NETFILTER_DROP      | Function: nf_hook_slow+0x12a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each line tells you: when it happened, which process was in context, why the kernel dropped it, and exactly where in kernel code the decision was made.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on PID Accuracy
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;bpf_get_current_pid_tgid()&lt;/code&gt; returns the PID of whichever process the kernel is running when the drop occurs. For &lt;code&gt;TCP_LISTEN_OVERFLOW&lt;/code&gt;, this is typically the listening process, the drop happens in its context. But for other drop types, particularly ones that occur during interrupt handling or in kernel threads, the PID might not correspond to the actual owner of the dropped packet.&lt;/p&gt;

&lt;p&gt;This is a fundamental limitation of the approach. The kernel doesn't always know which userspace process "owns" a packet at the point it gets dropped. For debugging specific issues like my listen queue overflow, the PID is accurate and useful. For a general-purpose production monitoring tool, you'd want to validate accuracy per drop type before relying on it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Beyond the Code
&lt;/h2&gt;

&lt;p&gt;This project started as a way to fix a single bug, but it ended up being a masterclass in how much complexity lives just beneath our main() functions.&lt;/p&gt;

&lt;p&gt;While you might reach for a platform like Cilium or Datadog for 24/7 production observability, there is something incredibly powerful about writing 30 lines of C that can peer into the heart of the kernel. It turns the "black box" of networking into a transparent stream of events.&lt;/p&gt;

&lt;p&gt;The source for this project is open on &lt;a href="https://github.com/PrachiJha-404/ebpf-tcp-monitor" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Presented at &lt;a href="https://hasgeek.com/bengalurusystemsmeetup" rel="noopener noreferrer"&gt;Bengaluru Systems Meetup&lt;/a&gt;, January 2026. Thanks to the organizers for the welcoming "just show up and talk about what you built" energy, it made all the difference.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>systems</category>
      <category>kernel</category>
      <category>programming</category>
      <category>go</category>
    </item>
    <item>
      <title>My Go Server was so fast it self-DDoS'd my laptop</title>
      <dc:creator>Prachi Jha</dc:creator>
      <pubDate>Sun, 28 Dec 2025 11:34:10 +0000</pubDate>
      <link>https://dev.to/prachi_awesome_jha/my-go-server-was-so-fast-it-self-ddosd-my-laptop-48i2</link>
      <guid>https://dev.to/prachi_awesome_jha/my-go-server-was-so-fast-it-self-ddosd-my-laptop-48i2</guid>
      <description>&lt;p&gt;In my fourth semester, my teammate and I built an auction server using sockets in Python. It was a final project for our Computer Networks course - functional enough to pass, inefficient enough to haunt me later.&lt;/p&gt;

&lt;p&gt;Fast forward to last month, when polishing my resume, I realized I needed a project that showed I could handle high-concurrency systems. So, I revisited that old auction server and rewrote it in Go, aiming for a 2-3x performance improvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/PrachiJha-404/High-Throughput-Auction-Server.git" rel="noopener noreferrer"&gt;Full source code available here&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead, I ended up with a 80% failure rate at 1,000 concurrent users.&lt;br&gt;
Not because my code was broken. Because it was too fast. My Go implementation was so efficient it overwhelmed the Windows TCP stack and DDoS'd my own laptop.&lt;/p&gt;

&lt;p&gt;This is that story. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Python Baseline
&lt;/h3&gt;

&lt;p&gt;The original Python version used &lt;code&gt;selectors&lt;/code&gt; for I/O multiplexing. The standard stuff - The OS would wake up our event loop when clients sent bids, we'd process them, repeat. The architecture was clean and worked fine for the project demo.&lt;/p&gt;

&lt;p&gt;However, I later realized that it had limitations. Python's Global Interpreter Lock (GIL) meant only one thread could execute bytecode at a time, no matter how many cores were available. My 18-core laptop was essentially operating on one thread.&lt;/p&gt;

&lt;p&gt;Still, for a baseline test with 100 users sending 50 bids each (5,000 total requests), Python performed decently:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frcum5f9lf74x9fs3xq96.jpeg" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Frcum5f9lf74x9fs3xq96.jpeg" alt="Python performance with 100 users" width="569" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are some solid numbers! Time to see how I went about improving this performance using Go.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Go Rewrite
&lt;/h3&gt;

&lt;p&gt;Go's concurrency model is fundamentally different from Python's. Every client connection gets its own goroutine, a lightweight thread that costs about 2KB of memory. When a goroutine blocks waiting for network I/O, Go's scheduler parks it and moves on to other work. There is no spinning, polling or wasted cycles.&lt;/p&gt;

&lt;p&gt;I made three key architectural changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Binary protocol&lt;/strong&gt;&lt;br&gt;
I replaced string parsing with fixed-size headers. The server now knows exactly how many bytes to read for each message, eliminating guesswork and partial frame errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Persistence: Moving from local memory to Redis&lt;/strong&gt;&lt;br&gt;
I switched from using in-memory dictionaries to Redis with Lua scripts for atomic check-then-set operations. Now, every bid survives a server crash.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Buffered channels&lt;/strong&gt;&lt;br&gt;
I created a 5,000-slot channel buffer between the network layer and the bid processor. This decoupled "receiving data" from "processing data," allowing the system to handle traffic spikes without blocking.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I ran the same 100-user test and expected around 300k RPM.&lt;/p&gt;

&lt;p&gt;I got 480,000 RPM. 100% success rate. &lt;em&gt;With&lt;/em&gt; the Redis overhead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszpxd27isze462y82yhz.jpeg" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Fszpxd27isze462y82yhz.jpeg" alt="Go performance with 100 users" width="450" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Python was storing everything in local memory, with no external I/O beyond the client connections. In contrast, Go was making a network round-trip to Redis for each bid and still outperformed Python by 3.4x.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Self-DDoS
&lt;/h3&gt;

&lt;p&gt;I scaled the test to 1,000 users, each sending 50 bids. That's 50,000 total requests.&lt;/p&gt;

&lt;p&gt;Python struggled but stayed functional:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufsg9qpr3ecjpq9e3iut.jpeg" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Fufsg9qpr3ecjpq9e3iut.jpeg" alt="Python performance with 1k users" width="566" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I ran Go with the exact same parameters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwvflsdnl4ubbpom4ksi.jpeg" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Fjwvflsdnl4ubbpom4ksi.jpeg" alt="Go performance with 1k users" width="465" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The throughput was still higher than Python, but 80% of the requests failed. I checked my code looking for race conditions, panics, however I couldn't find anything that was obviously broken.&lt;/p&gt;

&lt;p&gt;Then I ran &lt;code&gt;netstat -s -p tcp&lt;/code&gt; to check the tcp stats, which revealed the problem: 11,053 Failed Connection Attempts and 31,984 Segments Retransmitted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswswxu9gb05ak6engytw.jpeg" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Fswswxu9gb05ak6engytw.jpeg" alt="netstat results" width="493" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My server hadn't crashed. The Windows TCP stack just couldn't keep up.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Root Cause
&lt;/h3&gt;

&lt;p&gt;Here's what happened:&lt;/p&gt;

&lt;p&gt;My Go benchmarker spawned 1,000 goroutines almost simultaneously and each tried to connect to the server immediately. That meant 1,000 SYN packets hit the kernel in a single burst.&lt;/p&gt;

&lt;p&gt;The kernel has a finite "waiting room" for new connections (the listen backlog queue). When that queue overflowed, it started silently dropping SYN packets.&lt;/p&gt;

&lt;p&gt;The clients, receiving no SYN-ACK response, assumed packet loss and retransmitted. This created a feedback loop: more retransmissions led to more congestion, which caused more drops and retransmissions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classic TCP Congestion Collapse&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But then, &lt;strong&gt;why did Python succeed?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Python "succeeded" because its GIL accidentally rate-limited the connections. It was too slow to overwhelm the Operating System.&lt;/p&gt;

&lt;p&gt;Go exposed a bottleneck that Python never even reached.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;The solution wasn't in my application code. It lay in working with the physical limits of the operating system.&lt;/p&gt;

&lt;p&gt;I implemented connection pacing by introducing a small delay between spawning each client goroutine.&lt;/p&gt;

&lt;p&gt;1ms pacing: Success rate jumped from 21% to 82%.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5s6r0dt7c8icur9r4v6j.jpeg" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2F5s6r0dt7c8icur9r4v6j.jpeg" alt="Go performance with 1k users and pacing of 1 ms" width="493" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2ms pacing: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fah4rkt8py78bzw93ggqp.jpeg" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Fah4rkt8py78bzw93ggqp.jpeg" alt="Go performance with 1k users and 2 ms pacing" width="524" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3ms pacing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1078qbt114mdbsoa5ag.jpeg" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Fh1078qbt114mdbsoa5ag.jpeg" alt="Go performance with 1k users and 3 ms pacing" width="522" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result? &lt;strong&gt;100.00%&lt;/strong&gt; Success Rate at &lt;strong&gt;650,209 RPM&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;That was great, but I thought, why stop here?&lt;/p&gt;

&lt;p&gt;I experimented with increasing the delay between bids from the same user (from 10ms to 20ms), hoping it would help give the system more breathing room. However, I saw the success rate drop instead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp6pgbwdnw0dk4md9metu.jpeg" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Fp6pgbwdnw0dk4md9metu.jpeg" alt="Go performance with 1k users, pacing of 1 ms, time between same user sending bids increased to 20 ms" width="496" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem? Holding 1,000 sockets open while they sat idle put massive pressure on the TCP window. Windows eventually timed them out to reclaim resources.&lt;/p&gt;

&lt;p&gt;The sweet spot turned out to be 3ms pacing between connections, 10ms between bids. That's where the OS and Go runtime finally synced up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The contrast is striking: Python stored all bid data in local memory with no external database or network hops beyond the client connection. Go made a Redis round-trip for every single bid.&lt;/p&gt;

&lt;p&gt;Python peaked at ~150k RPM with dropped requests. Go sustained 650k RPM with 100% reliability and full persistence.&lt;/p&gt;

&lt;p&gt;This wasn't about Go being "faster" in some abstract sense. It was about Go's runtime, designed to maximize modern hardware until hitting the next bottleneck, which in this case, was the operating system itself.&lt;/p&gt;

&lt;p&gt;Python managed 100 users, not because it was exceptionally built, but because it was too slow to hit the limits of the OS that Go found at 1,000 users.&lt;/p&gt;

&lt;p&gt;My Go server was finally fast enough to find the one limit I couldn't code my way out of: the physical capacity of the Windows TCP stack.&lt;/p&gt;

&lt;p&gt;Moving from Python to Go was more than just changing syntax.  It shifted how the application approached concurrency and I/O. By utilizing Go’s M:N scheduler and runtime netpoller instead of Python’s GIL-limited model, I was able to push the system to a point where the operating system became the bottleneck. &lt;/p&gt;

&lt;h3&gt;
  
  
  Hardware Details
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;CPU: Intel(R) Core(TM) Ultra 5 125 H (18 cores)&lt;/li&gt;
&lt;li&gt;OS: Windows 11 Version 24H2&lt;/li&gt;
&lt;li&gt;Redis: 7.x running locally on localhost&lt;/li&gt;
&lt;li&gt;Network: All connections over loopback (127.0.0.1)&lt;/li&gt;
&lt;li&gt;Go Version: 1.25.5&lt;/li&gt;
&lt;li&gt;Python Version: 3.12.6&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All tests were conducted on a single machine to eliminate network variability from application-level performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/PrachiJha-404/High-Throughput-Auction-Server" rel="noopener noreferrer"&gt;View full implementation and benchmarks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Raw benchmark data&lt;/strong&gt;: Available in &lt;code&gt;/benchmarks&lt;/code&gt; directory&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>godev</category>
      <category>programming</category>
      <category>database</category>
      <category>systems</category>
    </item>
  </channel>
</rss>
