<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Computer Broscience - Joey Innes</title>
    <link>https://rmrf.uk/cs/</link>
    <description>An HTTP server and a panel-method aerofoil solver written from scratch in Zig, a distributed key-value store and a logging daemon in Go, and the occasional measurement that disproved something I was sure of.</description>
    <generator>Hugo</generator>
    <language>en-GB</language>
    <copyright>&#169; 2026 Joey Innes</copyright>
    <lastBuildDate>Mon, 27 Jul 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://rmrf.uk/cs/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>The question that broke Dave</title>
      <link>https://rmrf.uk/cs/2026-07-27-the-question-that-broke-dave/</link>
      <pubDate>Mon, 27 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://rmrf.uk/cs/2026-07-27-the-question-that-broke-dave/</guid>
      <category>Computer Broscience</category>
      <description>Can you build a network that behaves like shared memory, with no token, no payment and no accounting of who stores what? I tried in Go two years ago. It worked, and then answering one much smaller question destroyed it. What actually failed, checked against the code — and the three answers the project has produced since.</description>
      <content:encoded>&lt;p&gt;I wrote about &lt;a href=&#34;https://rmrf.uk/cs/2024-05-01-dave/&#34;&gt;Dave&lt;/a&gt; here in May 2024 — a peer-to-peer network that disseminates packets of data, without accounts, without a token, and without anyone in charge. That article carries an edit note admitting it went out of date within months. This is what happened in those months, and why I&amp;rsquo;ve just thrown most of it away.&lt;/p&gt;&#xA;&lt;p&gt;Underneath every version of it there&amp;rsquo;s one question, and it&amp;rsquo;s the reason I keep coming back to this instead of using something that exists:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Can you build a network that behaves like a shared pool of memory — anyone writes, anyone reads, the data stays around — with no payment system, no token, and no accounting of who is storing what? Just an incentive structure simple enough that nothing has to be coordinated at all?&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The constraint is what makes it interesting. Filecoin, Arweave and Storj all answer the durability half with money and proofs of storage, and they work. Take money off the table and something else has to make a node want to keep your bytes, or the pool is only ever as good as the goodwill sitting in it.&lt;/p&gt;&#xA;&lt;p&gt;Two years on, Dave has produced three different answers to that question. Two of them are in this post because I built them. The third I only recognised while writing it, and it had been sitting in the code since 2024.&lt;/p&gt;&#xA;&lt;p&gt;The question in this article&amp;rsquo;s title is a much smaller one, and much more boring. It&amp;rsquo;s the one that broke it.&lt;/p&gt;&#xA;&lt;p&gt;One thing to be clear about before any of the criticism, because it changes how you should read it. This was months of full-time work, and the decisions in it were deliberate — argued through, mostly written down, often revised more than once. What follows isn&amp;rsquo;t a list of things I hadn&amp;rsquo;t considered. It&amp;rsquo;s largely the opposite: mechanisms I designed carefully, which fail for reasons that only surface when you push a formula to its limit, or think like an attacker for five minutes, or run the thing at a scale I never reached. That&amp;rsquo;s a more useful kind of mistake to read about than carelessness, and a far more common one.&lt;/p&gt;&#xA;&lt;h2 id=&#34;where-the-code-is-now&#34;&gt;Where the code is now&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve reset &lt;code&gt;godave&lt;/code&gt;&amp;rsquo;s &lt;code&gt;main&lt;/code&gt; branch back to commit &lt;code&gt;4bc87d5&lt;/code&gt;, from 19 November 2024, and tagged it &lt;code&gt;pre-signed-dats&lt;/code&gt;. Everything after it — seventy-nine commits, three more weeks — lives on a branch called &lt;code&gt;signed-dats&lt;/code&gt;. Nothing is lost, but the default branch is now the version I actually want people to read.&lt;/p&gt;&#xA;&lt;p&gt;That version is 1,074 lines. It builds, and its tests pass. The version I abandoned in December is 3,573 lines across nine packages, and I could no longer hold it in my head.&lt;/p&gt;&#xA;&lt;p&gt;The whole divergence is one commit, and you can see it in a single diff of the protobuf spec:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; enum Op {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-    DAT = 2;                    a thing that exists&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    PUT = 2;                    an operation on a named slot&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; message M {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-    bytes v = 3;   // value&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-    bytes t = 4;   // time&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-    bytes s = 5;   // salt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-    bytes w = 6;   // work&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    bytes datKey = 3;           addressable&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    bytes val = 4;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    bytes time = 5;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    bytes salt = 6;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    bytes work = 7;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    bytes pubKey = 8;           ownership&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    bytes sig = 9;              asymmetric crypto&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Addressability, ownership and signatures all arrive together. Before that commit a dat was a value, a time, a salt and a proof of work — content-addressed by its own hash, with no key and no owner. Nobody could update it, because there was no &lt;em&gt;it&lt;/em&gt; to update; there was just the data, and the data was its own name.&lt;/p&gt;&#xA;&lt;p&gt;The rename tells the story on its own. &lt;code&gt;DAT&lt;/code&gt; is a thing that exists. &lt;code&gt;PUT&lt;/code&gt; is an operation on a named slot. I changed one into the other in an afternoon and spent the next three weeks discovering what that cost.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-it-did&#34;&gt;What it did&lt;/h2&gt;&#xA;&lt;p&gt;Each node keeps a table of peers and a table of dats. Every epoch it picks one dat at random and sends it to one peer at random. That&amp;rsquo;s the entire dissemination mechanism, and it&amp;rsquo;s the part I still like.&lt;/p&gt;&#xA;&lt;p&gt;Storage was governed by something I called &lt;em&gt;mass&lt;/em&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Mass&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;work&lt;/span&gt; []&lt;span style=&#34;color:#66d9ef&#34;&gt;byte&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;t&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Time&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;float64&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; float64(&lt;span style=&#34;color:#a6e22e&#34;&gt;nzerobit&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;work&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; float64(&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Since&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;t&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;Milliseconds&lt;/span&gt;()))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Difficulty divided by age. The idea was that a dat with a harder proof of work should persist longer, and that everything should eventually decay unless somebody keeps re-publishing it. Trust worked the same way: when a peer sent you a dat you didn&amp;rsquo;t already have, its trust score went up by the mass of that dat, and trusted peers got picked more often for gossip.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;That is the first answer to the question at the top: measure what a participant contributes, and pay them in the only currencies the network has — a share of its disk, and a share of its bandwidth.&lt;/strong&gt; No token, no ledger, and a pleasing symmetry between the two halves.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-part-that-worked&#34;&gt;The part that worked&lt;/h2&gt;&#xA;&lt;p&gt;Before the things that didn&amp;rsquo;t, the part I&amp;rsquo;d write again unchanged — and which survives into the rewrite more or less intact.&lt;/p&gt;&#xA;&lt;p&gt;Peer discovery and liveness are the same pair of messages. You send &lt;code&gt;GETPEER&lt;/code&gt;; a well-behaved node replies &lt;code&gt;PEER&lt;/code&gt; with a couple of addresses. If a peer hasn&amp;rsquo;t been heard from in &lt;code&gt;DROP&lt;/code&gt;, it&amp;rsquo;s deleted. There&amp;rsquo;s no separate heartbeat, no separate bootstrap protocol, and no membership list — the packet that discovers peers is the packet that proves they&amp;rsquo;re alive.&lt;/p&gt;&#xA;&lt;p&gt;That doubling-up turns out to do a third job as well, and it&amp;rsquo;s the one I&amp;rsquo;m most pleased with.&lt;/p&gt;&#xA;&lt;p&gt;&lt;figure class=&#34;diagram&#34;&gt;&#xA;  &lt;img src=&#34;https://rmrf.uk/img/dave/punch.svg&#34; alt=&#34;Two nodes, A and B, each inside a dashed box labelled home router, with a reachable edge node above them. Faint arrows show each node talking outbound to the edge, and the edge gossiping each to the other. Two bold arrows then cross both router boundaries in opposite directions&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Nothing in the code mentions NAT. The traversal falls out of pinging everything you hear about, on a tick short enough that both sides do it at once.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&lt;/p&gt;&#xA;&lt;p&gt;Most nodes are behind a home router, and two of them can&amp;rsquo;t exchange UDP without help. The usual answer is a rendezvous server that tells both sides to transmit at the same moment, so each one&amp;rsquo;s outbound packet opens a return path through its own router. Dave has no such server and no code that mentions NAT at all. It gets there anyway, out of three unremarkable decisions:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// a packet from a stranger adds the stranger&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;peers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;pkfp&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; !&lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;peers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;pkfp&lt;/span&gt;] = &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;peer&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;pd&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;pkpd&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;fp&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;pkfp&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;added&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Now&lt;/span&gt;(), &lt;span style=&#34;color:#a6e22e&#34;&gt;seen&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Now&lt;/span&gt;()} }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// gossiped addresses are added unconditionally&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dave&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Op_PEER&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;mpd&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pk&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;msg&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Pds&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; !&lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;peers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;mpdfp&lt;/span&gt;] = &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;peer&lt;/span&gt;{&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;} } }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// and the tick pings every peer in the table, verified or not&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pingTick&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;C&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pid&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;peers&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; !&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;edge&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Since&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;seen&lt;/span&gt;) &amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DROP&lt;/span&gt; { delete(&lt;span style=&#34;color:#a6e22e&#34;&gt;peers&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;pid&lt;/span&gt;) }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Since&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;peermsg&lt;/span&gt;) &amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;PING&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;pktout&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pkt&lt;/span&gt;{&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;dave&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;M&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;Op&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;dave&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Op_GETPEER&lt;/span&gt;}, &lt;span style=&#34;color:#a6e22e&#34;&gt;addr&lt;/span&gt;} }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A newly gossiped peer has never sent a &lt;code&gt;PEER&lt;/code&gt; message, so &lt;code&gt;time.Since(p.peermsg)&lt;/code&gt; is enormous and it gets a &lt;code&gt;GETPEER&lt;/code&gt; on the very next tick. Which means: once an edge node has told A about B and B about A, both sides start pinging each other within eight seconds, independently. Each one&amp;rsquo;s outbound &lt;code&gt;GETPEER&lt;/code&gt; opens its own router for the other. Whichever packet arrives once both mappings exist gets through, and the receiver adds the sender on sight.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s a simultaneous open with nobody coordinating it. It works through full-cone and address- or port-restricted routers, which is most domestic kit; symmetric NAT and carrier-grade NAT still defeat it, because there the external port differs per destination and the address the edge saw is no use to anyone else. Good enough that I ran Dave across EC2 instances and Raspberry Pis at home without ever writing traversal code.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s a defence in there too, and it&amp;rsquo;s the detail I&amp;rsquo;d most like to have back if I&amp;rsquo;d lost it. Look at the two constants:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;DROP&lt;/span&gt;        = &lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Second&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// Time until silent peers are dropped from the peer table.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;SHARE_DELAY&lt;/span&gt; = &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Second&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// Time until new peers are shared. Must be greater than DROP.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you reply to a &lt;code&gt;GETPEER&lt;/code&gt;, you only offer peers you&amp;rsquo;ve known for at least &lt;code&gt;SHARE_DELAY&lt;/code&gt;. A peer that never answers is deleted at &lt;code&gt;DROP&lt;/code&gt;. Sixteen is less than twenty, so &lt;strong&gt;an address that doesn&amp;rsquo;t respond is dropped before it ever becomes eligible to be passed on&lt;/strong&gt;. You can inject a bogus address into one node&amp;rsquo;s table — anyone can, it&amp;rsquo;s a UDP packet — but it dies there, and no amount of injecting makes it spread. That&amp;rsquo;s peer-table poisoning closed off by an inequality between two constants rather than by any code that knows what poisoning is.&lt;/p&gt;&#xA;&lt;p&gt;The same section has a rate limit on &lt;code&gt;PEER&lt;/code&gt; messages: one accepted per &lt;code&gt;PING&lt;/code&gt; interval, so a malicious peer can&amp;rsquo;t flood you with addresses to drown out the honest ones. I found that hole while writing the 2024 post and fixed it in the same afternoon, which is the only time writing an article has directly found me a bug.&lt;/p&gt;&#xA;&lt;p&gt;Two honest limits on that, before I sound too pleased with myself. It stops addresses that are dead or invented; it does nothing about a live malicious node, which responds and therefore gets shared like anyone else. And discv5&amp;rsquo;s endpoint proof — the grown-up version of this — has exactly the same limitation, so at least the ceiling is in the right place.&lt;/p&gt;&#xA;&lt;p&gt;I should be calibrated about the rest of it too. Combining discovery with liveness is what Kademlia&amp;rsquo;s &lt;code&gt;FIND_NODE&lt;/code&gt; does. Refusing to gossip reputation is a call several deployed networks got wrong, but it isn&amp;rsquo;t new. Implicit hole punching happens incidentally in most DHTs — what&amp;rsquo;s unusual here is only that it was designed for rather than stumbled into. The delay inequality is the one I&amp;rsquo;d defend as properly neat, and even there the &lt;em&gt;goal&lt;/em&gt; is standard; it&amp;rsquo;s getting it from an ordering between two timeouts rather than a round trip and some cryptography that I haven&amp;rsquo;t seen elsewhere.&lt;/p&gt;&#xA;&lt;p&gt;So: not novel, mostly. I&amp;rsquo;ve stopped thinking that matters much. Landing independently on the same answers as the systems that got it right is the outcome you want from a first attempt at something, and it&amp;rsquo;s a better one than a genuinely new idea that doesn&amp;rsquo;t work.&lt;/p&gt;&#xA;&lt;p&gt;None of this is what went wrong. The dissemination and storage side is where it comes apart, and it does so in three separate ways.&lt;/p&gt;&#xA;&lt;h2 id=&#34;one-packet-is-enough&#34;&gt;One packet is enough&lt;/h2&gt;&#xA;&lt;p&gt;Look at what happens to mass as age approaches zero.&lt;/p&gt;&#xA;&lt;p&gt;&lt;figure class=&#34;diagram&#34;&gt;&#xA;  &lt;img src=&#34;https://rmrf.uk/img/dave/mass.svg&#34; alt=&#34;A curve of mass against age, rising steeply towards infinity as age approaches zero, crossing a dashed line marked MAXTRUST = 25 at an age of 0.32 milliseconds&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;The cheapest legal dat is eight leading zero bits, about 256 hashes to find. At any age below 0.32 ms it is worth more than the entire trust cap.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&lt;/p&gt;&#xA;&lt;p&gt;Trust accumulates like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;novel&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;trust&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;peers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;pkfp&lt;/span&gt;].&lt;span style=&#34;color:#a6e22e&#34;&gt;trust&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;trust&lt;/span&gt; &amp;lt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MAXTRUST&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;trust&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Mass&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;pk&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;msg&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;W&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Btt&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;pk&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;msg&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;MAXTRUST&lt;/code&gt; is 25. The cheapest dat the network accepts has eight leading zero bits, which costs about 256 hashes to find — call it free. Mass for that dat is &lt;code&gt;8 / age_in_ms&lt;/code&gt;, so it exceeds 25 at any age below 0.32 milliseconds.&lt;/p&gt;&#xA;&lt;p&gt;An attacker mints a dat, hands it straight to the socket, and it arrives at a peer sub-millisecond on a LAN or from a colocated machine. It is novel by construction, because they invented it. One packet takes them from nothing to the trust ceiling.&lt;/p&gt;&#xA;&lt;p&gt;And if the timestamp on the dat is the current millisecond, &lt;code&gt;time.Since(t).Milliseconds()&lt;/code&gt; is zero, &lt;code&gt;1 / 0&lt;/code&gt; in float64 is &lt;code&gt;+Inf&lt;/code&gt;, and mass is infinite. The cap saves the arithmetic from doing any damage, but only by discarding whatever the number was supposed to mean.&lt;/p&gt;&#xA;&lt;p&gt;The mistake underneath the arithmetic is the interesting one. I was rewarding peers for supplying &lt;em&gt;novel data with valid proof of work&lt;/em&gt;, and I&amp;rsquo;d convinced myself that was evidence of useful contribution. It isn&amp;rsquo;t. It&amp;rsquo;s evidence of having spent CPU, and the attacker has CPU. A reputation metric only works if the thing it rewards is scarce to an adversary and abundant to an honest node, and &amp;ldquo;bytes nobody has seen before&amp;rdquo; is the opposite: trivially abundant to whoever is willing to make some up.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;MAXTRUST&lt;/code&gt; exists precisely because I&amp;rsquo;d thought about farming — an uncapped score is obviously gameable, and the cap bounds what any peer can accumulate no matter how much they send. What defeats it is the interaction between the cap and the divergence, and that only shows up if you take the formula to its limit and ask what mass is worth at an age of zero. A cap you can reach in one packet isn&amp;rsquo;t a reputation system; it&amp;rsquo;s a single bit, and the bit says &amp;ldquo;has sent me something&amp;rdquo;, which every attacker can also say. The defence was real. It was aimed at the wrong axis.&lt;/p&gt;&#xA;&lt;h2 id=&#34;mass-was-recency-wearing-a-costume&#34;&gt;Mass was recency wearing a costume&lt;/h2&gt;&#xA;&lt;p&gt;Dats are stored in shards, and the shard is chosen by difficulty:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;keys&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;work&lt;/span&gt; []&lt;span style=&#34;color:#66d9ef&#34;&gt;byte&lt;/span&gt;) (&lt;span style=&#34;color:#66d9ef&#34;&gt;uint8&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; min(&lt;span style=&#34;color:#a6e22e&#34;&gt;MAXWORK&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;nzerobit&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;work&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MINWORK&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;xxhash&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sum64&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;work&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Every dat in shard &lt;em&gt;k&lt;/em&gt; has exactly &lt;em&gt;k&lt;/em&gt; + 8 leading zero bits. When the pruner keeps the most massive dats in a shard, the difficulty term in &lt;code&gt;mass&lt;/code&gt; is the same constant for every dat it&amp;rsquo;s comparing — so the comparison collapses to one over age. Within a shard, mass &lt;em&gt;is&lt;/em&gt; recency. The difficulty half of the formula does no work at all.&lt;/p&gt;&#xA;&lt;p&gt;It isn&amp;rsquo;t quite doing nothing globally, and what it &lt;em&gt;is&lt;/em&gt; doing is the best idea in the codebase — which I only worked out properly while writing this.&lt;/p&gt;&#xA;&lt;p&gt;Difficulty picks your shard, and each shard has its own independent capacity. So a harder proof doesn&amp;rsquo;t buy you priority directly; it moves you into a &lt;strong&gt;less contended queue&lt;/strong&gt;. And because every node prunes its own shards independently, the effect compounds across the network into a self-clearing price for permanence. Want a dat to survive? Do more work and land somewhere emptier. As more people do sixteen bits, that tier fills and the price drifts to twenty. Nobody sets the price, nobody verifies anything, no payment changes hands, and there is no ledger, no contract and no challenge-response anywhere in it. It falls out of local pruning decisions alone.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s worth sitting with, given where the project went. Every other way of buying durability in a decentralised store is heavy: Filecoin and Arweave use money and proofs of storage; Freenet uses popularity. The version of Dave I abandoned used &lt;strong&gt;storage challenges&lt;/strong&gt; — the mechanism that dragged in replica tracking and reassignment and eventually ate the whole design. I already had a mechanism that gets a weaker version of the same property for nothing, and I walked past it to build the expensive one.&lt;/p&gt;&#xA;&lt;p&gt;I can&amp;rsquo;t claim it&amp;rsquo;s unprecedented; I know the well-known systems rather than every paper. But I haven&amp;rsquo;t seen proof-of-work difficulty used as a storage tier with per-tier caps anywhere else, and I&amp;rsquo;d have expected to.&lt;/p&gt;&#xA;&lt;p&gt;What defeats it in practice is mundane. Almost nobody does more work than the minimum, because nothing in the interface suggests a reason to, so everything piles into the first shard and fights over the same slots while fifty-five tiers above it sit empty. A market with one participant doesn&amp;rsquo;t clear. Combined with the mass formula collapsing to recency, the effective policy was &amp;ldquo;keep the most recent few thousand dats&amp;rdquo;, which is not what the documentation said. The interaction is subtle enough that I wrote both halves and still didn&amp;rsquo;t see it: partitioning by difficulty and &lt;em&gt;then&lt;/em&gt; ranking by a formula containing difficulty looks like belt and braces, right up until you notice the second term is identical across everything it&amp;rsquo;s being asked to compare.&lt;/p&gt;&#xA;&lt;p&gt;The idea deserves a better implementation than I gave it, and it&amp;rsquo;s separable from all of this — &lt;a href=&#34;#a-loose-end-worth-picking-up&#34;&gt;see below&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-anonymity-claim-was-too-strong&#34;&gt;The anonymity claim was too strong&lt;/h2&gt;&#xA;&lt;p&gt;From the README on that pinned commit:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Propagating dats in this way ensures that an adversary is unable to create a timing attack to discern the source of a dat, even with a broad view of network traffic.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The mechanism is sound and I still like it. Because every node emits one random dat to one random peer every epoch, at a constant rate, there&amp;rsquo;s no correlation between when a node &lt;em&gt;received&lt;/em&gt; something and when it passes it on. A forwarder is genuinely indistinguishable from another forwarder.&lt;/p&gt;&#xA;&lt;p&gt;But that&amp;rsquo;s forwarding. Origination is a different act, and the code does it differently:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dave&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Op_DAT&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;dats&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Dat&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;V&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;S&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;W&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Btt&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;)})&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rndpeers&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;peerList&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;trustSum&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;FANOUT&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;pktout&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pkt&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;addr&lt;/span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When an application writes a dat, it goes out to five peers immediately, and then repeats on a timer for however many rounds the caller asked for. The originator is the first node to transmit it and the most frequent. Random push hides everyone &lt;em&gt;downstream&lt;/em&gt; of the source. It doesn&amp;rsquo;t hide the source, which is precisely what the sentence claims it does.&lt;/p&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t think I was being dishonest when I wrote that. I think I&amp;rsquo;d designed one mechanism carefully, and then let the claim it supported creep one step wider than the evidence.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-one-i-never-solved&#34;&gt;The one I never solved&lt;/h2&gt;&#xA;&lt;p&gt;Those three are things I got wrong. This one is different — it&amp;rsquo;s the problem I worked hardest on, never cracked, and still think is the most interesting thing in the project.&lt;/p&gt;&#xA;&lt;p&gt;Every epoch a node sends one dat to one peer. Which dat?&lt;/p&gt;&#xA;&lt;p&gt;v1&amp;rsquo;s answer is: pick one at random. That works beautifully at the scale I ran it — data moved around exactly as it should across a handful of machines. But the selection probability for any given dat is one in N, so as the store grows a newly-arrived dat waits proportionally longer for its turn. At a thousand dats it&amp;rsquo;s fine. At a million, a new dat is essentially never chosen, and propagation stops working at all.&lt;/p&gt;&#xA;&lt;p&gt;&lt;figure class=&#34;diagram&#34;&gt;&#xA;  &lt;img src=&#34;https://rmrf.uk/img/dave/select.svg&#34; alt=&#34;Two panels. On the left, one large box of many identical dots with a single arrow leaving it, one dot highlighted and lost among the others. On the right, a small box of a few highlighted dots with several fast arrows leaving, above a large box of many faint dots with a single slow arrow in each direction&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;The left panel is what v1 does. The right is what the problem actually wants — two mechanisms, because the two jobs have opposite requirements.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&lt;/p&gt;&#xA;&lt;p&gt;I tried a lot of things. The one still in the pinned code is a &lt;strong&gt;ring buffer&lt;/strong&gt;: every novel dat is written to a thousand-entry ring, and each push epoch sends one dat from the ring as well as one from the store at large. That genuinely fixes the acute half — new data propagates immediately, however big the store gets.&lt;/p&gt;&#xA;&lt;p&gt;What it doesn&amp;rsquo;t fix is the tail. A dat that falls out of the ring is back to one in N, so anything a node missed while offline, or anything that existed before it joined, only ever reaches it by luck. I also tried cuckoo filters: track what each peer has already seen, and don&amp;rsquo;t waste a push sending them something they&amp;rsquo;ve got. That work is on a branch called &lt;code&gt;filter&lt;/code&gt; and it isn&amp;rsquo;t in the pinned version, which tells you how it went.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;None of it worked because I was asking one mechanism to do two jobs with opposite requirements.&lt;/strong&gt; Getting new data out fast wants a small working set, aggressive sending, and a short life. Repairing the long tail wants completeness, an unbounded working set, and can afford to be slow. A single random pick from a single pool is a compromise that is bad at both.&lt;/p&gt;&#xA;&lt;p&gt;The literature has known this since 1987. Demers et al., &lt;em&gt;Epidemic Algorithms for Replicated Database Maintenance&lt;/em&gt;, splits it exactly along that line: &lt;strong&gt;rumour mongering&lt;/strong&gt; for new items — push them hard, and each time you push to someone who already has it, probabilistically stop, so the hot set limits itself and stays small — and &lt;strong&gt;anti-entropy&lt;/strong&gt; in the background, periodically reconciling state with a random peer. You run both. My ring buffer is rumour mongering with its stopping rule missing; I had the instinct without the feedback that makes it terminate.&lt;/p&gt;&#xA;&lt;p&gt;And the cuckoo filter was simply the wrong primitive, which is the part I&amp;rsquo;d most want to tell 2024 me. A membership filter answers &lt;em&gt;&amp;ldquo;do you have this one?&amp;rdquo;&lt;/em&gt;, an item at a time. The question you actually need answered is &lt;em&gt;&amp;ldquo;what are you missing?&amp;rdquo;&lt;/em&gt; — in one round trip, in bytes proportional to the size of the difference rather than the size of the set. That&amp;rsquo;s &lt;strong&gt;set reconciliation&lt;/strong&gt;, and it&amp;rsquo;s a different tool entirely: Merkle trees over sorted ranges, as Cassandra and Dynamo use for repair; IBLTs, as Graphene uses for blocks; or PinSketch, which is what Bitcoin&amp;rsquo;s Erlay uses — having hit precisely this problem with transaction relay and solved it by pairing low-fanout flooding with periodic reconciliation.&lt;/p&gt;&#xA;&lt;p&gt;The rewrite changes the shape of the question rather than answering it head-on. Flooding doesn&amp;rsquo;t select: every cell goes to every neighbour exactly once, deduplicated by the seen-set, so for new data there&amp;rsquo;s no choice to make and the problem doesn&amp;rsquo;t arise. What&amp;rsquo;s left is the tail — a node that was offline, or has just joined — and that is now a separate mechanism with its own name, the time-range sync. It&amp;rsquo;s the same split Demers describes, arrived at from the other direction.&lt;/p&gt;&#xA;&lt;p&gt;Which puts the interesting work in exactly one place. That sync is currently naive and re-sends things you already have. Replacing it with real range-based reconciliation is the piece I most want to build, and it&amp;rsquo;ll be the first time in this project that the hard problem in front of me is one with a well-understood answer.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-question&#34;&gt;The question&lt;/h2&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the one that actually killed it.&lt;/p&gt;&#xA;&lt;p&gt;A dat is addressed by its own work hash. To retrieve one, you send &lt;code&gt;GET&lt;/code&gt; with that hash, and any peer that has it replies. So: how do you get the hash?&lt;/p&gt;&#xA;&lt;p&gt;You don&amp;rsquo;t. Not from the network. There&amp;rsquo;s no index, no enumeration, no namespace, nothing to browse. You can only ask for something you were already told about through some other channel — and if that channel exists, a lot of the reason for the network existing has quietly moved into it.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s worse than that, because &lt;code&gt;GET&lt;/code&gt; doesn&amp;rsquo;t route. A read checks your own store first, and on a hit it returns without sending a single packet:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dave&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Op_GET&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;shardKey&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;datKey&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;keys&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;W&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;dat&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dats&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;shardKey&lt;/span&gt;][&lt;span style=&#34;color:#a6e22e&#34;&gt;datKey&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;found&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;apprecv&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;dat&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; !&lt;span style=&#34;color:#a6e22e&#34;&gt;found&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rndpeers&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;peerList&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;trustSum&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;FANOUT&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;pktout&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pkt&lt;/span&gt;{&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;dave&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;M&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;Op&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;dave&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Op_GET&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;W&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;W&lt;/span&gt;}, &lt;span style=&#34;color:#a6e22e&#34;&gt;addr&lt;/span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;figure class=&#34;diagram&#34;&gt;&#xA;  &lt;img src=&#34;https://rmrf.uk/img/dave/lookup.svg&#34; alt=&#34;Two panels. On the left, a node fires five arrows at randomly chosen peers while the node actually holding the data sits untouched, marked with a cross. On the right, every node in the network is filled in, and the reader reads from itself, marked with a tick&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Left: what godave did on a local miss. Right: what the rewrite does instead. The second panel isn&amp;#39;t a better lookup — it&amp;#39;s the absence of one.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&lt;/p&gt;&#xA;&lt;p&gt;On a miss it goes to five peers picked at random and repeats on a timer. No key space, no distance metric, nothing pointing the query towards whoever holds the data.&lt;/p&gt;&#xA;&lt;p&gt;That local check is worth dwelling on, because it looks like the answer and isn&amp;rsquo;t. It&amp;rsquo;s a cache lookup, and a cache is only as good as the thing it misses to. What sits in a node&amp;rsquo;s store is a random sample of recent traffic — you receive one random dat per peer per epoch, and the pruner discards everything past the shard capacity every minute. The chance that an arbitrary hash happens to be in there is low, and it falls as the network holds more data. The fallback is doing the real work, and the fallback is the broken part.&lt;/p&gt;&#xA;&lt;p&gt;Which is also why I didn&amp;rsquo;t see this for months. On a dozen nodes on my desk, with capacity far exceeding the data, the local hit rate is close to one and the network path almost never fires — and when it does, five out of twelve peers is most of the network. Dave behaved correctly at every scale I ever ran it at. The lookup only fails once the data in the network exceeds what a single node can hold, which is precisely the point at which you would want it to work. I called Dave a distributed hash table in that first article. It was a distributed hash table with no table, and a small enough network to hide it.&lt;/p&gt;&#xA;&lt;p&gt;So I gave dats a key, and an owner, and a signature — the commit at the top of this piece. Now data had a name you could form without being told it, and a namespace per public key you could enumerate. That is a genuine fix for a genuine problem, and I&amp;rsquo;d make the same call again with the information I had.&lt;/p&gt;&#xA;&lt;p&gt;What followed was mechanical. If dats have keys, the sensible place to put a dat is near its key, so I added XOR distance. If placement is deterministic, some node is &lt;em&gt;supposed&lt;/em&gt; to hold each dat, so I added replica tracking. If replicas are tracked, peers leaving means reassigning them, so I added that. If replicas can be reassigned, I need to know whether a peer is actually storing what it claims, so I added storage challenges. Values then outgrew a UDP datagram, so I added a TCP fallback — a second transport, with its own code path for the same operation.&lt;/p&gt;&#xA;&lt;p&gt;Every one of those follows from the one above it. Not one of them was a bad decision on its own terms. Together they deleted the property the whole thing was for: once placement is deterministic, an observer knows exactly which nodes should hold what, and &amp;ldquo;random push for anonymity&amp;rdquo; no longer hides anything at all.&lt;/p&gt;&#xA;&lt;p&gt;By December I was sketching a third system inside the second — direct messaging, layered on the keyed store, with recipient-keyed namespaces. I never committed it. That&amp;rsquo;s where I stopped.&lt;/p&gt;&#xA;&lt;h2 id=&#34;deleting-the-question&#34;&gt;Deleting the question&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;m writing it again, in Zig, and the whole design rests on one rule I wrote down before any code:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;A node never needs to know anything about the network beyond its immediate neighbours.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The point of the rule isn&amp;rsquo;t what it permits. It&amp;rsquo;s what it refuses. &lt;code&gt;GET(key)&lt;/code&gt; means routing a query towards a node you can&amp;rsquo;t see, so there is no lookup — and with no lookup there is no placement, no replica set, no reassignment, and no storage challenge. The entire collapse is rejected by one sentence, and it&amp;rsquo;s rejected &lt;em&gt;automatically&lt;/em&gt;, before I have a chance to talk myself into each step.&lt;/p&gt;&#xA;&lt;p&gt;So how do you read anything, with no lookup?&lt;/p&gt;&#xA;&lt;p&gt;You already have it. Everything floods to everyone, and reading is a query against your own disk. Following someone is a filter over your own store by their key. There&amp;rsquo;s no relay to ask, no query to route, and nothing to place, because nothing was ever anywhere in particular.&lt;/p&gt;&#xA;&lt;p&gt;That is the same first move v1 made, with a completely different guarantee behind it. v1 checked a cache and fell back to a broken query; this checks a replica, and there is no fallback because none is needed. The difference isn&amp;rsquo;t the lookup — both start by looking in the same place. The difference is whether the design is working for or against that lookup succeeding. Random push and mass pruning actively hold a v1 node&amp;rsquo;s store down to a small sample; flooding and a retention window deliberately fill it.&lt;/p&gt;&#xA;&lt;p&gt;That sounds like it can&amp;rsquo;t possibly scale, and it scales further than I expected. At a million users each posting ten times a day, a node carries about 0.6 Mbit/s and 2% of one core. Ingress doesn&amp;rsquo;t bind until several hundred million users on a normal home connection. Storage is the real cost, and it&amp;rsquo;s each node&amp;rsquo;s own choice of how far back to keep — a phone keeps a week, a machine with a spare disk keeps everything and becomes an archive.&lt;/p&gt;&#xA;&lt;p&gt;That last part is deliberately not a protocol feature. There&amp;rsquo;s no mechanism to declare an archive, verify one, reward one or challenge one, and there is never going to be, because that mechanism is exactly what &amp;ldquo;storage challenges&amp;rdquo; were. Data stays available for as long as somebody chooses to keep it, which is how Usenet worked and how Nostr works. Any protocol promising more than that is either lying or has an accounting system that will eventually eat it, and I&amp;rsquo;ve now written the accounting system that ate mine.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;That is the second answer, and it works by refusing the question.&lt;/strong&gt; Nobody is doing anyone a favour, so there is nothing to pay for: you receive everything regardless, and you keep the part you want to read. The simplest possible incentive structure is no incentive structure — just self-interest, and no coordination because none is required.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s an honest answer and I think it&amp;rsquo;s the right one to build on. It also has a hole in it, which took me a while to see. Self-interest keeps the data &lt;em&gt;somebody wants&lt;/em&gt;. It does nothing at all for data that nobody in particular wants yet — the unpopular, the archival, the thing you publish today that matters in five years. Under this scheme that data quietly evaporates, and there is no way to pay to keep it, because paying is the thing we removed.&lt;/p&gt;&#xA;&lt;p&gt;Everything on the wire is one fixed size:&lt;/p&gt;&#xA;&lt;p&gt;&lt;figure class=&#34;diagram&#34;&gt;&#xA;  &lt;img src=&#34;https://rmrf.uk/img/dave/cell.svg&#34; alt=&#34;A byte-layout diagram of a 224-byte cell: a one-byte version field, a one-byte hop counter, a 32-byte ephemeral key, a 16-byte authentication tag, and 174 bytes of ciphertext&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Every packet is exactly this shape, so size and structure leak nothing. A message that doesn&amp;#39;t fit is split across several cells, each sealed independently.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s no recipient field. Every node tries to open every cell against its own key, and the authentication tag says whether it worked — so nobody, including the node holding a cell, can tell who it&amp;rsquo;s for. On my machine that costs 31 microseconds a cell, essentially all of it the X25519 scalar multiplication.&lt;/p&gt;&#xA;&lt;p&gt;The peer layer comes across almost unchanged: discovery and liveness in one message pair, gossip only in replies, sharing gated behind a delay longer than the drop timeout, and reputation kept strictly local and never gossiped. The one correction is that identity becomes the public key rather than the address, because a table keyed by &lt;code&gt;ip:port&lt;/code&gt; treats a node that moves as a stranger — and on IP, moving is the normal case, not the exception.&lt;/p&gt;&#xA;&lt;p&gt;The traversal needs writing down this time, which it never was. Nothing in v1 records that the ping interval is load-bearing for anything but liveness, and the obvious optimisation — back off the tick, skip unverified peers — is correct for liveness and quietly fatal for hole punching. I nearly made exactly that change while reviewing this code, having read the same three lines and concluded the traversal was accidental. It wasn&amp;rsquo;t; it was the reason the discovery design looks the way it does. The mechanism survived, and the note explaining it didn&amp;rsquo;t.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-zig&#34;&gt;Why Zig&lt;/h2&gt;&#xA;&lt;p&gt;Partly because I want to, which is a sufficient reason for a hobby project. But there&amp;rsquo;s a real argument too, and it&amp;rsquo;s about where this code can run.&lt;/p&gt;&#xA;&lt;p&gt;The protocol has no business needing an allocator on the receive path: a cell is a fixed 224 bytes, and opening one is arithmetic on a fixed-size buffer. Zig lets me write that once and compile it for a server or for a microcontroller with no operating system underneath it. I checked rather than assumed — the whole receive path, X25519 and BLAKE3 and ChaCha20-Poly1305 and the framing, cross-compiles to a Cortex-M4 in an eighteen-kilobyte object, with no allocator and no OS.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not building the radio version now; I scoped this rewrite to IP so that it might actually get finished. But it means the door stays open, and it&amp;rsquo;s the one thing this project can do that the mature alternatives can&amp;rsquo;t.&lt;/p&gt;&#xA;&lt;p&gt;Zig 0.16 is also recent enough that most of what&amp;rsquo;s written about the language online no longer compiles. &lt;code&gt;main&lt;/code&gt; takes a &lt;code&gt;std.process.Init&lt;/code&gt;, randomness comes through &lt;code&gt;std.Io&lt;/code&gt;, &lt;code&gt;std.time.Timer&lt;/code&gt; is gone. I&amp;rsquo;ve been finding these by compiling rather than by reading.&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-loose-end-worth-picking-up&#34;&gt;A loose end worth picking up&lt;/h2&gt;&#xA;&lt;p&gt;The third answer is the one I didn&amp;rsquo;t recognise until I sat down to write this, and it had been in the code since 2024. It&amp;rsquo;s also the piece that fills the hole in the second: a way to buy permanence for data nobody else wants, paid for in work rather than money, with nothing keeping accounts.&lt;/p&gt;&#xA;&lt;p&gt;I want to write it down properly, because it doesn&amp;rsquo;t need a peer-to-peer network to be interesting and I&amp;rsquo;d like to come back to it.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The mechanism.&lt;/strong&gt; Every stored item carries a proof of work of difficulty &lt;code&gt;d&lt;/code&gt;, in leading zero bits. The store is partitioned into tiers by &lt;code&gt;d&lt;/code&gt;, and each tier has its own capacity. When a tier is full, it evicts within itself — oldest first is fine. A writer chooses &lt;code&gt;d&lt;/code&gt; before publishing, so a writer is choosing which queue to stand in.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Why it should clear.&lt;/strong&gt; Treat each tier as a queue with capacity &lt;code&gt;C&lt;/code&gt; and arrival rate &lt;code&gt;λ&lt;/code&gt;. In steady state, an item&amp;rsquo;s expected residency is &lt;code&gt;T = C / λ&lt;/code&gt; — Little&amp;rsquo;s law, and for evict-oldest it&amp;rsquo;s exact. So the writer faces a straight trade: pay &lt;code&gt;2^d&lt;/code&gt; hashes, receive &lt;code&gt;C_d / λ_d&lt;/code&gt; seconds of retention.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s a market, and it clears without anyone running it. If a tier gets popular its arrival rate climbs, residency falls, and the marginal writer moves up a bit. Cost doubles per bit while capacity stays put, so demand spreads itself across the tiers geometrically. No payment, no ledger, no proof of storage, no contract — just local eviction, on every node independently, adding up to a price.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;What was missing, and it&amp;rsquo;s the whole failure.&lt;/strong&gt; A market needs four things. A cost function: proof of work, present. Differentiated goods: the tiers, present. Independent clearing per good: per-tier caps, present. And &lt;strong&gt;a price signal&lt;/strong&gt; — which was entirely absent. Nothing in Dave ever told a writer that tier 0 was full and tier 12 was empty. There was no way to observe occupancy, so there was no way to act on it, so every writer rationally paid the minimum. The market had a price and no way to display it, and a market with one participant doesn&amp;rsquo;t clear.&lt;/p&gt;&#xA;&lt;p&gt;The fix is small: have nodes report how full each of their tiers is, and let a writer sample a few and pick a difficulty from how long they want the thing to live. Occupancy is aggregate information about your own disk, so publishing it leaks very little.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;How to test it with no network at all.&lt;/strong&gt; This is why it&amp;rsquo;s worth doing separately. Simulate a population of writers with some distribution of how much they care about persistence, and one node&amp;rsquo;s store with tiers and caps. Feed arrivals in, evict, and measure whether residency time ends up proportional to cost. That&amp;rsquo;s a discrete-event simulation of a caching policy in a couple of hundred lines, and it answers the question completely — no sockets, no peers, no protocol.&lt;/p&gt;&#xA;&lt;p&gt;Four things I&amp;rsquo;d want it to tell me:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;How to divide capacity across tiers.&lt;/strong&gt; v1 gave every tier the same cap, which wastes most of the disk when demand is concentrated at the bottom. Allocating in proportion to observed demand is self-defeating, because the tier you escape to grows to meet you. There&amp;rsquo;s a right answer here and I don&amp;rsquo;t know what it is.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Whether it oscillates.&lt;/strong&gt; Publish occupancy and everyone can see the same empty tier. They all move there, it fills, they all move again. That&amp;rsquo;s textbook herding, and it probably needs damping or hysteresis to settle.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;What lying buys you.&lt;/strong&gt; A node that reports tier 20 as empty makes writers burn work for nothing. Sampling several nodes and taking a median helps, and a writer can check empirically whether the item actually survived, but this needs thinking about.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Whether the absolute numbers work at all.&lt;/strong&gt; Dave&amp;rsquo;s minimum was eight bits — 256 hashes, which is nothing. Twenty bits is about a million, still only seconds of CPU, so the &amp;ldquo;expensive&amp;rdquo; tiers can be squatted cheaply. The scheme only prices anything if the top tiers are genuinely costly, which means calibrating against current hardware rather than against a number I picked in 2024.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;If it survives that, it&amp;rsquo;s a retention policy any store could use, and it never needed to be part of a network in the first place.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-i-dont-know&#34;&gt;What I don&amp;rsquo;t know&lt;/h2&gt;&#xA;&lt;p&gt;Whether the question at the top has a good answer at all. Three attempts so far. Reward measured contribution — which failed, because anything I could measure locally was something an attacker could manufacture. Refuse the premise and rely on self-interest — which works, and leaves anything unpopular to rot. Price permanence in work and let local eviction clear a market nobody runs — which is untested, and is the only one of the three that&amp;rsquo;s an incentive structure in the sense I originally meant. One is built, one is a cautionary tale, and one is a weekend of simulation away from being either interesting or dead.&lt;/p&gt;&#xA;&lt;p&gt;Whether spam is survivable. Flooding means anyone can inject, everyone must process, and now that data is retained, spam persists as well as propagates. My current answer is to ration rather than reward — per-neighbour rate limits, and a single shared budget that &lt;em&gt;all&lt;/em&gt; unknown authors compete for, so ten thousand fake identities get one stranger&amp;rsquo;s worth of throughput between them. That&amp;rsquo;s an argument, not a result. So was the trust metric.&lt;/p&gt;&#xA;&lt;p&gt;Whether anyone will use it. Almost certainly not many, and that&amp;rsquo;s fine. I want it to be good, and used by a handful of people I know, which is a different target from the one I was implicitly aiming at in 2024 and a much more reachable one.&lt;/p&gt;&#xA;&lt;p&gt;What I do know is the thing I&amp;rsquo;d tell 2024 me. The first version had no rule that could reject a feature. Every addition answered &amp;ldquo;is this good?&amp;rdquo;, and each one honestly was. None of them ever had to answer &amp;ldquo;is this &lt;em&gt;ours&lt;/em&gt;?&amp;rdquo; — and by the time that question would have been useful, there was no longer a clear enough idea of what &amp;ldquo;ours&amp;rdquo; meant to answer it with.&lt;/p&gt;&#xA;&lt;p&gt;The code is at &lt;a href=&#34;https://github.com/intob/godave&#34;&gt;github.com/intob/godave&lt;/a&gt;, pinned where it was still one idea.&lt;/p&gt;&#xA;</content:encoded>
    </item>
    <item>
      <title>Writing a panel-method solver in Zig</title>
      <link>https://rmrf.uk/cs/2026-07-26-writing-a-panel-method-solver-in-zig/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://rmrf.uk/cs/2026-07-26-writing-a-panel-method-solver-in-zig/</guid>
      <category>Computer Broscience</category>
      <description>The first in a series about Camber, a simulation backend for aerofoil design. How a panel method actually works, explained with physics rather than mathematics — and why the hardest part turns out to be a single line of code.</description>
      <content:encoded>&lt;p&gt;I&amp;rsquo;ve been flying soft wings for years without really understanding the sections they&amp;rsquo;re built from. Paragliders, speedwings, kites — they all live or die on the shape of a slice through the wing, and I&amp;rsquo;ve mostly taken that shape on trust. So I&amp;rsquo;ve started building something to fix that: a simulation backend called &lt;strong&gt;Camber&lt;/strong&gt;, which takes a wing section, an angle, and an airspeed, and tells you what the air does about it.&lt;/p&gt;&#xA;&lt;p&gt;This is the first of what I expect to be quite a few posts. It covers the part that works: the panel method. My maths is not strong, so I&amp;rsquo;ve had to build the intuition first and let the equations follow, and that&amp;rsquo;s how I&amp;rsquo;m going to explain it. If you can picture air moving, you can follow all of this.&lt;/p&gt;&#xA;&lt;h2 id=&#34;two-stories-about-air&#34;&gt;Two stories about air&lt;/h2&gt;&#xA;&lt;p&gt;Air behaves in two completely different ways depending on how close to the wing you are, and almost every practical aerodynamics tool is built on that split.&lt;/p&gt;&#xA;&lt;p&gt;Nearly everywhere, air is so slippery that friction may as well not exist. It just has to flow &lt;em&gt;around&lt;/em&gt; the shape rather than through it. Solve that, and you get the pressure everywhere on the surface — and pressure is where lift comes from.&lt;/p&gt;&#xA;&lt;p&gt;Then there&amp;rsquo;s a film glued to the surface, about one percent of the chord thick, where friction is the &lt;em&gt;only&lt;/em&gt; thing that matters. That film is where drag comes from, and where stall comes from.&lt;/p&gt;&#xA;&lt;p&gt;This post is entirely about the first story. It&amp;rsquo;s the easier one, it runs in about a millisecond, and it gets you lift and pressure. The second one is much harder, I&amp;rsquo;m still fighting it, and it&amp;rsquo;ll get its own post.&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-wing-is-a-list-of-points&#34;&gt;A wing is a list of points&lt;/h2&gt;&#xA;&lt;p&gt;The first job is turning &amp;ldquo;NACA 4412&amp;rdquo; into something a computer can chew on. You walk all the way around the section and write down coordinates, then join consecutive points with straight lines. Those straight segments are the &lt;strong&gt;panels&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;figure class=&#34;diagram&#34;&gt;&#xA;  &lt;img src=&#34;https://rmrf.uk/img/camber/panels.svg&#34; alt=&#34;An aerofoil drawn as 42 flat panels, with nodes marked at the corners and outward normals drawn along the surface&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Chord is always normalised to 1, so the nose is at x=0 and the tail at x=1, whatever the real wing measures.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&lt;/p&gt;&#xA;&lt;p&gt;Two details in that picture do real work.&lt;/p&gt;&#xA;&lt;p&gt;The points aren&amp;rsquo;t evenly spaced. They&amp;rsquo;re bunched at the nose and the tail using a cosine distribution. Over the middle of a section the flow does almost nothing interesting — the pressure changes gently and slowly. Around the nose it goes berserk: air comes to a dead stop, then accelerates to well above freestream speed within a couple of percent of chord. Even spacing would spend most of your panels describing the boring part and smear the most important feature on the whole surface.&lt;/p&gt;&#xA;&lt;p&gt;And the direction you walk matters. I go clockwise — tail, along the bottom, round the nose, back along the top. Every panel computes its outward normal as &amp;ldquo;the direction I&amp;rsquo;m travelling, rotated ninety degrees&amp;rdquo;. Walk the other way and every normal points &lt;em&gt;into&lt;/em&gt; the wing, and the sign of the lift flips. I have this written in capital letters in my notes.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-trick-throw-the-wing-away&#34;&gt;The trick: throw the wing away&lt;/h2&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s where it gets clever, and where I had to stop and think for a while.&lt;/p&gt;&#xA;&lt;p&gt;You&amp;rsquo;d assume you solve the flow around a solid object. You don&amp;rsquo;t. You delete the object entirely and replace its surface with a &lt;strong&gt;vortex sheet&lt;/strong&gt; — imagine laying a carpet of tiny spinning rollers along where the skin used to be. Each roller drags the air around with it.&lt;/p&gt;&#xA;&lt;p&gt;&lt;figure class=&#34;diagram&#34;&gt;&#xA;  &lt;img src=&#34;https://rmrf.uk/img/camber/vortex-sheet.svg&#34; alt=&#34;An aerofoil outline covered in small rotation markers, with freestream arrows approaching from the left and a red no-entry symbol over an arrow trying to cross the surface&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;The rollers have no physical existence. They&amp;#39;re a device for producing a flow that happens to follow the shape you care about.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&lt;/p&gt;&#xA;&lt;p&gt;Now ask one question: &lt;em&gt;how hard does each roller have to spin so that the wind plus all the rollers together never pushes air through the line where the skin used to be?&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;Answer that, and you&amp;rsquo;ve solved the flow — because a flow that doesn&amp;rsquo;t cross the skin is indistinguishable from a flow around a solid wing.&lt;/p&gt;&#xA;&lt;p&gt;Why spinning rollers, specifically? Because spin is what makes lift. You could lay down a carpet of little sprinklers instead, blowing air outwards, and they&amp;rsquo;d make the shape appear fatter — but they&amp;rsquo;d generate exactly zero lift no matter how you tuned them. Net circulation around the section &lt;em&gt;is&lt;/em&gt; lift. Vorticity is the only ingredient that can produce it.&lt;/p&gt;&#xA;&lt;p&gt;Chop the surface into N panels, let the spin vary linearly along each one, and the unknowns become the values at the N+1 corners. Then write one equation per panel: &lt;em&gt;at this panel&amp;rsquo;s midpoint, the flow perpendicular to the surface is zero.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s N equations and N+1 unknowns. One short.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-bit-where-nature-has-to-choose&#34;&gt;The bit where nature has to choose&lt;/h2&gt;&#xA;&lt;p&gt;That missing equation isn&amp;rsquo;t a bookkeeping mistake. It&amp;rsquo;s the most interesting thing in the whole method.&lt;/p&gt;&#xA;&lt;p&gt;Frictionless flow theory genuinely does not have a unique answer. There&amp;rsquo;s an entire family of valid solutions around any wing section — one for every possible amount of circulation — and every single one satisfies &amp;ldquo;no air through the skin&amp;rdquo; perfectly. In one of them the air whips around the sharp trailing edge from below to above. In another it goes the other way. In exactly one, the flow leaves the trailing edge smoothly, both streams merging and departing together.&lt;/p&gt;&#xA;&lt;p&gt;&lt;figure class=&#34;diagram&#34;&gt;&#xA;  &lt;img src=&#34;https://rmrf.uk/img/camber/kutta.svg&#34; alt=&#34;Two trailing-edge close-ups side by side: on the left, marked with a cross and drawn in red, a streamline whipping around the sharp corner; on the right, marked with a tick and drawn in blue, two streams merging and leaving smoothly&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Both are mathematically valid. Only the right-hand one is ever observed.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&lt;/p&gt;&#xA;&lt;p&gt;Nature picks the smooth one. It picks it &lt;em&gt;because&lt;/em&gt; real air has viscosity, and viscosity flatly refuses to let flow turn a sharp corner at infinite speed. The first attempt to do so sheds a vortex downstream — you can watch this happen in a water tank — and that departing vortex leaves behind precisely the circulation needed to make the trailing edge smooth ever after.&lt;/p&gt;&#xA;&lt;p&gt;So the missing equation is a fact about &lt;em&gt;sticky&lt;/em&gt; air, smuggled into a model that has no friction in it anywhere. In my code it is one line:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Kutta condition: gamma_0 + gamma_N = 0 (closes the (m x m) system).&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a[n &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; m &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a[n &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; m &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; n] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The spin strength just under the tail, plus the spin strength just above it, equals zero. Since the sheet strength turns out to &lt;em&gt;be&lt;/em&gt; the local surface speed, that says the speeds on the two sides of the trailing edge match: no jump, no infinite turn.&lt;/p&gt;&#xA;&lt;p&gt;This is where the lift comes from. Everything else in the file is bookkeeping. When I had a sign error early on and my sections produced negative lift, this was where the bug was.&lt;/p&gt;&#xA;&lt;h2 id=&#34;its-all-one-linear-system&#34;&gt;It&amp;rsquo;s all one linear system&lt;/h2&gt;&#xA;&lt;p&gt;With that last equation in place you have a square system: a matrix, a vector of unknown spin strengths, and a right-hand side.&lt;/p&gt;&#xA;&lt;p&gt;&lt;figure class=&#34;diagram&#34;&gt;&#xA;  &lt;img src=&#34;https://rmrf.uk/img/camber/linear-system.svg&#34; alt=&#34;A schematic of the linear system: a square influence matrix with its final row highlighted as the Kutta condition, multiplied by the unknown vector, equalling the freestream vector&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;N rows of “no air through this panel”, plus one row of physics.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&lt;/p&gt;&#xA;&lt;p&gt;Each entry of the matrix answers a very concrete question: &lt;em&gt;if roller j were spinning at unit strength, how much air would it push through panel i?&lt;/em&gt; That&amp;rsquo;s pure geometry — there&amp;rsquo;s a closed-form expression involving a logarithm and an arctangent, and it&amp;rsquo;s the same on every panel method ever written.&lt;/p&gt;&#xA;&lt;p&gt;The bit worth noticing is what &lt;em&gt;isn&amp;rsquo;t&lt;/em&gt; in the matrix. The angle of attack appears nowhere in it. It only touches the right-hand side. So the expensive half — factorising the matrix, which is the cubic-time part — gets done once per shape, and each new angle is a cheap back-substitution. Running a whole polar sweep across twenty angles costs roughly the same as solving one.&lt;/p&gt;&#xA;&lt;h2 id=&#34;getting-the-answer-back-out&#34;&gt;Getting the answer back out&lt;/h2&gt;&#xA;&lt;p&gt;Once you&amp;rsquo;ve solved for the spin strengths, you might expect to have to sum up every roller&amp;rsquo;s contribution at every point to find the surface speed. You don&amp;rsquo;t, and this is my favourite part.&lt;/p&gt;&#xA;&lt;p&gt;A vortex sheet is &lt;em&gt;defined&lt;/em&gt; as a jump in tangential velocity across it. The solution was constructed so the air inside the imaginary body is perfectly still. So the jump across the sheet is simply the speed outside it. The strength you solved for &lt;strong&gt;is&lt;/strong&gt; the speed of the air sliding over the skin.&lt;/p&gt;&#xA;&lt;p&gt;Then Bernoulli turns speed into pressure — fast air means low pressure — and the whole thing collapses to one line:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; gamma_mid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (gamma[i] &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; gamma[i &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cp_arr[i] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; .{ .x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; geom.xc[i], .cp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; gamma_mid &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; gamma_mid };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Cp&lt;/code&gt; is the pressure coefficient: local pressure minus ambient, divided by the pressure you&amp;rsquo;d get by bringing the freestream to a dead stop. Dividing through like that means it doesn&amp;rsquo;t depend on how fast you&amp;rsquo;re going or how thick the air is — the same curve holds at 10 m/s at sea level and 30 m/s at three thousand metres. It&amp;rsquo;s the universal currency of this field.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s what actually comes out of the binary:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;{&amp;#34;naca&amp;#34;:&amp;#34;4412&amp;#34;,&amp;#34;aoa_deg&amp;#34;:6,&amp;#34;n_points&amp;#34;:200,&amp;#34;re&amp;#34;:1000000}&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  | ./solver/zig-out/bin/camber-solver&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;figure class=&#34;diagram&#34;&gt;&#xA;  &lt;img src=&#34;https://rmrf.uk/img/camber/cp-4412.svg&#34; alt=&#34;Pressure distribution over a NACA 4412 at six degrees, upper and lower surface curves plotted with negative Cp upward, showing the stagnation point, suction peak, and pressure recovery&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Straight out of the solver: 198 panels, Cl = 1.2386. Everyone plots Cp upside down, so that the area between the curves reads as lift.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&lt;/p&gt;&#xA;&lt;p&gt;Three things to read off it. &lt;strong&gt;Cp = +1&lt;/strong&gt;, bottom left, is air brought completely to rest — the stagnation point, and the highest value physically possible. The big negative spike just behind the nose is the &lt;strong&gt;suction peak&lt;/strong&gt;, where air is moving far faster than freestream and pressure has dropped well below ambient; most of the lift lives there. And the long climb from the peak back towards zero at the tail is the &lt;strong&gt;pressure recovery&lt;/strong&gt; — air being asked to move into rising pressure. That climb is the whole ballgame for the second story, because it&amp;rsquo;s what eventually pushes the boundary layer into separating.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-drag-that-isnt-drag&#34;&gt;The drag that isn&amp;rsquo;t drag&lt;/h2&gt;&#xA;&lt;p&gt;The solver reports a drag number. It&amp;rsquo;s a lie, and I&amp;rsquo;ve had to be quite disciplined about saying so.&lt;/p&gt;&#xA;&lt;p&gt;In genuinely frictionless flow, a closed body experiences no drag at all. The pressure pushing back on the front is exactly cancelled by pressure pushing forward on the rear. This is d&amp;rsquo;Alembert&amp;rsquo;s paradox, and it bothered people for about a century before boundary layers were understood.&lt;/p&gt;&#xA;&lt;p&gt;So the drag my panel solver computes is not physical. It&amp;rsquo;s the residue of chopping a smooth curve into flat segments — a measure of how good my panelling is, nothing more. On the run above it&amp;rsquo;s 0.000172, which is a couple of orders of magnitude smaller than the real drag of that section. If it ever gets large, that tells me my resolution is bad, not that I&amp;rsquo;ve designed a draggy wing.&lt;/p&gt;&#xA;&lt;p&gt;I have a rule written into the project: never report this number as drag. It would be very easy to ship something that looks like a working aerodynamics tool and is quietly nonsense.&lt;/p&gt;&#xA;&lt;h2 id=&#34;two-checks-that-keep-me-honest&#34;&gt;Two checks that keep me honest&lt;/h2&gt;&#xA;&lt;p&gt;The pressure integration gives lift. But there&amp;rsquo;s a completely independent route to the same number — Kutta–Joukowski, which says lift is just twice the total circulation. Two different calculations from the same solution:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cl      = 1.2386&#xA;cl_circ = 1.2387&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If those ever diverge by more than a percent, the solver attaches a warning to its own output. It&amp;rsquo;s a cheap and brutal test: the two agree only if the solution is actually right.&lt;/p&gt;&#xA;&lt;p&gt;The second check is that every fast path has a slow twin. The influence matrix is built with SIMD, four panels at a time, and there&amp;rsquo;s a plain scalar version of the same code that a test requires to agree to floating-point round-off. The optimisation is only safe to keep because the twin exists.&lt;/p&gt;&#xA;&lt;p&gt;One result that looks like a bug and isn&amp;rsquo;t: my lift slope comes out at 6.86 per radian, against the textbook thin-aerofoil value of 2π ≈ 6.28. That&amp;rsquo;s 9.2% high. It&amp;rsquo;s meant to be. Thin-aerofoil theory linearises the thickness away, and thickness genuinely adds lift. Every thick-section panel method overshoots the textbook line by about this much. It took me an embarrassingly long time to stop trying to &amp;ldquo;fix&amp;rdquo; it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-zig&#34;&gt;Why Zig&lt;/h2&gt;&#xA;&lt;p&gt;I wrote about &lt;a href=&#34;https://rmrf.uk/cs/2024-12-23-zig-first-thoughts/&#34;&gt;first impressions of Zig&lt;/a&gt; a while back, and this project has mostly deepened what I liked.&lt;/p&gt;&#xA;&lt;p&gt;Arena allocators suit this problem perfectly. A solve allocates a lot of scratch — matrices, geometry arrays, per-panel state — and every bit of it dies at the same moment. So you allocate freely from an arena and throw the whole thing away at the end, with no per-object bookkeeping at all.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;@Vector(4, f64)&lt;/code&gt; gives you SIMD without intrinsics or a library, and it stays readable.&lt;/p&gt;&#xA;&lt;p&gt;And the discipline that&amp;rsquo;s paid off most isn&amp;rsquo;t a language feature but a rule I set early: no global state, everything passed as a parameter. That felt slightly pedantic while writing it. Then I wanted to run six independent validation cases at once, and it turned out there was nothing to synchronise, because there was nothing shared. It just worked, on the first attempt, for a 3.6× speedup.&lt;/p&gt;&#xA;&lt;p&gt;The thing I still find genuinely hard is the build system. It&amp;rsquo;s powerful and it&amp;rsquo;s clean, but 0.16 moved to a module-based &lt;code&gt;build.zig&lt;/code&gt; and most of what you&amp;rsquo;ll find online is written against something older.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-it-cant-do&#34;&gt;What it can&amp;rsquo;t do&lt;/h2&gt;&#xA;&lt;p&gt;This is worth being blunt about, because a plausible-looking wrong answer is worse than no answer.&lt;/p&gt;&#xA;&lt;p&gt;There is no viscosity in any of this. So: no real drag, no stall, no separation, no transition from laminar to turbulent flow, no compressibility. You cannot get a glide ratio out of it, because you cannot get drag out of it. You cannot find the stall angle, because nothing in the model knows how to stall — push the angle up and the lift just keeps rising happily, forever, which real wings famously do not.&lt;/p&gt;&#xA;&lt;p&gt;What it does give you, and gives you fast and accurately, is lift and the pressure distribution for attached flow. That turns out to be exactly the input the second story needs.&lt;/p&gt;&#xA;&lt;h2 id=&#34;next&#34;&gt;Next&lt;/h2&gt;&#xA;&lt;p&gt;The boundary layer. That&amp;rsquo;s where drag and stall live, and it&amp;rsquo;s a much nastier problem — the thin film and the outer flow each need the other&amp;rsquo;s answer, so you end up iterating between two solvers until they stop arguing. I have that working, mostly. I also have a validation gate that it currently fails on two criteria out of five, which I&amp;rsquo;ll write about honestly, because the failures are more interesting than the passes.&lt;/p&gt;&#xA;&lt;p&gt;The diagrams in this post were generated from the solver itself rather than drawn by hand, which felt like the right way to do it. If any of it is wrong, I&amp;rsquo;d genuinely like to know.&lt;/p&gt;&#xA;</content:encoded>
    </item>
    <item>
      <title>How we built Soaring Spirit</title>
      <link>https://rmrf.uk/cs/2026-07-26-how-we-built-soaring-spirit/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://rmrf.uk/cs/2026-07-26-how-we-built-soaring-spirit/</guid>
      <category>Computer Broscience</category>
      <description>Soaring Spirit is my first company, a Swiss GmbH running speedflying trips and coaching. The whole product runs as one Go binary on a Raspberry Pi in my house. Sixteen months in, this is what that choice has cost and what it has bought.</description>
      <content:encoded>&lt;p&gt;&lt;a href=&#34;https://soaringspirit.ch&#34;&gt;Soaring Spirit&lt;/a&gt; is my first company. It&amp;rsquo;s a Swiss GmbH, it runs speedflying and paragliding trips and coaching, and the people in it are pilots rather than programmers — Greg cooks and does the osteopathy, Jamie has more speedwing hours than almost anyone alive, Oli drives the bus and keeps the books, Nika gets us into the parts of Georgia nobody else can reach. So when I say &amp;ldquo;we built&amp;rdquo;, I mean the company is a we. The software is mine, and this post is about the software.&lt;/p&gt;&#xA;&lt;p&gt;First commit was 2025-03-13. Sixteen months and 706 commits later it books trips, takes deposits and balances through Stripe, issues invoices, runs a coaching-availability calendar, hosts a flying syllabus, keeps a database of speedflying incidents scraped from the Swiss and French fatality reports, and logs in every user without ever having stored a password.&lt;/p&gt;&#xA;&lt;p&gt;All of it is one Go binary, cross-compiled to arm64 and copied onto a Raspberry Pi in my house.&lt;/p&gt;&#xA;&lt;p&gt;I want to write about the constraints rather than the features, because the constraints are the interesting part. Nearly every decision here was some version of &amp;ldquo;what is the smallest thing that can possibly work&amp;rdquo;, and a few of them were wrong in ways that took months to surface.&lt;/p&gt;&#xA;&lt;h2 id=&#34;one-binary-one-machine&#34;&gt;One binary, one machine&lt;/h2&gt;&#xA;&lt;p&gt;The deploy script is worth quoting more or less in full, because it is the whole operations story:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GOOS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;linux GOARCH&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;arm64 go build -ldflags &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;...&amp;#34;&lt;/span&gt; -o ss_linux_arm64 .&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;scp ss_linux_arm64 &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$HOST&lt;span style=&#34;color:#e6db74&#34;&gt;:~/ss.new&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ssh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$HOST&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mv ~/ss.new ~/ss &amp;amp;&amp;amp; sudo systemctl restart ss&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;aside class=&#34;note&#34;&gt;The tunnel is the part that makes this respectable rather than reckless. The Pi holds an outbound connection to Cloudflare and nothing else; there is no inbound port open on my home network at all, and no origin address to find.&lt;/aside&gt;&#xA;&#xA;&lt;p&gt;That&amp;rsquo;s it. Build, copy, restart. The Pi sits behind a Cloudflare Tunnel, so there is no public IP, no inbound port forwarding, no load balancer and no bill. Postgres runs on the same machine. &lt;code&gt;pgbackrest&lt;/code&gt; ships backups to S3 on a schedule.&lt;/p&gt;&#xA;&lt;p&gt;The frontend is deployed by &lt;code&gt;rsync&lt;/code&gt;, and then the Cloudflare cache is purged. There is no build step for it at all — I&amp;rsquo;ll come back to that.&lt;/p&gt;&#xA;&lt;p&gt;The obvious objection is that this is one machine and one power cut. That&amp;rsquo;s true, and it&amp;rsquo;s a real risk I&amp;rsquo;ve accepted rather than solved. The mitigation is entirely on the data side: the backups are off-site and verified, and the binary can be rebuilt and redeployed onto anything with an ARM chip in about ninety seconds. What I&amp;rsquo;d lose in a hardware failure is availability, not the business. For a company that sells six trips a year to people who mostly book after talking to us in person, that&amp;rsquo;s the right trade. It would be a bad trade for something with real-time revenue.&lt;/p&gt;&#xA;&lt;p&gt;The less obvious cost is that one process serving both the API and the site means a deploy takes the whole site down for the second it takes systemd to restart. I&amp;rsquo;ve never minded this and I doubt anyone has noticed, but it&amp;rsquo;s the kind of thing that&amp;rsquo;s fine until it isn&amp;rsquo;t.&lt;/p&gt;&#xA;&lt;h2 id=&#34;ten-dependencies&#34;&gt;Ten dependencies&lt;/h2&gt;&#xA;&lt;p&gt;The &lt;code&gt;go.mod&lt;/code&gt; has ten direct requirements: a Postgres driver, Stripe, WebAuthn, an SMTP pool, three image libraries, and three &lt;code&gt;golang.org/x&lt;/code&gt; packages. No web framework, no ORM, no dependency injection container, no code generation.&lt;/p&gt;&#xA;&lt;p&gt;The router is 154 lines. Here is essentially all of it:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Router&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;ServeHTTP&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ResponseWriter&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;req&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Request&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;req&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;URL&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Path&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;route&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;routes&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;params&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;matchPath&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;route&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Segments&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;route&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Method&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;req&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Method&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&#x9;&lt;span style=&#34;color:#75715e&#34;&gt;// ...inject params into the context, wrap in middleware, serve&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;notFoundHandler&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ServeHTTP&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;req&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A linear scan over about 110 routes, splitting the path on &lt;code&gt;/&lt;/code&gt; and comparing segments. It is not clever and it does not need to be — this is a Raspberry Pi answering maybe a few thousand requests a day, and the linear scan is nowhere near the top of any profile I&amp;rsquo;ve taken. I wrote it before Go 1.22 gave the standard library pattern matching, and I&amp;rsquo;ve left it because it works and because the middleware ordering is explicit in a way I like.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not going to pretend this is universally the right call. I wrote a router, a session store, an image pipeline and a mail queue that all exist as libraries, and each of them cost me a weekend I could have spent on the actual product. What I got back is that when something breaks at eleven at night before a trip, the stack trace goes through code I wrote, and I can read all of it. That has paid for itself several times.&lt;/p&gt;&#xA;&lt;p&gt;The honest counterweight is the test suite: about 11,000 lines of Go tests against 19,000 lines of everything else, across 31 integration test files that run against a real Postgres in Docker Compose. Writing your own primitives is only defensible if you also write the tests that a library would have shipped with. I didn&amp;rsquo;t understand that at the start and the early code shows it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;no-frontend-build-step&#34;&gt;No frontend build step&lt;/h2&gt;&#xA;&lt;p&gt;The frontend is about 20,000 lines of hand-written JavaScript. Lit is vendored as a single file. There is no bundler, no transpiler, no &lt;code&gt;npm run build&lt;/code&gt;, no &lt;code&gt;node_modules&lt;/code&gt; in the deploy. Files are served from disk, so an edit is live on reload.&lt;/p&gt;&#xA;&lt;p&gt;Routes are plain objects with a dynamic import:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;trip/:id&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;component&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ss-trip-detail&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;action&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; () =&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;./components/app/trip/trip-detail.js&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The browser does the code splitting, because ES modules already are code splitting. Every component is a &lt;code&gt;LitElement&lt;/code&gt; with its styles in a tagged template literal next to its markup.&lt;/p&gt;&#xA;&lt;p&gt;What this buys is that the edit-to-live loop is a file save, and that there is no build to break. What it costs is that &lt;code&gt;node --check&lt;/code&gt; is the entire compile step — nothing catches a typo in a property name until it renders wrong. Over sixteen months, that has cost me less than I expected. Shadow DOM turns out to do most of what a build step&amp;rsquo;s CSS scoping would.&lt;/p&gt;&#xA;&lt;p&gt;It has one wart I still haven&amp;rsquo;t fixed properly. The server pre-renders the SPA shell&amp;rsquo;s &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, so it keeps its own table of route titles and descriptions in &lt;code&gt;app/prerender.go&lt;/code&gt;, mirroring the one in &lt;code&gt;site/index.js&lt;/code&gt;. Two hand-kept copies of the same data. It&amp;rsquo;s documented in three places and I&amp;rsquo;ve still got it wrong once.&lt;/p&gt;&#xA;&lt;h2 id=&#34;money-is-where-the-discipline-goes&#34;&gt;Money is where the discipline goes&lt;/h2&gt;&#xA;&lt;p&gt;Everything above is &amp;ldquo;how little can I get away with&amp;rdquo;. Bookings are where that stops.&lt;/p&gt;&#xA;&lt;p&gt;A booking moves through ten states — &lt;code&gt;DRAFT&lt;/code&gt;, &lt;code&gt;IN_REVIEW&lt;/code&gt;, &lt;code&gt;AWAITING_DEPOSIT&lt;/code&gt;, &lt;code&gt;DEPOSIT_PAID&lt;/code&gt;, &lt;code&gt;DEPOSIT_TO_REFUND&lt;/code&gt;, &lt;code&gt;DEPOSIT_REFUNDED&lt;/code&gt;, &lt;code&gt;AWAITING_BALANCE&lt;/code&gt;, &lt;code&gt;FINALISED&lt;/code&gt;, &lt;code&gt;VOID&lt;/code&gt;, &lt;code&gt;REJECTED&lt;/code&gt; — and the legal transitions are an explicit switch, not a convention:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;current&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BookingStateInReview&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BookingStateAwaitingDeposit&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BookingStateRejected&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BookingStateVoid&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BookingStateDepositPaid&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BookingStateDepositToRefund&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BookingStateAwaitingBalance&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;errInvalidTransition&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Every transition goes through one function, which opens a transaction, takes &lt;code&gt;SELECT state FROM app.bookings WHERE id = $1 FOR UPDATE&lt;/code&gt;, validates, and writes with the old state in the &lt;code&gt;WHERE&lt;/code&gt; clause. The row lock is there because the check-then-write without it is a textbook TOCTOU: two concurrent callers both read &lt;code&gt;AWAITING_DEPOSIT&lt;/code&gt;, both decide the transition is legal, both proceed.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s one flag on it that took me a while to get right. A transition to the state you&amp;rsquo;re already in is treated as a no-op rather than an error, because Stripe redelivers webhooks and a retried handler re-observing its own applied state is normal. But a caller that&amp;rsquo;s about to issue an actual refund on the strength of that transition cannot accept a no-op — two callers racing there means refunding twice. So &lt;code&gt;RequireStateChange&lt;/code&gt; turns the no-op back into a conflict for exactly those callers:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Set this for callers about to perform an external side effect (e.g. a&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Stripe refund) on the strength of this transition, so a second,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// concurrent caller that raced past the same check doesn&amp;#39;t repeat the&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// side effect.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;the-retry-loop&#34;&gt;The retry loop&lt;/h3&gt;&#xA;&lt;p&gt;The bug I&amp;rsquo;m most glad to have found is not in that machine, it&amp;rsquo;s in what surrounds it.&lt;/p&gt;&#xA;&lt;p&gt;Stripe redelivers any webhook that doesn&amp;rsquo;t return 2xx, backing off over several days. That&amp;rsquo;s correct for a transient failure — database down, network blip — where a later attempt might work. It is actively harmful for a deterministic rejection.&lt;/p&gt;&#xA;&lt;p&gt;During the host merge in July, a past &lt;code&gt;invoice.paid&lt;/code&gt; event got resent for a booking that had already reached &lt;code&gt;FINALISED&lt;/code&gt;. The handler read it as a deposit payment and attempted &lt;code&gt;FINALISED&lt;/code&gt; → &lt;code&gt;DEPOSIT_PAID&lt;/code&gt;. The state machine rejected it, correctly. The handler returned 500. Stripe retried. The state machine rejected it again, identically, because the state machine is deterministic — that&amp;rsquo;s the entire point of it.&lt;/p&gt;&#xA;&lt;p&gt;So it retried for days.&lt;/p&gt;&#xA;&lt;p&gt;The fix is a distinction rather than a patch: an invalid-transition error is now a sentinel that the webhook handler acknowledges with a 2xx, because &amp;ldquo;received, nothing to do&amp;rdquo; is the honest answer when the booking is already in its terminal state. Only genuinely transient errors get a 5xx. It&amp;rsquo;s obvious in hindsight and it was not obvious at all in advance, and it&amp;rsquo;s the clearest lesson the project has taught me — &lt;em&gt;retry semantics are a property of the error, not of the endpoint.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;let-the-database-say-no&#34;&gt;Let the database say no&lt;/h2&gt;&#xA;&lt;p&gt;The application checks that two coaching sessions don&amp;rsquo;t overlap. That check is a race, always, no matter how carefully it&amp;rsquo;s written, because two requests can pass it concurrently before either commits.&lt;/p&gt;&#xA;&lt;aside class=&#34;note&#34;&gt;&lt;code&gt;btree_gist&lt;/code&gt; is needed because the constraint mixes two kinds of comparison: plain equality on &lt;code&gt;contact_id&lt;/code&gt; and overlap on the date range. A stock gist index handles the range; the extension is what lets an ordinary scalar sit beside it in the same index.&lt;/aside&gt;&#xA;&#xA;&lt;p&gt;Postgres will just do this for you:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CREATE&lt;/span&gt; EXTENSION &lt;span style=&#34;color:#66d9ef&#34;&gt;IF&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;EXISTS&lt;/span&gt; btree_gist;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ALTER&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;TABLE&lt;/span&gt; app.coaching_sessions&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;ADD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;CONSTRAINT&lt;/span&gt; excl_coaching_sessions_overlap&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    EXCLUDE &lt;span style=&#34;color:#66d9ef&#34;&gt;USING&lt;/span&gt; gist (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        contact_id &lt;span style=&#34;color:#66d9ef&#34;&gt;WITH&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        daterange(window_start, window_end, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[]&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;WITH&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ) &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; (status &lt;span style=&#34;color:#66d9ef&#34;&gt;IN&lt;/span&gt; (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;PENDING&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;CONFIRMED&amp;#39;&lt;/span&gt;));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The application check stays, because it produces a friendly error message at the right moment in the UI. The constraint is the thing that&amp;rsquo;s actually true. I&amp;rsquo;ve come round to thinking this is the correct shape for most validation: the readable error in the app, the authority in the schema.&lt;/p&gt;&#xA;&lt;p&gt;The same instinct runs through the rest of the schema — seventy-odd numbered migration files, currently up to &lt;code&gt;0193&lt;/code&gt;. Images are content-addressed by SHA-256 and stored as &lt;code&gt;bytea&lt;/code&gt;, so uploading the same photo twice is a no-op; they&amp;rsquo;re resized and encoded to both JPEG and AVIF in-process, on the Pi, which is slower than I&amp;rsquo;d like and has never been slow enough to matter.&lt;/p&gt;&#xA;&lt;p&gt;My favourite migration is &lt;code&gt;0130_remove_passwords.sql&lt;/code&gt;, which drops six columns and is the entire content of the file. Login is passkeys, or a six-digit code by email. Nothing to leak.&lt;/p&gt;&#xA;&lt;h2 id=&#34;two-ways-to-get-email-wrong&#34;&gt;Two ways to get email wrong&lt;/h2&gt;&#xA;&lt;p&gt;Until three weeks ago, sending mail went straight to SMTP inline, and it managed to be wrong in two opposite directions at once.&lt;/p&gt;&#xA;&lt;p&gt;Most call sites sent from a detached goroutine and logged the error. A momentary SMTP outage meant a user silently never learned their booking was approved — no record, no retry, no way to answer &amp;ldquo;did they actually get told?&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;The Stripe webhook did the opposite: it returned the send error up, so a failed email failed the whole webhook, and Stripe retried the hook and re-ran every side effect around it. A bounced email could void an invoice twice.&lt;/p&gt;&#xA;&lt;p&gt;Both are the same bug — treating delivery as part of the transaction that caused it. The fix is a transactional outbox: write the intent to a table, drain it from a worker with backoff and a max attempt count, and key rows on an idempotency key so a redelivered webhook produces one receipt rather than two. Delivered rows are kept for ninety days rather than deleted, because &amp;ldquo;did that user get told?&amp;rdquo; should be a query.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-thing-that-stopped-running&#34;&gt;The thing that stopped running&lt;/h2&gt;&#xA;&lt;p&gt;For eleven days in July, every page on the site told Google it was a duplicate of the homepage.&lt;/p&gt;&#xA;&lt;p&gt;The SPA ships one generic &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;, description and canonical in &lt;code&gt;index.html&lt;/code&gt;, and rewrites them in JavaScript once the router resolves. That&amp;rsquo;s invisible to anything that doesn&amp;rsquo;t execute JavaScript, so to fix it for crawlers there was a Cloudflare Pages Function — &lt;code&gt;site/functions/_middleware.js&lt;/code&gt; — which filled the head in server-side. It worked. It had worked since the day I wrote it.&lt;/p&gt;&#xA;&lt;aside class=&#34;note&#34;&gt;Added 2026-07-01 with the rest of the SEO work — sitemap, &lt;code&gt;robots.txt&lt;/code&gt;, per-route meta. All of that survived the move. The middleware was the only piece that depended on &lt;em&gt;where&lt;/em&gt; the site was served from.&lt;/aside&gt;&#xA;&#xA;&lt;p&gt;Then on 15 July I moved site hosting off Pages and onto the Pi, so the site would deploy by &lt;code&gt;rsync&lt;/code&gt; and sit behind the same cache rules as everything else. That was a good change and I&amp;rsquo;d make it again. It also switched the middleware off, because a Pages Function is a thing Pages does, not a file that runs wherever it happens to sit.&lt;/p&gt;&#xA;&lt;p&gt;Nothing errored. Nothing logged. &lt;code&gt;site/functions/_middleware.js&lt;/code&gt; stayed in the repo looking exactly as maintained as it had the week before, and every page quietly went back to shipping &lt;code&gt;&amp;lt;link rel=&amp;quot;canonical&amp;quot; href=&amp;quot;https://soaringspirit.ch/&amp;quot;&amp;gt;&lt;/code&gt; — each one declaring itself a duplicate of the homepage.&lt;/p&gt;&#xA;&lt;p&gt;Eleven days is about as cheap as this class of bug gets, and I only caught it because I was planning the host merge and went to check what the middleware would do afterwards. What bothers me isn&amp;rsquo;t the eleven days, it&amp;rsquo;s that nothing could have caught it. There&amp;rsquo;s no test for &amp;ldquo;this file is still in a position to execute&amp;rdquo;. The deploy got faster and simpler, and what broke was a capability rather than a line of code.&lt;/p&gt;&#xA;&lt;p&gt;The fix folded into a merge I wanted anyway. &lt;code&gt;api.soaringspirit.ch&lt;/code&gt; and &lt;code&gt;soaringspirit.ch&lt;/code&gt; were two processes from the same binary on the same machine behind the same tunnel, and the split existed for no reason beyond history — while costing a CORS middleware, a preflight round-trip on every non-simple request, and a sitemap on the wrong host. Moving the API under &lt;code&gt;/api&lt;/code&gt; on the site origin deleted the CORS code entirely and let the head for &lt;code&gt;/trip/:id&lt;/code&gt; be rendered from a direct database query instead of an HTTP call to ourselves.&lt;/p&gt;&#xA;&lt;p&gt;Sixteen route collisions had to be resolved to do it, and &lt;code&gt;http.StripPrefix&lt;/code&gt; meant all 110 route registrations stayed exactly as written. Everyone gets the same correct head now — no user-agent sniffing, because varying HTML by user agent would fragment the edge cache and there&amp;rsquo;s no upside.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-id-tell-myself-at-the-first-commit&#34;&gt;What I&amp;rsquo;d tell myself at the first commit&lt;/h2&gt;&#xA;&lt;p&gt;Write the integration tests before the third feature, not after the thirtieth. They&amp;rsquo;re the only reason the no-framework approach is defensible, and I built up months of debt before I understood that.&lt;/p&gt;&#xA;&lt;p&gt;Push correctness into the schema earlier. Every exclusion constraint and foreign key I added late replaced application code I&amp;rsquo;d been quietly trusting.&lt;/p&gt;&#xA;&lt;p&gt;And when you move a platform, inventory what the old one was doing for you. Migration notes list what you&amp;rsquo;re carrying across, and I wrote good ones. What they don&amp;rsquo;t list is the work the old platform did for free, because nobody had to think about it while it worked — that&amp;rsquo;s exactly the part that leaves no diff, throws no error, and shows up weeks later in a search result. A file can keep being correct long after it has stopped being &lt;em&gt;reachable&lt;/em&gt;, and those two things fail completely independently.&lt;/p&gt;&#xA;&lt;p&gt;The company is sixteen months old and the software has never lost a booking. For a first attempt at both, I&amp;rsquo;ll take it.&lt;/p&gt;&#xA;</content:encoded>
    </item>
    <item>
      <title>Progress on the Peregrine HTTP server</title>
      <link>https://rmrf.uk/cs/2025-01-09-progress-on-peregrine/</link>
      <pubDate>Thu, 09 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://rmrf.uk/cs/2025-01-09-progress-on-peregrine/</guid>
      <category>Computer Broscience</category>
      <description>As a way for me to dive deeper into Zig, I'm writing a HTTP server from scratch. This article describes some of the work I've done recently, and a few lessons that I've learned.</description>
      <content:encoded>&lt;p&gt;I&amp;rsquo;m implementing a HTTP server from scratch in Zig, as a fun way to learn the Zig systems programming language, and also to get more comfortable and familiar with some modern kernel features that make highly performant network IO possible.&lt;/p&gt;&#xA;&lt;p&gt;I started this project on the 24th December 2024, and within a couple of days, I had a bare-bones TCP server that could parse and respond to HTTP requests. Initially, I followed Karl Seguin&amp;rsquo;s excellent &lt;a href=&#34;https://www.openmymind.net/TCP-Server-In-Zig-Part-1-Single-Threaded/&#34;&gt;guide to creating a TCP server in Zig&lt;/a&gt;. If you&amp;rsquo;re interested in learning Zig, after solving some &lt;a href=&#34;https://codeberg.org/ziglings/exercises/&#34;&gt;Ziglings&lt;/a&gt;, I suggest that you look at Karl&amp;rsquo;s blog posts on Zig.&lt;/p&gt;&#xA;&lt;p&gt;After some light optimisation work, I wrote some example applications because I know that the best way to test the usability of your library is to use it.&lt;/p&gt;&#xA;&lt;p&gt;Initially, the server was initialised with an &lt;code&gt;on_request(request, response) void&lt;/code&gt; handler function. I found that this approach made it difficult to create a stateful request handler. After trying a VTable-based interface, I settled on the more idiomatic Zig generic pattern, leveraging Zig&amp;rsquo;s comptime metaprogramming. Basically, you just call &lt;code&gt;Server(MyHandler).init(config)&lt;/code&gt;, where Server is a function that returns a type of Server that has your handler built in at compile time. You then simply define your handler as the implementation of an interface, containing at least the required methods. If the user messes anything up, the program will simply fail to compile, and some useful error will be output.&lt;/p&gt;&#xA;&lt;p&gt;Anyway, with the nicer API, I implemented a simple counter server where the handler increments a counter on each request, and responds with the current value. I then wanted to see my server doing something useful, so I wrote a directory server, and served this website locally. To my delight, the server was working great, and the implementation of the DirServer helper required no significant changes to the server. It was fun to see the server working well, serving binaries and text files that were rendered correctly by the browser.&lt;/p&gt;&#xA;&lt;p&gt;At this point, the server was slightly more performant than Go&amp;rsquo;s stdlib HTTP server. Go&amp;rsquo;s HTTP server is a good benchmark, because it&amp;rsquo;s well written and used extensively in production. Also, it supports HTTP/2 and TLS out of the box, which is really great. Unfortunately, Peregrine does not yet support HTTP/2 or TLS. However, Peregrine does beat Go&amp;rsquo;s HTTP server in one regard, and that is built in support for WebSockets. Peregrine features full support for WebSockets, and adding WebSocket support to your application is as easy as handling the connection upgrade by calling the upgrade helper function, and implementing the WS handler methods in your handler. It really could not be easier, and I&amp;rsquo;m quite happy with how that turned out.&lt;/p&gt;&#xA;&lt;p&gt;Slightly more performant than Go&amp;rsquo;s HTTP server? There is no way that we can be happy with that&amp;hellip; after all, many HTTP servers outperform Go&amp;rsquo;s.&lt;/p&gt;&#xA;&lt;p&gt;From my own tests, NGINX only marginally outperforms Go&amp;rsquo;s, even with some tuning such as enabling Kqueue/Epoll and setting the thread count equal to the CPU core count. That surprised me, because as far as I know, Go relies on its runtime scheduler, and does not use Kqueue/Epoll for socket readiness notifications, which should eliminate wasted CPU time spent polling the sockets.&lt;/p&gt;&#xA;&lt;p&gt;At the front of the pack is &lt;a href=&#34;https://github.com/zigzap/zap&#34;&gt;Zap&lt;/a&gt;, built on &lt;a href=&#34;https://facil.io&#34;&gt;Facil.io&lt;/a&gt;. Zap is basically Zig bindings for the HTTP server part of Facil.io. Facil.io is a marvellous networking library written in C. The more I study and benchmark Facil.io, the more I&amp;rsquo;ve come to appreciate its stability and performance. On my M2 Pro, Zap will process in excess of 170K static GET requests per second with 1000 connections. I&amp;rsquo;ve not yet found a HTTP server that outperforms this with 1000 connections. Please let me know if there is one.&lt;/p&gt;&#xA;&lt;p&gt;As a baseline, on 28th December, 4 days after the start of the project, Peregrine was doing around 86K static GET requests per second with 1000 connections. Similar to Go&amp;rsquo;s stdlib HTTP server and also similar to (tuned) NGINX. At this point, Zap/Facil.io does more than twice the throughput. We have a lot of work to do&amp;hellip;&lt;/p&gt;&#xA;&lt;h2 id=&#34;step-1-improve-the-handler&#34;&gt;Step 1: Improve the handler&lt;/h2&gt;&#xA;&lt;p&gt;As I mentioned earlier, the nature of the library&amp;rsquo;s on_request hook made implementing a stateful handler inconvenient at best. This was at the top of my list. I implemented a VTable-based interface, but I didn&amp;rsquo;t like that the user needed to declare the VTable in their handler:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// commit 0beb0019df043abb43e924d4aa8234f479eba607&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ./example/basic.zig&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; MyHandler &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; vtable: pereg.HandlerVTable &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; .{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .handle &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; handle,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handle&lt;/span&gt;(ptr: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;anyopaque&lt;/span&gt;, req: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;pereg.Request, resp: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;pereg.Response) &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; self &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @as(&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;@This(), @alignCast(@ptrCast(ptr)));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self.&lt;span style=&#34;color:#a6e22e&#34;&gt;handleWithError&lt;/span&gt;(req, resp) &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;err&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            std.debug.&lt;span style=&#34;color:#a6e22e&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;error handling request: {any}&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, .{err});&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;inline&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handleWithError&lt;/span&gt;(_: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;@This(), _: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;pereg.Request, resp: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;pereg.Response) &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _ &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; resp.&lt;span style=&#34;color:#a6e22e&#34;&gt;setBody&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Kawww&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; len_header &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; pereg.Header.&lt;span style=&#34;color:#a6e22e&#34;&gt;init&lt;/span&gt;(.{ .key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Content-Length&amp;#34;&lt;/span&gt;, .value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;6&amp;#34;&lt;/span&gt; });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; resp.headers.&lt;span style=&#34;color:#a6e22e&#34;&gt;append&lt;/span&gt;(len_header);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This version actually performed quite well, doing a little over 160K requests per second. That&amp;rsquo;s nearly a 2x improvement from our baseline! In retrospect, I&amp;rsquo;m surprised that this version performs so well, because the indirection of the VTable lookup should cost at least a few nanoseconds, not to mention that this indirection should increase CPU cache misses for calls to handler methods. It seems that function pointers are even more costly than VTables. Anyway, this performs well, but it&amp;rsquo;s quite ugly, and does not make use of what Zig has to offer. We need a better way to &amp;lsquo;inject&amp;rsquo; our handler into the server.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// commit 2d13a14cd5ed77de040bea003ad37a55020fb54d&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ./example/basic.zig&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; std &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @import(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;std&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pereg &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @import(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;peregrine&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; MyHandler &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;init&lt;/span&gt;(allocator: std.mem.Allocator) &lt;span style=&#34;color:#f92672&#34;&gt;!*&lt;/span&gt;@This() {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; allocator.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(@This());&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;deinit&lt;/span&gt;(_: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;@This()) &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handle&lt;/span&gt;(self: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;@This(), req: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;pereg.Request, resp: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;pereg.Response) &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self.&lt;span style=&#34;color:#a6e22e&#34;&gt;handleWithError&lt;/span&gt;(req, resp) &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;err&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            std.debug.&lt;span style=&#34;color:#a6e22e&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;error handling request: {any}&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, .{err});&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handleWithError&lt;/span&gt;(_: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;@This(), _: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;pereg.Request, resp: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;pereg.Response) &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _ &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; resp.&lt;span style=&#34;color:#a6e22e&#34;&gt;setBody&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Kawww&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; len_header &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; pereg.Header.&lt;span style=&#34;color:#a6e22e&#34;&gt;init&lt;/span&gt;(.{ .key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Content-Length&amp;#34;&lt;/span&gt;, .value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;6&amp;#34;&lt;/span&gt; });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; resp.headers.&lt;span style=&#34;color:#a6e22e&#34;&gt;append&lt;/span&gt;(len_header);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; gpa &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; std.heap.&lt;span style=&#34;color:#a6e22e&#34;&gt;GeneralPurposeAllocator&lt;/span&gt;(.{}){};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; allocator &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; gpa.&lt;span style=&#34;color:#a6e22e&#34;&gt;allocator&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;defer&lt;/span&gt; _ &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; gpa.&lt;span style=&#34;color:#a6e22e&#34;&gt;deinit&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; srv &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; pereg.&lt;span style=&#34;color:#a6e22e&#34;&gt;Server&lt;/span&gt;(MyHandler).&lt;span style=&#34;color:#a6e22e&#34;&gt;init&lt;/span&gt;(allocator, &lt;span style=&#34;color:#ae81ff&#34;&gt;3000&lt;/span&gt;, .{});&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    std.debug.&lt;span style=&#34;color:#a6e22e&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;listening on 0.0.0.0:3000&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, .{});&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; srv.&lt;span style=&#34;color:#a6e22e&#34;&gt;start&lt;/span&gt;(); &lt;span style=&#34;color:#75715e&#34;&gt;// Blocks if there is no error&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By the way, sorry for &lt;code&gt;@This()&lt;/code&gt; in the method signatures, this was pasted as committed. I&amp;rsquo;ve since started declaring &lt;code&gt;const Self = @This();&lt;/code&gt; to clean up method signatures. This looks much better. Our handler simply implements the 3 required methods; init, deinit, and handle. Our handler is then given to the Server by making Server a function that returns a type. Our handler is passed to the &amp;lsquo;constructor&amp;rsquo; as a parameter. Under the hood:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// commit 2d13a14cd5ed77de040bea003ad37a55020fb54d&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ./src/server.zig&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Server&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;comptime&lt;/span&gt; Handler: &lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;comptime&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;@hasDecl(Handler, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;init&amp;#34;&lt;/span&gt;)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                @compileError(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Handler must implement init(std.mem.Allocator) !*@This()&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;@hasDecl(Handler, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;deinit&amp;#34;&lt;/span&gt;)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                @compileError(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Handler must implement deinit(*@This()) void&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;@hasDecl(Handler, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;handle&amp;#34;&lt;/span&gt;)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                @compileError(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Handle must implement handle(*@This(), *Request, *Response) void&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        handler: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;Handler,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pretty simple, right? As it should be. As you can see, we will get a compile error if our handler is missing any of the 3 required methods.&lt;/p&gt;&#xA;&lt;h2 id=&#34;step-2-optimise-the-request-parser&#34;&gt;Step 2: optimise the request parser&lt;/h2&gt;&#xA;&lt;p&gt;Being a text protocol, efficient parsing of a HTTP request comes down to well-implemented buffered reading from the socket, and optimised parsing operations. The parts of a HTTP request are delimited by CRLF (sometimes clients may use bare LF line endings). The first line contains the request method, path, and HTTP version. The following lines contain headers, until an empty line. If present, the request body would follow. The length would be specified in a Content-Length header. For example (without body):&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;GET /path/to/resource HTTP/1.1\r\n&#xA;Host: localhost\r\n&#xA;User-Agent: Microsoft Internet Explorer 6\r\n&#xA;Accept: */*\r\n&#xA;Connection: keep-alive\r\n&#xA;\r\n&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As a side note, the Connection header is the client&amp;rsquo;s request for the server to either close or keep-alive the TCP connection. HTTP/1.1 introduced the keep-alive mechanism, allowing connections to be reused. This greatly improves HTTP&amp;rsquo;s efficiency, especially as modern websites typically consist of many resources that are requested individually. In HTTP/1.0, clients had to create a new connection for each request. Our server must handle both HTTP/1.0 and HTTP/1.1 as a bare minimum. HTTP/2 and HTTP/3 would be great to have, but these are binary protocols, and must be upgraded to from a HTTP/1.1 connection. Additionally, HTTP/3 is not over TCP, but QUIC, which is over UDP. Support for HTTP/2 and 3 may come later.&lt;/p&gt;&#xA;&lt;p&gt;So, how to parse this request? Logically, we should read it line-by-line. So far, I&amp;rsquo;ve found that efficient request parsing is about minimising search and copy operations.&lt;/p&gt;&#xA;&lt;p&gt;If you&amp;rsquo;re interested, check out &lt;a href=&#34;https://github.com/intob/peregrine/blob/main/src/reader.zig&#34;&gt;./src/reader.zig&lt;/a&gt;. I&amp;rsquo;ll spare you a complicated code-dump.&lt;/p&gt;&#xA;&lt;h3 id=&#34;method-path-and-version&#34;&gt;Method, path and version&lt;/h3&gt;&#xA;&lt;p&gt;In essence, the method and version are parsed first, at the beginning and the end of the first line. Parsing the method was originally done using a compile-time generated lookup table of 8-byte masks. SIMD operations were then used to compare the first 8 bytes of the request with the lookup table. In practice, I was not happy with how some methods were parsed fairly quickly (9ns), but other methods further down the lookup table took considerably longer (over 30ns) due them requiring more comparisons.&lt;/p&gt;&#xA;&lt;p&gt;While a few nanoseconds may seem negligible, I&amp;rsquo;m trying to keep request parsing under 2000 CPU cycles. That&amp;rsquo;s around 600ns on my M2 Pro.&lt;/p&gt;&#xA;&lt;p&gt;I also tried a switch of the method length before byte comparisons, but this necessitated finding the first &amp;rsquo; &amp;lsquo;, which cost more than the saving of narrowing down the number of possible methods.&lt;/p&gt;&#xA;&lt;p&gt;Today, I settled on the following:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; Method &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt;(u4) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    GET,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PUT,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    HEAD,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    POST,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PATCH,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    TRACE,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    DELETE,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    CONNECT,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    OPTIONS,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parse&lt;/span&gt;(bytes: []&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;u8&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;Method {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (bytes.len &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;.UnsupportedMethod;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; (bytes[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;G&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; .GET,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;P&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; (bytes[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;O&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; .POST,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;U&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; .PUT,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;A&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; .PATCH,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;.UnsupportedMethod,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;H&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; .HEAD,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;T&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; .TRACE,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;D&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; .DELETE,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;C&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; .CONNECT,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;O&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; .OPTIONS,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;.UnsupportedMethod,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;toLength&lt;/span&gt;(self: Method) &lt;span style=&#34;color:#66d9ef&#34;&gt;usize&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; (self) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .GET &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .PUT &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .HEAD &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .POST &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .PATCH &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .TRACE &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .DELETE &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .CONNECT &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .OPTIONS &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As beautiful as the comptime-generated lookup table was, I found that the overhead of initialising SIMD vectors was too expensive for this use-case. The above solution is much faster, with methods parsed between 6ns and 9ns. We simply give it at least 3 bytes, and in the fewest operations possible, it will return the method.&lt;/p&gt;&#xA;&lt;p&gt;We then parse the HTTP version. Not worthy of a code-dump, we simply take the last 8 bytes of the request line, and check that the 6th byte is &amp;lsquo;1&amp;rsquo; (HTTP/1), the 7th byte is &amp;lsquo;.&amp;rsquo;, and switch over the 8th byte to return the version enum 1.0 or 1.1. You can see it in &lt;a href=&#34;https://github.com/intob/peregrine/blob/main/src/version.zig&#34;&gt;./src/version.zig&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Next is extracting the path. You probably noticed the toLength method in the Method enum. We know that everything between the method and the version (minus the spaces) is the path and query. We also know that the version is 8 bytes in length. Therefore, by calling toLength() on the method, we have the position of the beginning of the path without a single scalar search for the delimiting spaces (&amp;rsquo; &amp;lsquo;).&lt;/p&gt;&#xA;&lt;p&gt;But what about separating the query from the path? We could simply defer this until (if ever) the query is needed, omitting the search for &amp;lsquo;?&amp;rsquo;, but I found that this hurt the usability of the library. I may change this again because searching for &amp;lsquo;?&amp;rsquo; is relatively expensive, and not always required. For now, the path and query are separated, but the query is never parsed, and instead stored in its raw URL-encoded form. Calling request.parseQuery() will populate a hash map in the request.&lt;/p&gt;&#xA;&lt;h3 id=&#34;parsing-headers&#34;&gt;Parsing headers&lt;/h3&gt;&#xA;&lt;p&gt;Here, we simply read the headers line-by-line, parsing them using a scalar search for &amp;lsquo;:&amp;rsquo; which is the delimiter for the key-value pair. The header is simply a struct with key and value fields. If you&amp;rsquo;re looking at &lt;a href=&#34;https://github.com/intob/peregrine/blob/main/src/header.zig&#34;&gt;./src/header.zig&lt;/a&gt; and wondering why Header contains fixed-sized arrays and length fields&amp;hellip; Requests are pre-allocated with a fixed-sized array of 32 Headers. This means that we don&amp;rsquo;t need to allocate headers for each request.&lt;/p&gt;&#xA;&lt;p&gt;Some of the most important optimisations can manifest as quite ugly &amp;lsquo;hacky&amp;rsquo; tricks. Such is the case for the Connection header. The Connection header is a little special, because it isn&amp;rsquo;t really for an application/library user to think about, but a client request for the server to handle the connection in a specific way. The value is normally either &amp;ldquo;keep-alive&amp;rdquo; or &amp;ldquo;close&amp;rdquo;. The server may still choose to close the connection, for example, if the limit of connection requests has been reached. Therefore, when we respond to a HTTP/1.1 request, we most often need to evaluate the Connection header. Initially, I was searching for the Connection header while generating the response, but I noticed that we could save a few nanoseconds by moving this into the header parsing step, as we&amp;rsquo;re already iterating over the headers. This was as simple as adding a boolean flag to the request &lt;code&gt;keep_alive&lt;/code&gt;. Looking at the implementation at time of writing, it&amp;rsquo;s so ugly that I don&amp;rsquo;t want to dump the code here! If you want, check it out in the parseHeaders method in &lt;a href=&#34;https://github.com/intob/peregrine/blob/main/src/reader.zig&#34;&gt;./src/reader.zig&lt;/a&gt;. Basically, we do a quick process of elimination by raw length, before checking if the value starts with &amp;lsquo;cl&amp;rsquo; for &amp;lsquo;close&amp;rsquo;. Notice the &lt;code&gt;conn_header_found&lt;/code&gt; flag in the method to prevent wasted operations after we&amp;rsquo;ve found it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;simd-for-readerreadline&#34;&gt;SIMD for &lt;code&gt;reader.readLine&lt;/code&gt;&lt;/h3&gt;&#xA;&lt;p&gt;How could I forget? &lt;code&gt;reader.readLine(fd: posix.socket_t)&lt;/code&gt; is right in the critical path. See &lt;a href=&#34;https://github.com/intob/peregrine/blob/main/src/reader.zig&#34;&gt;./src/reader.zig&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;One important optimisation here is the use of SIMD (vectors/chunks) when searching for line-endings. Rather than check byte-by-byte if we have a new-line character, we can leverage the SIMD (single-instruction-multiple-data) support of modern CPUs. This allows us to fill a mask with new-line characters, and check whether each 16-byte chunk contains the new-line. If it does, we simply call the built-in @ctz (count trailing zeros) to calculate its offset (position) in the chunk.&lt;/p&gt;&#xA;&lt;p&gt;For any remaining bytes (if the line-length is not divisible by 16), we simply iterate over them byte-by-byte.&lt;/p&gt;&#xA;&lt;p&gt;The use of SIMD is often integral to efficient data processing, and you will see it a lot.&lt;/p&gt;&#xA;&lt;h2 id=&#34;step-3-write-iovecs&#34;&gt;Step 3: Write iovecs&lt;/h2&gt;&#xA;&lt;p&gt;Finally for this article, when the &lt;a href=&#34;https://github.com/intob/peregrine/blob/main/src/worker.zig&#34;&gt;./src/worker.zig&lt;/a&gt; responds to a request, making a syscall to write each part of the response, or concatenating the response status line, headers, and body buffers, we can make use of &lt;code&gt;posix.writev&lt;/code&gt; to write a slice of iovecs (IO vectors) with a single syscall. This is much more efficient. Thanks to Karl Seguin for his &lt;a href=&#34;https://www.openmymind.net/TCP-Server-In-Zig-Part-3-Minimizing-Writes-and-Reads/&#34;&gt;guide to writing iovecs in Zig&lt;/a&gt;. I&amp;rsquo;d like to reiterate just how helpful I&amp;rsquo;ve found his guides, so thank you Karl.&lt;/p&gt;&#xA;&lt;h2 id=&#34;wrap-up&#34;&gt;Wrap up&lt;/h2&gt;&#xA;&lt;p&gt;I hope you found some of this interesting. I quite enjoy writing these, as it&amp;rsquo;s a good way for me to reflect on some of the decisions that I&amp;rsquo;ve made while. While writing this, I even found a couple of little things that I can improve.&lt;/p&gt;&#xA;&lt;p&gt;Thanks to Bo (author of Facil.io), for his advice for optimising the server implementation. Facil.io still outperforms Peregrine by around 10%, and shows better stability of response times under load. I think that this is due to Bo&amp;rsquo;s superior solution for distributing and scheduling the computation.&lt;/p&gt;&#xA;&lt;p&gt;Today, I implemented a fairly performant ring buffer queue (50ns for one read and one write). Maybe I can still improve this by a factor of 2, as a hardware lock should only take a single cycle.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; std &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @import(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;std&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RingQueue&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;comptime&lt;/span&gt; T: &lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; Self &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @This();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        allocator: std.mem.Allocator,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        buffer: []T,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        read_index: &lt;span style=&#34;color:#66d9ef&#34;&gt;usize&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        write_index: &lt;span style=&#34;color:#66d9ef&#34;&gt;usize&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        mutex: std.Thread.Mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; .{},&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        not_empty: std.Thread.Condition &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; .{},&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        not_full: std.Thread.Condition &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; .{},&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;init&lt;/span&gt;(allocator: std.mem.Allocator, capacity: &lt;span style=&#34;color:#66d9ef&#34;&gt;usize&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;!*&lt;/span&gt;Self {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; self &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; allocator.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(Self);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self&lt;span style=&#34;color:#f92672&#34;&gt;.*&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; .{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .allocator &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; allocator,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .buffer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; allocator.&lt;span style=&#34;color:#a6e22e&#34;&gt;alloc&lt;/span&gt;(T, capacity),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; self;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;deinit&lt;/span&gt;(self: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;Self) &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self.allocator.&lt;span style=&#34;color:#a6e22e&#34;&gt;free&lt;/span&gt;(self.buffer);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self.allocator.&lt;span style=&#34;color:#a6e22e&#34;&gt;destroy&lt;/span&gt;(self);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isFull&lt;/span&gt;(self: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;Self) &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (self.write_index &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; self.buffer.len &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; self.read_index;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isEmpty&lt;/span&gt;(self: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;Self) &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; self.write_index &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; self.read_index;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(self: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;Self, data: T) &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self.mutex.&lt;span style=&#34;color:#a6e22e&#34;&gt;lock&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;defer&lt;/span&gt; self.mutex.&lt;span style=&#34;color:#a6e22e&#34;&gt;unlock&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (self.&lt;span style=&#34;color:#a6e22e&#34;&gt;isFull&lt;/span&gt;()) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                self.not_full.&lt;span style=&#34;color:#a6e22e&#34;&gt;wait&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;self.mutex);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self.buffer[self.write_index] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; data;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self.write_index &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (self.write_index &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; self.buffer.len;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self.not_empty.&lt;span style=&#34;color:#a6e22e&#34;&gt;signal&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;(self: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;Self) T {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self.mutex.&lt;span style=&#34;color:#a6e22e&#34;&gt;lock&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;defer&lt;/span&gt; self.mutex.&lt;span style=&#34;color:#a6e22e&#34;&gt;unlock&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (self.&lt;span style=&#34;color:#a6e22e&#34;&gt;isEmpty&lt;/span&gt;()) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                self.not_empty.&lt;span style=&#34;color:#a6e22e&#34;&gt;wait&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;self.mutex);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self.buffer[self.read_index];&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self.read_index &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (self.read_index &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; self.buffer.len;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self.not_full.&lt;span style=&#34;color:#a6e22e&#34;&gt;signal&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; data;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This queue will make it possible to distribute work (requests to handle) to a thread pool on a FIFO basis. This would prevent long-running handlers from blocking an entire worker, which could block multiple connections.&lt;/p&gt;&#xA;&lt;p&gt;I also learned that Zig supports async/await as this could be an even better way for each worker in the pool to handle multiple requests concurrently.&lt;/p&gt;&#xA;&lt;p&gt;Peace &amp;amp; love!&#xA;J&lt;/p&gt;&#xA;</content:encoded>
    </item>
    <item>
      <title>The beginning of project Peregrine - a bleeding fast HTTP server</title>
      <link>https://rmrf.uk/cs/2024-12-26-starting-peregrine/</link>
      <pubDate>Thu, 26 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://rmrf.uk/cs/2024-12-26-starting-peregrine/</guid>
      <category>Computer Broscience</category>
      <description>As a way for me to dive deeper into Zig, I'm writing a HTTP server from scratch.</description>
      <content:encoded>&lt;p&gt;If you&amp;rsquo;re very familiar with Zig, then you&amp;rsquo;ll know that the standard library HTTP server is not written to be highly performant.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/zigzap/zap&#34;&gt;Zap&lt;/a&gt; is an alternative. Zap is built on the fantastic &lt;a href=&#34;https://facil.io&#34;&gt;Facil.io&lt;/a&gt; networking library written in C.&lt;/p&gt;&#xA;&lt;p&gt;Now, while there are plenty of fast HTTP servers, at time of writing, I couldn&amp;rsquo;t find a production-ready high-performance HTTP server written purely in Zig. There is &lt;a href=&#34;https://github.com/karlseguin/http.zig&#34;&gt;http.zig&lt;/a&gt;, but according to Karl, the developer, it must sit behind a reverse-proxy because it&amp;rsquo;s not resilient to DoS attack.&lt;/p&gt;&#xA;&lt;p&gt;As I need a real project to get deeper into the language, and having never written a HTTP server from scratch before, I felt that this would be a great opportunity to have fun learning about Zig, async IO, vectored IO, memory alignment, zero-alloc, zero-copy and a bunch of other systems stuff. I&amp;rsquo;ve also found that working with constraints (such as following a spec) is great for forcing one to find a solution, rather than skirting around an issue. Also, writing a library is a particularly good way to learn a language because it forces you to write reusable (and usable) software.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/intob/peregrine&#34;&gt;Peregrine - a bleeding fast HTTP server&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;My goal with this project is to write a robust, performant and simple HTTP server. In doing so, I will learn much of the Zig language, and a bunch about systems programming.&lt;/p&gt;&#xA;&lt;p&gt;I started this project on 2024-12-24, two days ago at time of writing. So how far have I got in two days?&lt;/p&gt;&#xA;&lt;p&gt;Currently, we have the main components of the server implemented. The server is written in a library form, and exposes a clean API to create a server and handle requests.&lt;/p&gt;&#xA;&lt;p&gt;There is a worker pool. Each worker runs on its own thread. For now, connecting sockets are assigned to a worker using round-robin distribution. This means that we don&amp;rsquo;t need a scheduler.&lt;/p&gt;&#xA;&lt;p&gt;Kqueue is the FreeBSD/MacOS solution for async IO. It&amp;rsquo;s a kernel event system. Basically, you register interest in a type of event. You then process the events. We have one of these event buses for our server&amp;rsquo;s main thread, where we accept connections. Each connecting socket is then assigned to a worker by registering interest in the client socket&amp;rsquo;s events with the worker&amp;rsquo;s event bus. This means that when clients write to the socket, the appropriate worker is notified and will process the event. In this case, the worker processing the event means reading from the socket.&lt;/p&gt;&#xA;&lt;p&gt;For the Linux kernel, there is a different solution, called epoll. In some ways Kqueue is superior to epoll because Kqueue allows registering interest in multiple events with a single system call. Conversely, with epoll, each interest must be registered with separate sys calls. In our case, this won&amp;rsquo;t make any difference because we make a sys call every time we accept a new connection. If we didn&amp;rsquo;t, clients would have to wait until their socket was registered. Otherwise, epoll is very similar to kqueue, as far as I know. In the next days, I will implement epoll to support Linux.&lt;/p&gt;&#xA;&lt;p&gt;When each worker starts, it does all of its heap allocations that it needs for normal operation. Ideally, no further heap allocations will be made per-request. This zero-alloc approach greatly improves performance because we don&amp;rsquo;t need to wait for memory to be allocated on the heap before progressing with processing each request.&lt;/p&gt;&#xA;&lt;p&gt;Each worker can handle one request at a time. This constraint allows us to reuse the buffers and data structures that it allocates. The only thing we have to ensure is that they are properly reset before reuse.&lt;/p&gt;&#xA;&lt;p&gt;Getting deeper into the guts of the server, each worker creates a request reader. This request reader reads from a given socket, and maintains a buffer of read-but-unprocessed data. When we call readRequest, unprocessed data in the buffer will be read before compacting the buffer and making another syscall to get more data. This approach means that fewer syscalls are used to read each request, while not needing a particularly large buffer.&lt;/p&gt;&#xA;&lt;p&gt;One optimisation that I&amp;rsquo;ve made already is that buffers are properly aligned. This means that the data&amp;rsquo;s memory address is a multiple of its size. Memory alignment is crucial for high-performance software because the CPU accesses memory most efficiently when data is naturally aligned. This allows the processor to fetch memory in a single operation, whereas misaligned data may require multiple accesses. Additionally, aligned data structures help to optimise cache efficiency. When data crosses cache line boundaries, it can result in additional cache misses, resulting in performance degradation.&lt;/p&gt;&#xA;&lt;p&gt;Below is the critical path of the worker&amp;rsquo;s event loop. Each kernel event is processed as follows.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ./src/worker.zig&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handleKevent&lt;/span&gt;(self: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;Worker, socket: posix.socket_t, reader: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;request.RequestReader) &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;defer&lt;/span&gt; posix.&lt;span style=&#34;color:#a6e22e&#34;&gt;close&lt;/span&gt;(socket);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    self.req.&lt;span style=&#34;color:#a6e22e&#34;&gt;reset&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; reader.&lt;span style=&#34;color:#a6e22e&#34;&gt;readRequest&lt;/span&gt;(socket, self.req);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    self.resp.&lt;span style=&#34;color:#a6e22e&#34;&gt;reset&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    self.&lt;span style=&#34;color:#a6e22e&#34;&gt;on_request&lt;/span&gt;(self.req, self.resp);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;self.resp.hijacked) &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; self.&lt;span style=&#34;color:#a6e22e&#34;&gt;respond&lt;/span&gt;(socket);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;respond&lt;/span&gt;(self: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;Worker, socket: posix.socket_t) &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; headers_len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; self.resp.&lt;span style=&#34;color:#a6e22e&#34;&gt;serialiseHeaders&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;self.resp_buf);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (self.resp.body_len &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; iovecs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [_]posix.iovec_const{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .{ .base &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @ptrCast(self.resp_buf[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt;headers_len]), .len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; headers_len },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .{ .base &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @ptrCast(self.resp.body[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt;self.resp.body_len]), .len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self.resp.body_len },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; written &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; posix.&lt;span style=&#34;color:#a6e22e&#34;&gt;writev&lt;/span&gt;(socket, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;iovecs);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (written &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; headers_len &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; self.resp.body_len) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;.WriteError;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;writeAll&lt;/span&gt;(socket, self.resp_buf[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt;headers_len]);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One optimisation you can see above is in the respond method. This is called after the user&amp;rsquo;s on_request handler is called, providing that they did not hijack the response. More on that later. If the response has a body, vectored IO is used to write the headers and body in a single syscall, without needing to concatenate the buffers. If there is no body, the response is written by repeatedly writing to the socket until all bytes are written. Without a body, this is likely to be achieved in a single syscall.&lt;/p&gt;&#xA;&lt;p&gt;Making use of x86&amp;rsquo;s SIMD and ARM&amp;rsquo;s NEON capabilities allow us to speed up a variety of operations. One example that you will see often is chunking. This is where slices are processed in chunks, rather than operating on single bytes. This is important because operating on single bytes does not make good use of the cache. A good example of this is in Zig&amp;rsquo;s stdlib: &lt;code&gt;std.mem.indexOfScalarPos&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This searches a slice for a specific value. Normally this would be a slice of bytes, and we&amp;rsquo;d be searching for a byte. If we were to search the slice byte-by-byte, the CPU would have to individually load each byte into a register for comparison. In &lt;code&gt;std.mem.indexOfScalarPos&lt;/code&gt;, you can see how a bit mask is used to efficiently test if the target byte is somewhere in the chunk.&#xA;The naive and much slower implementation would be as follows:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;indexOfPos&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;comptime&lt;/span&gt; T: &lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt;, slice []&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; T, start_index: &lt;span style=&#34;color:#66d9ef&#34;&gt;usize&lt;/span&gt;, value T) ?&lt;span style=&#34;color:#66d9ef&#34;&gt;usize&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (start_index &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; slice.len) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; i: &lt;span style=&#34;color:#66d9ef&#34;&gt;usize&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; start_index;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; slice.len) : (i &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (slice[i] &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; value) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; i;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Often, adding some room for complexity allows us to solve problems more efficiently. Good engineering is partly about balancing performance gains with simplicity. A good engineer knows what to optimise, and what not to. Tight loops in critical paths tend to offer the greatest reward for optimisation.&lt;/p&gt;&#xA;&lt;p&gt;Anyway, hopefully that was interesting. After a couple of days, we have the bare bones of a high-performance HTTP server, written purely in Zig. Considering that I&amp;rsquo;m new to systems programming and Zig, I feel that I&amp;rsquo;m doing alright. It surely is a very fun and expressive language. I&amp;rsquo;m enjoying the build speed and the quality of the compiler&amp;rsquo;s errors.&lt;/p&gt;&#xA;&lt;p&gt;Stay tuned for my progress, as I will be working on this to make it production-ready. I&amp;rsquo;m not sure how long it will take, but I am sure that I&amp;rsquo;ll continue learning a lot.&lt;/p&gt;&#xA;&lt;p&gt;Peace and love!&#xA;J&lt;/p&gt;&#xA;</content:encoded>
    </item>
    <item>
      <title>First thoughts on Zig</title>
      <link>https://rmrf.uk/cs/2024-12-23-zig-first-thoughts/</link>
      <pubDate>Mon, 23 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://rmrf.uk/cs/2024-12-23-zig-first-thoughts/</guid>
      <category>Computer Broscience</category>
      <description>I describe what I like about the Zig systems programming language, and what I don't like after a couple of weeks of tinkering.</description>
      <content:encoded>&lt;p&gt;I&amp;rsquo;ve done very little systems programming. I wrote Conway&amp;rsquo;s Game of Life in C as a teenager. I also did a bit of assembly at GE Aviation Systems. A few years ago, I completed the Rust programming book, but struggled with the language beyond that, and never grew to love it.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m currently diving quite deep into systems programming in Zig. While there are many things I love about the language, namely its elegant approach to generics and meta-programming, I do feel that the language is lacking in some ways.&lt;/p&gt;&#xA;&lt;h2 id=&#34;simplicity&#34;&gt;Simplicity&lt;/h2&gt;&#xA;&lt;p&gt;What I like most about the language is its simplicity. Similar to Go in this regard, it didn&amp;rsquo;t take me long to become comfortable with the syntax.&lt;/p&gt;&#xA;&lt;h2 id=&#34;errors-lack-context&#34;&gt;Errors lack context&lt;/h2&gt;&#xA;&lt;p&gt;One of the most obvious flaws with the language is its error type. It&amp;rsquo;s impossible to add context or details to errors. In contrast, an error in Go for example, is just a value. This allows you to add context as the error is passed up the call stack.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not sure if Zig will offer a solution for this.&lt;/p&gt;&#xA;&lt;p&gt;Despite this, errors in Zig are otherwise great, and very easy to handle or throw up the stack.&lt;/p&gt;&#xA;&lt;h2 id=&#34;powerful-build-system&#34;&gt;Powerful build system&lt;/h2&gt;&#xA;&lt;p&gt;Zig exposes a very powerful yet usable build API. Linking C libraries is quite effortless, and I&amp;rsquo;m a noob to this.&lt;/p&gt;&#xA;&lt;p&gt;While I&amp;rsquo;m not yet familiar with much of the build system, I have the feeling that it is probably the best build system that I&amp;rsquo;ve seen yet. For me, it was also the hardest part of Zig to become comfortable with. I&amp;rsquo;m still not really comfortable with it. In addition, as the language is so young and volatile, much of the 3rd party documentation for the build system is out of date.&lt;/p&gt;&#xA;&lt;p&gt;Fortunately, the API is very clean, and so it&amp;rsquo;s not so difficult to figure things out on your own.&lt;/p&gt;&#xA;&lt;h2 id=&#34;lack-of-interfaces&#34;&gt;Lack of interfaces&lt;/h2&gt;&#xA;&lt;p&gt;Zig&amp;rsquo;s philosophy is to provide low-level building blocks, rather than high-level abstractions. As such, the Zig team felt that interfaces are an unnecessary abstraction.&lt;/p&gt;&#xA;&lt;p&gt;While I understand this view, I feel that the language&amp;rsquo;s lack of built-in support for interfaces can make interface implementations feel quite clunky. This became particularly apparent to me when I began using the standard library&amp;rsquo;s filesystem types. I&amp;rsquo;m sure that this could be greatly improved as the language server matures.&lt;/p&gt;&#xA;&lt;h2 id=&#34;expressive-language&#34;&gt;Expressive language&lt;/h2&gt;&#xA;&lt;p&gt;I am often surprised by Zig because it tends to work exactly as you&amp;rsquo;d hope. This is particularly noticeable when implementing polymorphic types and platform-dependent behaviour. This makes writing cross-platform software remarkably easy. For me, Zig really shines in this regard.&lt;/p&gt;&#xA;&lt;h2 id=&#34;generics-are-beautiful&#34;&gt;Generics are beautiful&lt;/h2&gt;&#xA;&lt;p&gt;Thanks to Zig&amp;rsquo;s expressive compile-time metaprogramming, generics are very easy to implement, and are perfectly type-safe.&lt;/p&gt;&#xA;&lt;p&gt;The following is a generic thread-safe message queue:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; std &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @import(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;std&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Queue&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;comptime&lt;/span&gt; T: &lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; Node &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; { data: T, next: ?&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;Node };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        mutex: std.Thread.Mutex,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        head: ?&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;Node,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        tail: ?&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;Node,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        allocator: std.mem.Allocator,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;init&lt;/span&gt;(allocator: std.mem.Allocator) &lt;span style=&#34;color:#f92672&#34;&gt;!*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Queue&lt;/span&gt;(T) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; queue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; allocator.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;Queue&lt;/span&gt;(T));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            queue&lt;span style=&#34;color:#f92672&#34;&gt;.*&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; .{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; std.Thread.Mutex{},&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .head &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .tail &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .allocator &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; allocator,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; queue;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;deinit&lt;/span&gt;(self: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Queue&lt;/span&gt;(T)) &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self.mutex.&lt;span style=&#34;color:#a6e22e&#34;&gt;lock&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;defer&lt;/span&gt; self.mutex.&lt;span style=&#34;color:#a6e22e&#34;&gt;unlock&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (self.head) &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;node&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; next &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; node.next;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                self.allocator.&lt;span style=&#34;color:#a6e22e&#34;&gt;destroy&lt;/span&gt;(node);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                self.head &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; next;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self.tail &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(self: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Queue&lt;/span&gt;(T), data: T) &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; new_node &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; self.allocator.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(Node);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            new_node&lt;span style=&#34;color:#f92672&#34;&gt;.*&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; .{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; data,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .next &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self.mutex.&lt;span style=&#34;color:#a6e22e&#34;&gt;lock&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;defer&lt;/span&gt; self.mutex.&lt;span style=&#34;color:#a6e22e&#34;&gt;unlock&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (self.tail) &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;tail&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                tail.next &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; new_node;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                self.tail &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; new_node;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                self.head &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; new_node;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                self.tail &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; new_node;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;pub&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;(self: &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Queue&lt;/span&gt;(T)) ?T {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self.mutex.&lt;span style=&#34;color:#a6e22e&#34;&gt;lock&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;defer&lt;/span&gt; self.mutex.&lt;span style=&#34;color:#a6e22e&#34;&gt;unlock&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (self.head) &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;node&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; node.data;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                self.head &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; node.next;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (self.head &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    self.tail &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                self.allocator.&lt;span style=&#34;color:#a6e22e&#34;&gt;destroy&lt;/span&gt;(node);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; data;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;test&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;basic usage&amp;#34;&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; gpa &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; std.heap.&lt;span style=&#34;color:#a6e22e&#34;&gt;GeneralPurposeAllocator&lt;/span&gt;(.{}){};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; allocator &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; gpa.&lt;span style=&#34;color:#a6e22e&#34;&gt;allocator&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; queue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Queue&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;u8&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;init&lt;/span&gt;(allocator);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; queue.&lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; queue.&lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; queue.&lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; std.testing.&lt;span style=&#34;color:#a6e22e&#34;&gt;expect&lt;/span&gt;(queue.&lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; std.testing.&lt;span style=&#34;color:#a6e22e&#34;&gt;expect&lt;/span&gt;(queue.&lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; std.testing.&lt;span style=&#34;color:#a6e22e&#34;&gt;expect&lt;/span&gt;(queue.&lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; std.testing.&lt;span style=&#34;color:#a6e22e&#34;&gt;expect&lt;/span&gt;(queue.&lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In Go, I very rarely found myself using generics. Generics in Go feel like an after-thought, and that&amp;rsquo;s probably because they are.&lt;/p&gt;&#xA;&lt;p&gt;Conversely, in Zig, I&amp;rsquo;ve used this comptime struct pattern many times to write reusable components.&lt;/p&gt;&#xA;&lt;h2 id=&#34;standard-library-is-still-rough-around-the-edges&#34;&gt;Standard library is still rough around the edges&lt;/h2&gt;&#xA;&lt;p&gt;Some packages in the standard library are not entirely production-ready. For example, the Blake3 implementation uses the reference rather than the optimised implementation.&lt;/p&gt;&#xA;&lt;p&gt;There are faster and more robust HTTP servers than the one in the standard library, namely &lt;a href=&#34;https://github.com/zigzap/zap&#34;&gt;Zap&lt;/a&gt; built on facil.io (written in C).&lt;/p&gt;&#xA;&lt;p&gt;As Zig is still young, it seems only natural that the standard library is not yet polished. Obviously, the language should mature and stabilise before polishing the standard library can become a priority.&lt;/p&gt;&#xA;&lt;h2 id=&#34;memory-management-made-easy&#34;&gt;Memory management made easy&lt;/h2&gt;&#xA;&lt;p&gt;When I learned Rust (or tried to), I never became comfortable with it, and I was constantly fighting the compiler.&lt;/p&gt;&#xA;&lt;p&gt;Possibly, I&amp;rsquo;m simply a better programmer these days, but I find it more likely that Zig&amp;rsquo;s approach to memory management is just simpler and more explicit.&lt;/p&gt;&#xA;&lt;p&gt;Zig is not a fancy language, it really does feel close to C. I feel that this inherently makes it easier to learn than Rust.&lt;/p&gt;&#xA;&lt;p&gt;Mostly writing Go recently, I expected that Zig&amp;rsquo;s memory management would be a really big hurdle. While it has taken me a few days to become comfortable allocating memory on the heap, I was surprised how easy I found it.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m still not fully familiar with each of the standard library&amp;rsquo;s allocators, and I mostly use the GeneralPurposeAllocator.&lt;/p&gt;&#xA;&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;&#xA;&lt;p&gt;I&amp;rsquo;m only a couple of weeks into my journey with Zig.&lt;/p&gt;&#xA;&lt;p&gt;Would I use Zig for work? If the project would benefit from optimal performance and reliability, absolutely. For bits such as tooling for managing infrastructure or automated key rotation, where performance is not a concern, I&amp;rsquo;d likely opt for Go. At time of writing, Zig&amp;rsquo;s ecosystem is still very small compared to Go&amp;rsquo;s.&lt;/p&gt;&#xA;&lt;p&gt;My reason for beginning with Zig was to get into systems programming, and I&amp;rsquo;m really enjoying it. I remember faffing with header files in C as a teenager, and I know that building C projects can be a real pain. So far I&amp;rsquo;m quite happy with Zig, and I&amp;rsquo;m optimistic that a lot of the kinks will be ironed out as the language matures.&lt;/p&gt;&#xA;&lt;p&gt;There will never be a perfect language for everyone for all use-cases. As I&amp;rsquo;ve been learning Zig, I&amp;rsquo;ve come to appreciate how hard it is to design a language that is fit for decades of mission-critical software engineering. It&amp;rsquo;s incredible how good C is, despite its (lack of) build system. I&amp;rsquo;ve also come to realise how much of Go they got right. Writing concurrent software in Zig has left me with an appreciation for Go&amp;rsquo;s channels. The only thing that I would change about Go is the verbose error handling.&lt;/p&gt;&#xA;&lt;p&gt;I feel that learning a new programming language allows us to open our minds to alternative paradigms. Questioning what we already know is fundamental to making progress.&lt;/p&gt;&#xA;</content:encoded>
    </item>
    <item>
      <title>Comparing the performance of a de Bruijn sequence against a simple lookup table</title>
      <link>https://rmrf.uk/cs/2024-05-17-zerobitperf/</link>
      <pubDate>Fri, 17 May 2024 00:00:00 +0000</pubDate>
      <guid>https://rmrf.uk/cs/2024-05-17-zerobitperf/</guid>
      <category>Computer Broscience</category>
      <description>Precomputed de Bruijn sequences are cool, but are they faster than simple lookup tables?</description>
      <content:encoded>&lt;p&gt;While optimising the naive proof-of-work algorithm in my dave protocol implementation, I compared the efficiency of some methods of counting the number of leading zero bits in a byte slice.&lt;/p&gt;&#xA;&lt;h1 id=&#34;approaches&#34;&gt;Approaches&lt;/h1&gt;&#xA;&lt;h2 id=&#34;1-naive-approach&#34;&gt;1. Naive approach&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;nzerobit_1&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt; []&lt;span style=&#34;color:#66d9ef&#34;&gt;byte&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &amp;lt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Iterating over each byte in the given key, we use the bitwise AND operator &lt;code&gt;&amp;amp;&lt;/code&gt; to check if the &lt;code&gt;i&lt;/code&gt;-th bit of the byte is zero. The expression &lt;code&gt;1&amp;lt;&amp;lt;i&lt;/code&gt; creates a bitmask with only the &lt;code&gt;i&lt;/code&gt;-th bit set to 1, and the &lt;code&gt;&amp;amp;&lt;/code&gt; operator performs a bitwise AND operation between the byte and the bitmask. If the result is zero, it means that the &lt;code&gt;i&lt;/code&gt;-th bit is zero, and we increment &lt;code&gt;n&lt;/code&gt;. If the bit is set, we return the current value of &lt;code&gt;n&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;2-right-shift&#34;&gt;2. Right shift&lt;/h2&gt;&#xA;&lt;p&gt;Instead of using &lt;code&gt;b&amp;amp;(1&amp;lt;&amp;lt;i) == 0&lt;/code&gt;, we can use &lt;code&gt;(b&amp;gt;&amp;gt;i)&amp;amp;1 == 0&lt;/code&gt;, avoiding the expensive left shift operation. This should yield a small performance improvement.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;nzerobit_2&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt; []&lt;span style=&#34;color:#66d9ef&#34;&gt;byte&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &amp;lt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;3-unroll-loop&#34;&gt;3. Unroll loop&lt;/h2&gt;&#xA;&lt;p&gt;Loops are expensive. Loop unrolling can help reduce overhead, and enable better instruction-level parallelism.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;nzerobit_3&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt; []&lt;span style=&#34;color:#66d9ef&#34;&gt;byte&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;4-simple-lookup-table&#34;&gt;4. Simple lookup table&lt;/h2&gt;&#xA;&lt;p&gt;We can create a lookup table storing the number of leading zero bits for every possible byte value.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;zeros&lt;/span&gt; = [&lt;span style=&#34;color:#ae81ff&#34;&gt;256&lt;/span&gt;]&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;nzerobit_4&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt; []&lt;span style=&#34;color:#66d9ef&#34;&gt;byte&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;zeros&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While simple, this approach can be significantly faster for small input sizes. As my use case is counting the number of leading zero bits in a 256 bit hash, this method could yield great results.&lt;/p&gt;&#xA;&lt;h2 id=&#34;5-de-bruijn-sequence&#34;&gt;5. De Bruijn sequence&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;debruijn64&lt;/span&gt; = [&lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;]&lt;span style=&#34;color:#66d9ef&#34;&gt;byte&lt;/span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;53&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;54&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;27&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;38&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;41&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;34&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;55&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;48&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;28&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;62&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;39&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;46&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;44&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;42&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;22&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;24&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;35&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;59&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;56&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;49&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;18&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;29&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;63&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;52&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;26&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;37&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;33&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;47&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;61&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;45&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;43&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;21&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;23&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;58&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;51&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;25&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;36&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;57&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;31&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;19&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;13&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;nzerobit_5&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt; []&lt;span style=&#34;color:#66d9ef&#34;&gt;byte&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; uint64(&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0x03f79d71b4ca8b09&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;58&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; int(&lt;span style=&#34;color:#a6e22e&#34;&gt;debruijn64&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;])&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; len(&lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The debruijn64 array is a precomputed lookup table that maps the position of the least significant set bit in a byte to the number of trailing zeros.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;i*8&lt;/code&gt; gives the number of zero bits in the previous bytes. &lt;code&gt;b&amp;amp;-b&lt;/code&gt; isolates the least significant set bit in the current byte. &lt;code&gt;uint64(b&amp;amp;-b)*0x03f79d71b4ca8b09&lt;/code&gt; multiplies the isolated bit with a magic constant to generate a unique index into the table. &lt;code&gt;&amp;gt;&amp;gt;58&lt;/code&gt; shifts the result to the right by 58 bits to bring the relevant bits into the least significant position. The looked-up value represents the number of trailing zeros in the current byte, which is added to &lt;code&gt;i*8&lt;/code&gt; to get the total number of leading zero bits.&lt;/p&gt;&#xA;&lt;p&gt;The de Bruijn sequence lookup table provides a constant-time method to determine the number of trailing zeros in a byte. By combining this with the byte index, the function efficiently calculates the total number of leading zero bits in the byte slice.&lt;/p&gt;&#xA;&lt;h1 id=&#34;benchmarks&#34;&gt;Benchmarks&lt;/h1&gt;&#xA;&lt;p&gt;The following results are from a 12-core Apple M2 Pro.&lt;/p&gt;&#xA;&lt;p&gt;The simple lookup and de Bruijn sequence are the clear winners, regardless of byte length and number of leading zero bytes. As there is no clear winner between these two, I suggest running tests for your specific use case.&lt;/p&gt;&#xA;&lt;h2 id=&#34;16-bytes-4-leading-zero-bytes&#34;&gt;16 bytes, 4 leading zero bytes&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Benchmark_1-12    &#x9;53595952        22.68 ns/op&#xA;Benchmark_2-12    &#x9;54691712        22.68 ns/op&#xA;Benchmark_3-12    &#x9;76803686        15.74 ns/op&#xA;Benchmark_4-12    &#x9;421736720       2.861 ns/op&#xA;Benchmark_5-12    &#x9;423180349       2.864 ns/op&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;32-bytes-2-leading-zero-bytes&#34;&gt;32 bytes, 2 leading zero bytes&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Benchmark_1-12    &#x9;80881609        14.65 ns/op&#xA;Benchmark_2-12    &#x9;86697114        13.87 ns/op&#xA;Benchmark_3-12    &#x9;100000000       11.22 ns/op&#xA;Benchmark_4-12    &#x9;577513431       2.087 ns/op&#xA;Benchmark_5-12    &#x9;570226458       2.076 ns/op&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;32-bytes-4-leading-zero-bytes&#34;&gt;32 bytes, 4 leading zero bytes&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Benchmark_1-12    &#x9;49990105        23.07 ns/op&#xA;Benchmark_2-12    &#x9;54375429        21.99 ns/op&#xA;Benchmark_3-12    &#x9;76524105        15.73 ns/op&#xA;Benchmark_4-12    &#x9;413261740       2.862 ns/op&#xA;Benchmark_5-12    &#x9;403608087       2.912 ns/op&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;32-bytes-8-leading-zero-bytes&#34;&gt;32 bytes, 8 leading zero bytes&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Benchmark_1-12    &#x9;34218296        35.23 ns/op&#xA;Benchmark_2-12    &#x9;35122378        34.62 ns/op&#xA;Benchmark_3-12    &#x9;62954121        19.30 ns/op&#xA;Benchmark_4-12    &#x9;315074923       3.810 ns/op&#xA;Benchmark_5-12    &#x9;315627609       3.805 ns/op&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;64-bytes-4-leading-zero-bytes&#34;&gt;64 bytes, 4 leading zero bytes&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Benchmark_1-12    &#x9;50546887        23.29 ns/op&#xA;Benchmark_2-12    &#x9;51958827        22.18 ns/op&#xA;Benchmark_3-12    &#x9;73563591        15.65 ns/op&#xA;Benchmark_4-12    &#x9;401217024       3.052 ns/op&#xA;Benchmark_5-12    &#x9;386922139       3.040 ns/op&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;128-bytes-4-leading-zero-bytes&#34;&gt;128 bytes, 4 leading zero bytes&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Benchmark_1-12    &#x9;53089023        22.98 ns/op&#xA;Benchmark_2-12    &#x9;53183925        22.37 ns/op&#xA;Benchmark_3-12    &#x9;74646594        16.12 ns/op&#xA;Benchmark_4-12    &#x9;142048627       8.512 ns/op&#xA;Benchmark_5-12    &#x9;142594008       8.442 ns/op&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;256-bytes-4-leading-zero-bytes&#34;&gt;256 bytes, 4 leading zero bytes&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Benchmark_1-12    &#x9;51616419        23.62 ns/op&#xA;Benchmark_2-12    &#x9;53396121        22.98 ns/op&#xA;Benchmark_3-12    &#x9;57543146        18.51 ns/op&#xA;Benchmark_4-12    &#x9;100000000       10.83 ns/op&#xA;Benchmark_5-12    &#x9;100000000       10.89 ns/op&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;256-bytes-64-leading-zero-bytes&#34;&gt;256 bytes, 64 leading zero bytes&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Benchmark_1-12    &#x9;5252018         231.0 ns/op&#xA;Benchmark_2-12    &#x9;5165072         231.0 ns/op&#xA;Benchmark_3-12    &#x9;12656263        97.03 ns/op&#xA;Benchmark_4-12    &#x9;57885492        20.43 ns/op&#xA;Benchmark_5-12    &#x9;58068614        20.46 ns/op&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&#34;repository&#34;&gt;Repository&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/intob/zerobitperf&#34;&gt;https://github.com/intob/zerobitperf&lt;/a&gt;&lt;/p&gt;&#xA;</content:encoded>
    </item>
    <item>
      <title>Dave</title>
      <link>https://rmrf.uk/cs/2024-05-01-dave/</link>
      <pubDate>Wed, 01 May 2024 00:00:00 +0000</pubDate>
      <guid>https://rmrf.uk/cs/2024-05-01-dave/</guid>
      <category>Computer Broscience</category>
      <description>Distributed KV store.</description>
      <content:encoded>&lt;p&gt;EDIT: In September &amp;amp; October I made significant changes to the DHT, and so much of this article is out of date. I have not yet found time to update this article, and I&amp;rsquo;m not currently working on the project.&lt;/p&gt;&#xA;&lt;p&gt;So&amp;hellip; what am I doing? When I started this project (first commit 2024-04-05), I didn&amp;rsquo;t know exactly, beyond designing a peer-to-peer network application that disseminates information. Now my vision is finally becoming clear, and I&amp;rsquo;m able to describe it.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m writing a peer-to-peer application that allows anyone, or any device, to read and write information without a value transaction of any kind. A writer pays in CPU cycles and electricity, while a reader pays in network participation.&lt;/p&gt;&#xA;&lt;p&gt;Simplicity, anonymity, efficiency, performance, and high availability are key aspects that I&amp;rsquo;m aiming for.&lt;/p&gt;&#xA;&lt;p&gt;Simplicity will come at the cost of pushing much application-specific complexity up the stack. We must be free to re-imagine a broken game, if it turns out to be broken&amp;hellip; At this time, I feel that this approach ensures a modular and upgradeable stack of protocols, while maximising the probability of success of the dave protocol.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m aware that such projects have been attempted and failed. I&amp;rsquo;m also aware that such a challenging project may not be an ideal start for a budding p2p network researcher. With that in mind, I do need a worthy challenge and vision to hold my interest.&lt;/p&gt;&#xA;&lt;h1 id=&#34;dave---distributed-kv-store&#34;&gt;Dave - distributed KV store&lt;/h1&gt;&#xA;&lt;p&gt;Dave is a protocol for continuously disseminating information in a decentralised, anonymous, and censorship-resistant manner, without the need for a native token or value transaction.&lt;/p&gt;&#xA;&lt;p&gt;New values are available nearly instantly, with sub-second lookup time. Old values have a significantly faster lookup time, because more nodes will likely have the data. Real-world speed depends on the bandwidth provisioned by the nodes. Most likely, the response time is better than average network ping, even with a naive implementation.&lt;/p&gt;&#xA;&lt;p&gt;Use-cases: Decentralised social media applications, serverless forms, communication layer for any decentralised application.&lt;/p&gt;&#xA;&lt;h2 id=&#34;design&#34;&gt;Design&lt;/h2&gt;&#xA;&lt;p&gt;The protocol is designed around a single message format with an enumerated operation code. There are 4 operation codes, as follows; GETPEER, PEER, PUT, GET.&lt;/p&gt;&#xA;&lt;p&gt;Execution is of a cyclic mode, with the minimum period defined by constant EPOCH. The program sends a uniform stream of packets, minimising packet loss and maximising efficiency. This allows the protocol to be adjusted safely, and in a way that could preserve interoperability with a network running variations for different bandwidth ideals &amp;amp; constraints.&lt;/p&gt;&#xA;&lt;h3 id=&#34;getpeer--peer-messages&#34;&gt;GETPEER &amp;amp; PEER Messages&lt;/h3&gt;&#xA;&lt;p&gt;These are the first two op-codes that I defined, and initially the only operations that the network performed. These two messages allow nodes on the network to discover peers, and to verify their availability.&lt;/p&gt;&#xA;&lt;p&gt;For every PING EPOCH, iterate over the peer table. If a peer is found that has not been seen in the last PING EPOCH, and has not been pinged within the last PING EPOCH, send to the peer a message with op-code GETPEER.&lt;/p&gt;&#xA;&lt;p&gt;A protocol-following peer will reply with op-code PEER, a message containing NPEER addresses for other peers.&lt;/p&gt;&#xA;&lt;p&gt;I often refer to these addresses as peer descriptors, as in future, they may not necessarily be IP addresses. I would like the possibility to cleanly implement interoperable transports. This would allow nodes to serve as bridges. As software-defined radio is a field of interest to me, I imagine that the dave protocol could used to create alternative low-energy networks that could bridge our existing infrastructure built on IP. Imagine a free and open internet that does not require an internet service provider, or any knowledge of its users. Know that I have not yet adjusted the protobuf specification to support this, as I prefer to focus on a building working proof over UDP for now.&lt;/p&gt;&#xA;&lt;p&gt;If a peer does not respond with any valid message, after DROP * EPOCH has elapsed, the peer is deleted from the peer table. Unresponsive peers are no-longer advertised after OPEN * EPOCH has elapsed without message, so as to ensure that unresponsive peers are not re-added from latent gossip.&lt;/p&gt;&#xA;&lt;h3 id=&#34;put-message&#34;&gt;PUT Message&lt;/h3&gt;&#xA;&lt;p&gt;A message with op-code PUT is a packet of data containing a Key, Value, Time, Salt, Work, Sig and PubKey. Salt and Work are outputs of the cost function, into which Key, Value and Time are passed.&lt;/p&gt;&#xA;&lt;p&gt;Every EPOCH, each node sends one randomly selected dat to one randomly selected peer.&lt;/p&gt;&#xA;&lt;p&gt;This ensures that information propagates and re-propagates through the network reliably, until it is eventually no-longer stored by any node. I describe the selection algorithm later.&lt;/p&gt;&#xA;&lt;p&gt;This provides a good level of anonymity for original senders, because it is virtually impossible to discern the origin of a dat, even with a broad view of network traffic.&lt;/p&gt;&#xA;&lt;p&gt;Anonymity is achieved by ensuring no correlation between the timing of a dat being received for the first time, and its eventual propagation to other nodes. This happens at random, but at a constant interval.&lt;/p&gt;&#xA;&lt;h3 id=&#34;get-message&#34;&gt;GET Message&lt;/h3&gt;&#xA;&lt;p&gt;A GET message is a packet with op-code GET, containing a Key. If the remote has the dat, the remote should reply with a message with op-code PUT, containing the data. A node may request the same dat from many peers simultaneously. This is up to the application to optimise, although the reference implementation should be pretty good.&lt;/p&gt;&#xA;&lt;h3 id=&#34;dat-selection-by-mass&#34;&gt;DAT Selection by Mass&lt;/h3&gt;&#xA;&lt;h4 id=&#34;proof-of-work&#34;&gt;Proof-of-work&lt;/h4&gt;&#xA;&lt;p&gt;A Salt is found, that when combined with the Key, Value &amp;amp; Time fields using a hash function, the output begins with some desired number of leading zero bits. The number of leading zeros (or some other constraint) probabilistically accurately reflects the energetic cost equivalent of computing the proof. This is commonly known as proof-of-work, and is well known for its use in Bitcoin mining.&lt;/p&gt;&#xA;&lt;p&gt;The number of leading zero bits is referred to as the &amp;ldquo;difficulty&amp;rdquo; throughout the remainder of this document.&lt;/p&gt;&#xA;&lt;p&gt;We compute a salty hash from an initial hash of key, value and time. For large values, this performs better than a single hash.&lt;/p&gt;&#xA;&lt;h5 id=&#34;work--blake3salt-blake3key-value-time&#34;&gt;work = blake3(salt, blake3(key, value, time))&lt;/h5&gt;&#xA;&lt;h4 id=&#34;mass-calculation&#34;&gt;Mass Calculation&lt;/h4&gt;&#xA;&lt;p&gt;As each machine has limited resources, we need a mechanism by which the software can select which dats should be stored, at the expense of others being dropped. In a peer-to-peer application without any central coordination or authority, each node must be able to decide which dats to keep on its own.&lt;/p&gt;&#xA;&lt;p&gt;So how can we give priority to some dats over others?&lt;/p&gt;&#xA;&lt;p&gt;As the cryptographic proof contains the value, and time, neither may be modified without invalidating the proof. As such, we can use the time, and the difficulty in the calculation of a score, referred to here as mass.&lt;/p&gt;&#xA;&lt;h5 id=&#34;mass--difficulty--1--millisecondssinceadded&#34;&gt;mass = difficulty * (1 / millisecondsSinceAdded)&lt;/h5&gt;&#xA;&lt;p&gt;The mass tends to zero over time. Dats with a harder proof of work persist longer in the network. In addition, difficulty scales exponentially (each added zero byte increases the difficulty by 256 times.&lt;/p&gt;&#xA;&lt;h3 id=&#34;peer-trust-mechanism&#34;&gt;Peer Trust Mechanism&lt;/h3&gt;&#xA;&lt;p&gt;A decentralised network depends on a trust system that incentivises fair play. It&amp;rsquo;s much more challenging than operating in a private network, as most web applications do today. So why do it? It allows us to build more powerful software that cannot be controlled by any single entity.&lt;/p&gt;&#xA;&lt;p&gt;The goal of the trust mechanism is to ensure that energy is not lost to malicious or protocol-deviating peers.&lt;/p&gt;&#xA;&lt;h4 id=&#34;earning-trust&#34;&gt;Earning Trust&lt;/h4&gt;&#xA;&lt;p&gt;Each time a packet is received containing a dat &lt;strong&gt;not already stored&lt;/strong&gt;, the remote peer&amp;rsquo;s trust value is incremented by the mass of the dat.&lt;/p&gt;&#xA;&lt;p&gt;If a peer is dropped, and then re-joins the network, they will begin with a trust score of zero.&lt;/p&gt;&#xA;&lt;p&gt;Trust scores are not gossiped, as this implies additional attack surface and complexity.&lt;/p&gt;&#xA;&lt;h4 id=&#34;use-of-trust&#34;&gt;Use of Trust&lt;/h4&gt;&#xA;&lt;p&gt;The trust score is weighed in to the random peer selection. A random threshold between the maximum trust score and zero is chosen. A peer with a trust score greater than the random threshold is selected at random.&lt;/p&gt;&#xA;&lt;p&gt;Therefore, peers with a higher trust score are more likely to be selected for gossip messages. This in turn increases the chance for the peer to learn of new dats earlier, reinforcing the pattern.&lt;/p&gt;&#xA;&lt;p&gt;In essence, the longer a peer (ip-port) remains in an other&amp;rsquo;s peer table, the higher the trust score will likely be, and therefore more bandwidth is allocated to that peer.&lt;/p&gt;&#xA;&lt;h2 id=&#34;resilience-to-attack&#34;&gt;Resilience to Attack&lt;/h2&gt;&#xA;&lt;h3 id=&#34;sybil-attack&#34;&gt;Sybil Attack&lt;/h3&gt;&#xA;&lt;p&gt;A Sybil attack is one where an attacker creates many peers that join the network. Failing to consider this would result in the network losing energy to these numerous byzantine nodes that are inexpensive to spawn.&lt;/p&gt;&#xA;&lt;h3 id=&#34;eclipse-attack&#34;&gt;Eclipse Attack&lt;/h3&gt;&#xA;&lt;p&gt;An Eclipse attack is one where an attacker attempts to poison the peer tables of peers that it discovers on the network. This poisoning may be either byzantine nodes controlled by the attacker, or simply bogus peer descriptors.&lt;/p&gt;&#xA;&lt;h3 id=&#34;denial-of-service&#34;&gt;Denial of Service&lt;/h3&gt;&#xA;&lt;p&gt;This attack is the simplest to orchestrate, and yet one of the hardest to deal with. A denial of service attack is one where an attacker sends packets that induce the maximum amount of computation on the remote. HTTP servers are commonly the recipients of such attacks, which is why we use CDNs where possible to shield our origin servers. This is obviously not possible for stateful APIs and databases. That&amp;rsquo;s why we put databases in private networks, and employ efficient filtering of API requests, often using an IP-based rate limiter. Obviously we can&amp;rsquo;t run our peer-to-peer application in a private network unless we only want to use it in a datacenter.&lt;/p&gt;&#xA;&lt;p&gt;Unfortunately, if an attacker has more bandwidth than any given node, the node can simply be denied service by exhausting their bandwidth. If the attacker has more bandwidth than the entire network combined, the whole network is denied service. So if you advocate for a given peer-to-peer network, the best way that you can support the network is by giving your bandwidth. Run a node.&lt;/p&gt;&#xA;&lt;h3 id=&#34;attack-mitigations&#34;&gt;Attack Mitigations&lt;/h3&gt;&#xA;&lt;h4 id=&#34;dat-delay&#34;&gt;Dat Delay&lt;/h4&gt;&#xA;&lt;p&gt;A node must wait some time before receiving dats. They must participate in the gossip of peers without receiving any dats, until the defined period has elapsed. They will then start to receive dats very slowly, based on the PROBE constant.&lt;/p&gt;&#xA;&lt;h4 id=&#34;trust&#34;&gt;Trust&lt;/h4&gt;&#xA;&lt;p&gt;A node normally selects only trusted peers to receive dats, but occasionally an untrusted peer is probed. Over time, a node is able to build trust with peers, therefore receiving dats more frequently. As a peer earns trust, they are more likely to be selected, thereby having more bandwidth allocated to them.&lt;/p&gt;&#xA;&lt;h4 id=&#34;npeer-limit&#34;&gt;NPEER Limit&lt;/h4&gt;&#xA;&lt;p&gt;We only accept PEER messages containing up to NPEER peer descriptors. I recommend that this value is either 2 or 3. I err on the side of caution with 2, because there is little benefit to speeding up the peer discovery in this way. It would be more secure to ask for peers more often.&lt;/p&gt;&#xA;&lt;p&gt;Funny! While writing this (2024-05-29), I discovered a vulnerability to eclipse attack. A byzantine peer may send many PEER messages, limited only by the packet filter. This made address poisoning trivial. This was resolved by recording the last time a PEER message was received, and only accepting a PEER message after the expected duration has elapsed since the last.&lt;/p&gt;&#xA;&lt;h2 id=&#34;storing-large-files&#34;&gt;Storing Large Files&lt;/h2&gt;&#xA;&lt;p&gt;As mentioned earlier, all application-specific complexity is pushed up the stack. Dave is purely a packet-sharing protocol, with no built-in features for storing large files. That said, I have considered the need for storing large files in the network.&lt;/p&gt;&#xA;&lt;h3 id=&#34;linked-list&#34;&gt;Linked List&lt;/h3&gt;&#xA;&lt;p&gt;The simplest approach to storing large files, and the earliest proof that I implemented, is a linked-list. Simply prepend or append the hash of the previous dat in next dat. This simple approach has the disadvantage that reading is slow because GETs cannot be made in parallel.&lt;/p&gt;&#xA;&lt;h3 id=&#34;merkle-tree&#34;&gt;Merkle Tree&lt;/h3&gt;&#xA;&lt;p&gt;An optimised approach is to write the large file as a sequence of dats, and concurrently build a merkle tree, where additional pointer dats are used to reference dats containing the file&amp;rsquo;s content. The hash of the root of the tree may then be used as the reference to the file. A reader may then traverse the tree, collecting all of the dats in parallel, and re-assemble the file. Reads of this nature are significantly faster than the linked-list approach.&lt;/p&gt;&#xA;&lt;h2 id=&#34;some-repositories&#34;&gt;Some Repositories&lt;/h2&gt;&#xA;&lt;h3 id=&#34;godave-is-the-protocol-implementation-in-library-form-written-in-go&#34;&gt;godave is the protocol implementation in library form, written in Go.&lt;/h3&gt;&#xA;&lt;p&gt;Protocol &lt;a href=&#34;https://github.com/intob/godave/&#34;&gt;https://github.com/intob/godave/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;daved-is-a-program-that-executes-the-protocol-as-any-other-application-that-may-join-the-network&#34;&gt;daved is a program that executes the protocol, as any other application that may join the network.&lt;/h3&gt;&#xA;&lt;p&gt;A tiny cli &lt;a href=&#34;https://github.com/intob/daved/&#34;&gt;https://github.com/intob/daved/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;garry-is-a-http-gateway&#34;&gt;garry is a HTTP gateway.&lt;/h3&gt;&#xA;&lt;p&gt;Anything can work with dave. &lt;a href=&#34;https://github.com/intob/garry/&#34;&gt;https://github.com/intob/garry/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;state-of-operations&#34;&gt;State of Operations&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;m no-longer running public edge (bootstrap) nodes myself. I was using AWS, with a bunch of t4g.nano arm64 VMs running Debian 12. I used scripts &amp;amp; programs to control groups of additional machines as I needed. I view logs by grepping the linux system journal. Logs begin with a short path prefix /fn/proc/action, allowing me to efficiently grep logs without need for typing quotes around the query.&lt;/p&gt;&#xA;&lt;p&gt;Thank you for reading. I value advice and ideas. If you have any, please do reach me.&lt;/p&gt;&#xA;&lt;h3 id=&#34;run-as-node&#34;&gt;Run as Node&lt;/h3&gt;&#xA;&lt;p&gt;Executing with no command (just flags) puts the program in its default mode of operation, participating in the network.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;daved &lt;span style=&#34;color:#75715e&#34;&gt;# listen on all network interfaces, port 1618&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;daved -v &lt;span style=&#34;color:#75715e&#34;&gt;# verbose logging, use grep to filter logs&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;daved -v | grep /d/pr &lt;span style=&#34;color:#75715e&#34;&gt;# verbose logging, with only the daemon&amp;#39;s prune procedure logs output&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;daved -l :2024 &lt;span style=&#34;color:#75715e&#34;&gt;# listen on all network interfaces, port 2024. Same as -l [::]:2024&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;daved -e :1969 &lt;span style=&#34;color:#75715e&#34;&gt;# bootstrap to peer on local machine, port 1969&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;daved -e 12.34.56.78:1234 &lt;span style=&#34;color:#75715e&#34;&gt;# bootstrap to peer at address&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;commands&#34;&gt;Commands&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;daved set hello_dave &lt;span style=&#34;color:#75715e&#34;&gt;# write hello_dave to the network with default difficulty of 16 bits&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;daved -d &lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt; set hello_world &lt;span style=&#34;color:#75715e&#34;&gt;# write hello_world to the network with difficulty of 32 bits&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;daved get $HASH &lt;span style=&#34;color:#75715e&#34;&gt;# get the dat with the given work hash from the network&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;daved setf myfile.txt &lt;span style=&#34;color:#75715e&#34;&gt;# write a very small file to the network&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded>
    </item>
    <item>
      <title>logd</title>
      <link>https://rmrf.uk/cs/2024-03-15-logd/</link>
      <pubDate>Fri, 15 Mar 2024 00:00:00 +0000</pubDate>
      <guid>https://rmrf.uk/cs/2024-03-15-logd/</guid>
      <category>Computer Broscience</category>
      <description>Real-time logging (tail & query) over UDP, with ring buffers.</description>
      <content:encoded>&lt;p&gt;Started ~2023-12&#xA;Real-time logging (tail &amp;amp; query) for virtually unlimited logs. A map of in-memory ring buffers. Built on UDP &amp;amp; homegrown ephemeral HMAC. Go std lib + Protobuf. Image is built from scratch (Linux kernel + app executable).&lt;/p&gt;&#xA;&lt;p&gt;2024-05:&#xA;Looking back, this was an enjoyable project to rediscover writing software. It&amp;rsquo;s amazing to return to something after a change of mind. A new mind has more fun, and is more productive. The same thing happened when I re-imagined speedflying after my spinal-cord injury. Some years after my recovery, I rediscovered a lost joy with a new mind.&lt;/p&gt;&#xA;&lt;p&gt;RE quality of this program: Looking back on past work is always funny, I leave this here as it still serves as a journal. Now, I would just run a process that reads the linux journal. Useless abstractions such as loggers pollute the code. This project is a useless abstraction, like most other software.&lt;/p&gt;&#xA;&lt;p&gt;2024-12:&#xA;Looking back at this project, I find it amusing to think that I struggled to solve the replay attack vulnerability.&lt;/p&gt;&#xA;&lt;p&gt;As the protocol is over UDP, each packet must be authenticated. I had tried using a ring-buffer of recent packet hashes, A/B cuckoo filters, and probably other things too. I should have just gone with a sequence counter, incremented and included in each packet. The server can then immediately reject packets that are far behind the sequence counter. A small ring-buffer could then be used to check packets that are slightly behind the sequence counter. This prevents replays, and is far more efficient than a large ring-buffer or A/B cuckoo filters.&lt;/p&gt;&#xA;&lt;p&gt;I learned of this sequence counter approach when I read through the WireGuard spec.&lt;/p&gt;&#xA;&lt;h2 id=&#34;repo&#34;&gt;Repo&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/intob/logd&#34;&gt;https://github.com/intob/logd&lt;/a&gt;&lt;/p&gt;&#xA;</content:encoded>
    </item>
    <item>
      <title>arpload</title>
      <link>https://rmrf.uk/cs/2024-03-09-arpload/</link>
      <pubDate>Sat, 09 Mar 2024 00:00:00 +0000</pubDate>
      <guid>https://rmrf.uk/cs/2024-03-09-arpload/</guid>
      <category>Computer Broscience</category>
      <description>I wanted to upload a 4k video to Arweave network... and failed at first. Safely upload large files to Arweave network with arpload.</description>
      <content:encoded>&lt;h2 id=&#34;arweave-is-a-decentralised-storage-network-that-offers-a-unique-approach-to-data-storage&#34;&gt;Arweave is a decentralised storage network that offers a unique approach to data storage.&lt;/h2&gt;&#xA;&lt;p&gt;The network is designed to provide permanent information storage, where users pay a one-time fee to store data for at least 200 years, with the potential for it to last much longer, possibly forever.&lt;/p&gt;&#xA;&lt;p&gt;This is based on the assumption that the cost of storage will continue to decrease over time, and the initial fee paid by the uploader is invested in an endowment that accrues value, similar to interest in a bank account.&lt;/p&gt;&#xA;&lt;p&gt;The endowment is a critical component of Arweave&amp;rsquo;s economic model. It is designed to ensure that the rewards for storing data remain higher than the cost of storage over time. The network uses a portion of the storage fees to pay miners in the future, which should theoretically allow the model to sustain itself indefinitely.&lt;/p&gt;&#xA;&lt;p&gt;This approach is expected to provide a solution to the problem of data impermanence on the internet.&lt;/p&gt;&#xA;&lt;p&gt;Arweave&amp;rsquo;s network is often compared to Bitcoin but for data, creating a permanent and decentralised web within an open ledger.&lt;/p&gt;&#xA;&lt;p&gt;The network uses a blockchain-like structure called the blockweave, which allows for the storage of large datasets and incentivises miners to store a larger amount of data.&lt;/p&gt;&#xA;&lt;p&gt;The Arweave protocol is also designed to be accessible through traditional web browsers, making it user-friendly and widely adoptable.&lt;/p&gt;&#xA;&lt;p&gt;The AR token is the native cryptocurrency of the Arweave network and is used to pay for transactions and storage fees. The supply of AR tokens is capped, which contributes to the economic model of the network.&lt;/p&gt;&#xA;&lt;p&gt;In short, the network aims to ensure data permanence by leveraging the decreasing cost of storage and the economic incentives provided to miners to maintain the data over centuries.&lt;/p&gt;&#xA;&lt;h2 id=&#34;finally-i-got-around-to-somewhat-finishing-a-simple-arweave-file-uploader&#34;&gt;Finally, I got around to somewhat finishing a simple Arweave file uploader.&lt;/h2&gt;&#xA;&lt;p&gt;The story goes like this; I wanted to upload a 4k video to Arweave network.&lt;/p&gt;&#xA;&lt;h3 id=&#34;web-wallet-nope&#34;&gt;Web wallet? Nope&lt;/h3&gt;&#xA;&lt;p&gt;I first tried to use the &lt;a href=&#34;https://arweave.app&#34;&gt;Arweave web wallet&lt;/a&gt;. This would have worked, but the upload hung for so long without visible feedback (I forgot to open dev tools). I incorrectly assumed that the upload was failing. I quickly realised that I paid for the failed transactions, so I should stop wasting AR.&lt;/p&gt;&#xA;&lt;h3 id=&#34;ardrive-nope&#34;&gt;ArDrive? Nope&lt;/h3&gt;&#xA;&lt;p&gt;I tried ArDrive, because I just wanted a nice UI for safe &amp;amp; easy upload of a large file. The web interface, is indeed user-friendly. However, ArDrive uses bundling, and I want a standalone transaction.&lt;/p&gt;&#xA;&lt;h2 id=&#34;ok-lets-build&#34;&gt;Ok&amp;hellip; let&amp;rsquo;s build&lt;/h2&gt;&#xA;&lt;p&gt;Finally, I concluded that I should write an application to do this. Thankfully, there is a great library from everFinance, &lt;a href=&#34;https://github.com/everFinance/goar&#34;&gt;goar&lt;/a&gt;. This made it trivial to write the uploader.&lt;/p&gt;&#xA;&lt;p&gt;Today, I finally got around to somewhat finishing it by implementing the functionality to resume an interrupted upload. It can now serve as an example, or maybe even help you to upload something.&lt;/p&gt;&#xA;&lt;h2 id=&#34;demo&#34;&gt;Demo&lt;/h2&gt;&#xA;&lt;p&gt;Tx: &lt;a href=&#34;https://viewblock.io/arweave/tx/TK60GE39cvuTh_DlLK5ZCnhcoOkN42YFQjsJfzwvrk4&#34;&gt;TK60GE39cvuTh_DlLK5ZCnhcoOkN42YFQjsJfzwvrk4&lt;/a&gt;&#xA;&lt;img src=&#34;https://jsxligcn7vzpxe4h6dsszlszbj4fzihjbxrwmbkchmex6pbpvzha.arweave.net/TK60GE39cvuTh_DlLK5ZCnhcoOkN42YFQjsJfzwvrk4&#34; alt=&#34;The 4K image uploaded to Arweave by arpload in this demo, served directly from the transaction linked above&#34; loading=&#34;lazy&#34; /&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;repo&#34;&gt;Repo&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/intob/arpload&#34;&gt;https://github.com/intob/arpload&lt;/a&gt;&lt;/p&gt;&#xA;</content:encoded>
    </item>
  </channel>
</rss>
