{"@attributes":{"version":"2.0"},"channel":{"title":"Posts on Segflow","link":"https:\/\/segflow.github.io\/post\/","description":"Recent content in Posts on Segflow","generator":"Hugo","language":"en-us","lastBuildDate":"Fri, 29 May 2026 00:40:00 +0200","item":[{"title":"Finding a needle in a 4 GB haystack: from 0.75 GB\/s to 49 GB\/s in Go","link":"https:\/\/segflow.github.io\/post\/fast-file-search-go\/","pubDate":"Fri, 29 May 2026 00:40:00 +0200","guid":"https:\/\/segflow.github.io\/post\/fast-file-search-go\/","description":"<p>I had a 4 GiB file that&rsquo;s almost entirely zeros, exactly <strong>one<\/strong> non-zero\n<code>int64<\/code> is hiding at offset <code>Size - 8<\/code> (the last aligned slot). The task: find\nthat offset, as fast as possible, in Go on Linux.<\/p>\n<p>It&rsquo;s a deliberately silly problem. There&rsquo;s no parsing, no indexing, no\ncleverness on the algorithm side. The only thing it measures is how much data\nwe can pull through a CPU per second. Exactly the kind of micro-task that\nexposes every layer of the stack: the Go runtime, the standard library, the\nkernel, the page cache, the memory hierarchy, and SIMD, including Go 1.26&rsquo;s\nbrand-new <code>simd\/archsimd<\/code> package that lets you write AVX-512 in pure Go.<\/p>\n<p>Starting from the most obvious <code>os.ReadFile<\/code> + <code>for range<\/code> we get <strong>0.75\nGB\/s<\/strong>. Thirteen variants later we&rsquo;re at <strong>49 GB\/s<\/strong>, a 66\u00d7 speedup, and\nwe&rsquo;ll know exactly which wall we hit and why.<\/p>"},{"title":"No, you don't need Oh My Zsh","link":"https:\/\/segflow.github.io\/post\/no-you-dont-need-ohmyzsh\/","pubDate":"Mon, 23 Jun 2025 12:00:00 +0100","guid":"https:\/\/segflow.github.io\/post\/no-you-dont-need-ohmyzsh\/","description":"<p>Don&rsquo;t get me wrong \u2013 Oh My Zsh is an excellent product. It&rsquo;s well-maintained, feature-rich, and has helped millions of developers enhance their shell experience. But here&rsquo;s the thing: <strong>you don&rsquo;t need it to have a great zsh experience<\/strong>.<\/p>\n<p>When I recently set up a new workstation, I decided to skip Oh My Zsh entirely and configure zsh manually. The result? A lightweight, fast, and fully customized shell that does exactly what I need \u2013 nothing more, nothing less.<\/p>"},{"title":"The case against helper packages named `utils`","link":"https:\/\/segflow.github.io\/post\/against-utils-packages\/","pubDate":"Thu, 09 Jan 2025 12:00:00 +0100","guid":"https:\/\/segflow.github.io\/post\/against-utils-packages\/","description":"<p>I argued earlier that <a href=\"..\/post\/self-explained-function-signatures\/\">a function&rsquo;s signature should never\nlie<\/a>, and then that <a href=\"..\/post\/errors-are-values\/\">errors are\nvalues only if you treat them like values<\/a>. Both\nposts circled the same question at different scales: <em>what is this code\nfor?<\/em> This one closes the loop at the package level. The short version: if\nyour package is named <code>utils<\/code>, you don&rsquo;t have an answer.<\/p>"},{"title":"Errors are values, but only if you treat them that way","link":"https:\/\/segflow.github.io\/post\/errors-are-values\/","pubDate":"Thu, 14 Nov 2024 17:10:00 +0100","guid":"https:\/\/segflow.github.io\/post\/errors-are-values\/","description":"<p>Last time I argued that a function&rsquo;s <a href=\"..\/post\/self-explained-function-signatures\/\">signature should never\nlie<\/a>. The <code>error<\/code> return is the\nmost lied-about part of every Go signature I read. It gets logged,\nswallowed, repackaged, panicked on, and printed back to the user as\n<code>internal error: internal error: internal error<\/code>. Three habits keep it\nhonest.<\/p>"},{"title":"Self-explained function signatures","link":"https:\/\/segflow.github.io\/post\/self-explained-function-signatures\/","pubDate":"Thu, 26 Sep 2024 08:45:00 +0100","guid":"https:\/\/segflow.github.io\/post\/self-explained-function-signatures\/","description":"<p>A function I once shipped looked like this:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"><code class=\"language-go\" data-lang=\"go\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">func<\/span> <span style=\"color:#a6e22e\">Process<\/span>(<span style=\"color:#a6e22e\">data<\/span> []<span style=\"color:#66d9ef\">byte<\/span>) <span style=\"color:#66d9ef\">error<\/span>\n<\/span><\/span><\/code><\/pre><\/div><p>Innocent. You give it bytes, it returns an error. What more do you need?<\/p>\n<p>Plenty, as it turned out. <code>Process<\/code> also wrote a file to <code>\/tmp<\/code>, posted the\nsame bytes to a webhook, and stashed a copy in a package-level global so the\nnext call could &ldquo;diff against the last one&rdquo;. None of that was in the\nsignature. None of it was in the doc comment either, because by the time\nanyone read the doc comment they&rsquo;d already wired the function into a hot path\nand were wondering why CI was flaky.<\/p>\n<p>The signature lied. Once a signature lies, every reader has to open the body\nto find the truth. That&rsquo;s the cost.<\/p>"},{"title":"Zero-copy in Go: sendfile, splice, and the cost of io.Copy","link":"https:\/\/segflow.github.io\/post\/zero-copy-sendfile-splice\/","pubDate":"Mon, 22 Jan 2024 10:00:00 +0100","guid":"https:\/\/segflow.github.io\/post\/zero-copy-sendfile-splice\/","description":"<p>A small file-serving service of mine slowed to a crawl one afternoon after a\n&ldquo;harmless&rdquo; middleware change. CPU on the server box doubled, throughput\nroughly halved. The diff was a single line: instead of handing a <code>*os.File<\/code>\nto <code>io.Copy<\/code>, somebody had wrapped it in a tiny logging reader to count\nbytes.<\/p>\n<p>That one wrap quietly turned off <code>sendfile(2)<\/code>.<\/p>\n<p>This post is about that fast path: what Go does for you for free, how to see\nit actually fire, and the surprisingly easy ways to lose it.<\/p>"},{"title":"Bounds-check elimination in Go: making the prover happy","link":"https:\/\/segflow.github.io\/post\/bounds-check-elimination\/","pubDate":"Sat, 08 Jul 2023 13:30:00 +0100","guid":"https:\/\/segflow.github.io\/post\/bounds-check-elimination\/","description":"<p>I had a hot loop I could not get any faster. Plain <code>for i := 0; i &lt; n; i++<\/code>,\ntwo slice reads, one add, return. On paper that is three or four x86\ninstructions per iteration. In practice it was running at about half the\nthroughput I expected, and <code>perf<\/code> kept pointing at the same two lines.<\/p>\n<p>When I finally dumped the assembly, the answer was sitting right there: a\n<code>CMPQ<\/code> followed by a conditional jump to <code>runtime.panicIndex<\/code> on every read.\nThe compiler was leaving the runtime bounds check in. The &ldquo;work&rdquo; of the\nloop was 8 bytes of useful instructions, plus 6 bytes of &ldquo;is this index\nstill safe&rdquo;, every single iteration.<\/p>\n<p>This is the fourth post in what was supposed to be a trilogy on the Go\ncompiler: after <a href=\"..\/post\/go-compiler-optimization\/\">following an <code>int<\/code>-from-<code>string<\/code> lookup down into the\ncompiler itself<\/a>, <a href=\"..\/post\/inlining-budgets\/\">budgeting\ninlining<\/a>, and <a href=\"..\/post\/escape-analysis-examples\/\">reading escape-analysis\noutput<\/a>, it would be rude to skip the one\nwhere rewriting two lines wins back 22% on the same hardware. Quartet it\nis.<\/p>"},{"title":"Backpressure for the impatient: channels vs. semaphores vs. tokens","link":"https:\/\/segflow.github.io\/post\/backpressure-patterns\/","pubDate":"Sun, 19 Feb 2023 16:00:00 +0100","guid":"https:\/\/segflow.github.io\/post\/backpressure-patterns\/","description":"<p>A pipeline has backpressure when the consumer can make the producer\nslow down, or refuse the work, or both. A bigger buffer does neither.\nIt just defers the same problem, with more memory held hostage in the\nmeantime. The two get confused a lot, and the difference shows up\nunmistakably in the tail latency numbers, so this post does the\ncomparison directly.<\/p>\n<p>When load exceeds capacity you have three sane options:<\/p>\n<ol>\n<li><strong>Block the caller<\/strong> until a slot frees. Throughput pins to whatever\nthe consumer sustains; tail latency degrades cleanly.<\/li>\n<li><strong>Reject the caller<\/strong> so they retry, fall back, or shed. Latency\nstays low for accepted work; some work never happens.<\/li>\n<li><strong>Rate-limit the caller<\/strong> so they never get close to overloading you.\nPredictable throughput, the rest is queued or dropped.<\/li>\n<\/ol>\n<p>Go has a direct idiom for each: a <strong>bounded channel<\/strong> for option 1, the\n<strong><code>golang.org\/x\/sync\/semaphore<\/code><\/strong> package for option 1 with per-item\nweighting, and a <strong><code>time.Ticker<\/code> token bucket<\/strong> for option 3 (and\ntrivially option 2). They are not interchangeable. They produce three\nvery different latency curves under the same workload, which is what we\nwill see below.<\/p>"},{"title":"What strace -c taught me about a fast CLI","link":"https:\/\/segflow.github.io\/post\/strace-c-fast-cli\/","pubDate":"Mon, 05 Sep 2022 11:20:00 +0100","guid":"https:\/\/segflow.github.io\/post\/strace-c-fast-cli\/","description":"<p>The CLI was fast. I had benchmarked it on my laptop, on a fresh clone of the\nrepo, and it finished in well under a second. Then a coworker pointed it at a\nreal monorepo, the kind with 30,000 files spread across a few thousand\ndirectories, and the thing crawled. Same code, same machine class, just more\nfiles. The user-visible work had not changed. The wall clock had.<\/p>\n<p>This is the story of the half hour I spent figuring out why, what <code>strace -c<\/code>\nshowed me, and why I now reach for it before any profiler when something\n&ldquo;feels slow&rdquo; on Linux.<\/p>\n<p>My first instinct was wrong, by the way. I assumed disk. The repo was big,\nthe laptop has an NVMe drive but it is not magic, and &ldquo;more files&rdquo; sounds\nlike &ldquo;more IO.&rdquo; So I ran the program twice in a row, expecting the second\nrun to be fast off the page cache. It was not. Both runs took roughly the\nsame time. Whatever was slow, it was not waiting on the disk.<\/p>"},{"title":"Escape analysis, demystified by 6 tiny examples","link":"https:\/\/segflow.github.io\/post\/escape-analysis-examples\/","pubDate":"Sun, 17 Apr 2022 14:45:00 +0100","guid":"https:\/\/segflow.github.io\/post\/escape-analysis-examples\/","description":"<p>A while back I had a hot loop that profiled like a heap-allocation\nmachine gun. <code>pprof<\/code> blamed <code>runtime.mallocgc<\/code>. The code looked\ninnocent. The fix turned out to be a one-line signature change, and\nthe compiler had been quietly screaming at me about it the whole\ntime via <code>-gcflags=-m<\/code>.<\/p>\n<p>This post is the second half of a tour I started in <a href=\"..\/post\/go-compiler-optimization\/\">My journey\noptimizing the Go Compiler<\/a> and\ncontinued in <a href=\"..\/post\/inlining-budgets\/\">What actually fits in Go&rsquo;s inlining\nbudget<\/a>. Inlining decides <em>where<\/em> code runs;\nescape analysis decides <em>where data lives<\/em>. Six 10-line examples\ncover roughly 90% of what you&rsquo;ll see in real Go.<\/p>"},{"title":"Designing for deletion","link":"https:\/\/segflow.github.io\/post\/designing-for-deletion\/","pubDate":"Mon, 11 Oct 2021 09:15:00 +0100","guid":"https:\/\/segflow.github.io\/post\/designing-for-deletion\/","description":"<p>A few months ago my team had to kill a feature. Nothing dramatic; a\nhalf-finished onboarding flow product had quietly stopped believing in.\nThe ticket said &ldquo;remove the new welcome wizard.&rdquo; I estimated two days.\nIt took three weeks.<\/p>\n<p>The wizard itself was about 400 lines. The reason it took three weeks\nis that those 400 lines had grown roots. A flag on the user record. A\ncolumn in the analytics table. Three branches in the auth middleware. A\ntemplate partial pulled into the main layout. A background job that\n&ldquo;would be useful for other things later.&rdquo; A helper in <code>internal\/util<\/code>\nthat nine packages now imported. By the time we&rsquo;d traced every tendril\nthe diff touched 60 files and the review thread looked like a hostage\nnegotiation.<\/p>"},{"title":"Tuning a Go TCP server toward 1M idle connections on a laptop","link":"https:\/\/segflow.github.io\/post\/million-idle-connections\/","pubDate":"Mon, 22 Mar 2021 18:30:00 +0100","guid":"https:\/\/segflow.github.io\/post\/million-idle-connections\/","description":"<p>I had been telling people for months that Go can &ldquo;trivially&rdquo; hold a million\nidle TCP connections. The runtime uses epoll, goroutines are cheap, what\ncould go wrong. Then a colleague asked me to actually do it, and I realised\nI had never tried. So I sat down with my laptop, a fresh <code>net.Listen<\/code>, and\na client that just wants to open a lot of sockets.<\/p>\n<p>The first wall I hit was 1024 file descriptors. After that came five more\nwalls in quick succession, none of them in user code. This post is the log\nof every wall I walked into and how to move it. Code, logs, and scripts\nare in the <a href=\".\/million-idle-connections\/code\/\">scratch directory<\/a>.<\/p>"},{"title":"Inlining budgets, and why your one-liner stayed slow","link":"https:\/\/segflow.github.io\/post\/inlining-budgets\/","pubDate":"Mon, 14 Sep 2020 10:00:00 +0100","guid":"https:\/\/segflow.github.io\/post\/inlining-budgets\/","description":"<p>After <a href=\"..\/post\/go-compiler-optimization\/\">my map-lookup contribution to the Go compiler<\/a>\nback in April, I kept poking the toolchain whenever a benchmark surprised me.\nLast week it surprised me again, and the lesson is short enough to fit in one\npost: Go inlines aggressively, but not infinitely, and the <em>shape<\/em> of your\nfunction matters more than its length.<\/p>\n<p>The story starts with a three-line helper that I was sure the compiler would\ninline. <code>pprof<\/code> disagreed.<\/p>"},{"title":"My journey optimizing the Go Compiler","link":"https:\/\/segflow.github.io\/post\/go-compiler-optimization\/","pubDate":"Tue, 28 Apr 2020 13:20:28 +0100","guid":"https:\/\/segflow.github.io\/post\/go-compiler-optimization\/","description":"<p>At <a href=\"https:\/\/edge.network\/\">EDGE<\/a> we write a lot of Go, and we love it for various reasons, one of them being speed. One day I got into a situation where I need to assign an <code>int<\/code> to a variable based on another string value.<\/p>\n<p>Sounds easy right? well yes, but this particular use case awakened the beast in me and made me think what&rsquo;s the <strong>best<\/strong> way to do it.<\/p>\n<p>The journey finished by me contributing to the language compiler and make <code>map<\/code> lookups faster.<\/p>"},{"title":"Noxale CTF: Grocery List (pwn)","link":"https:\/\/segflow.github.io\/post\/noxale-ctf-grocery_list\/","pubDate":"Sun, 09 Sep 2018 19:21:10 +0100","guid":"https:\/\/segflow.github.io\/post\/noxale-ctf-grocery_list\/","description":"<p>In this challenge, we are given a service IP and PORT, to which we can connect using <code>netcat<\/code> or any similar tool.\nWe are also provided with an <code>ELF<\/code> file.<\/p>"},{"title":"PlaidCTF: Shop (pwn)","link":"https:\/\/segflow.github.io\/post\/plaid-ctf-shop\/","pubDate":"Sun, 06 May 2018 20:17:33 +0100","guid":"https:\/\/segflow.github.io\/post\/plaid-ctf-shop\/","description":"<p>Below, you will find the full exploit for PlaidCTF pwn200 task. The full write-up will follow.<\/p>"},{"title":"AceBear CTF: Secure login (reverse)","link":"https:\/\/segflow.github.io\/post\/acebear-ctf-secure_login\/","pubDate":"Tue, 30 Jan 2018 21:12:11 +0100","guid":"https:\/\/segflow.github.io\/post\/acebear-ctf-secure_login\/","description":"<p>In this article I will share with you the solution to <code>Secure Login<\/code> challenge presented at <em>Acebear CTF<\/em>, this task was worth 900 points. Even though I did not manage to solve the challenge on time, I still enjoyed it a lot.<\/p>"},{"title":"34C3 CTF: GiftWrapper 2 (pwn)","link":"https:\/\/segflow.github.io\/post\/34c3-ctf-giftwrapper2\/","pubDate":"Sun, 21 Jan 2018 09:21:10 +0100","guid":"https:\/\/segflow.github.io\/post\/34c3-ctf-giftwrapper2\/","description":"<p>In this challenge, we are given a service IP and PORT, to which we can connect using <code>netcat<\/code> or any similar tool. We are also provided with a <code>tar<\/code> file that contains the service binary and some <code>.so<\/code> modules.<\/p>"},{"title":"3DS CTF: Xesar (crypto)","link":"https:\/\/segflow.github.io\/post\/3ds-ctf-xesar\/","pubDate":"Sat, 30 Dec 2017 13:20:47 +0100","guid":"https:\/\/segflow.github.io\/post\/3ds-ctf-xesar\/","description":"<p>Recently I decided to start playing CTFs again, and since I needed some training before playing a real one, I decided to take a look at some recent CTFs in <a href=\"https:\/\/ctftime.org\/\">CTFtime.org<\/a>.<\/p>\n<p>And then I found this crypto task with no write-up for it yet, and that&rsquo;s why I jumped into it :)<\/p>"}]}}