<?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>Dear Fortuna</title><link>http://dearfortuna.blog/</link><description>Recent content on Dear Fortuna</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><copyright>© 2026 Vasyl Bodnar | Text: CC BY 4.0 | Code: BSD-3</copyright><lastBuildDate>Wed, 11 Feb 2026 14:00:00 -0400</lastBuildDate><atom:link href="http://dearfortuna.blog/index.xml" rel="self" type="application/rss+xml"/><item><title>Why You Should Not Trust Language Benchmarks</title><link>http://dearfortuna.blog/posts/why-you-should-not-trust-language-benchmarks/</link><pubDate>Wed, 11 Feb 2026 14:00:00 -0400</pubDate><guid>http://dearfortuna.blog/posts/why-you-should-not-trust-language-benchmarks/</guid><description>&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>Recently I felt drawn back to OCaml.
It was always one of my favorite languages, but it did have its issues with the standard library (There is Base, but I&amp;rsquo;d rather not).
And, OCaml always lacked some optimization powers of a lower language like C.
However, this is 2026, the Year of OCaml, maybe.&lt;/p>
&lt;p>Plenty of QoL and new cool things like effects are added to the language and the actual standard library.
There is also OxCaml, a bunch of extensions for more QoL, better control over allocation, layouts, etc..
Some of those extensions are even being added to OCaml itself!
A dream come true for me certainly.&lt;/p></description><content:encoded><![CDATA[<h2 id="intro">Intro</h2>
<p>Recently I felt drawn back to OCaml.
It was always one of my favorite languages, but it did have its issues with the standard library (There is Base, but I&rsquo;d rather not).
And, OCaml always lacked some optimization powers of a lower language like C.
However, this is 2026, the Year of OCaml, maybe.</p>
<p>Plenty of QoL and new cool things like effects are added to the language and the actual standard library.
There is also OxCaml, a bunch of extensions for more QoL, better control over allocation, layouts, etc..
Some of those extensions are even being added to OCaml itself!
A dream come true for me certainly.</p>
<p>With all these thoughts, though, I have made a mistake in trying to look at OCaml benchmarks.
It is well known that there are lies, damn lies, and (language) benchmarks.
Comparing languages and their implementations is often a futile effort.
At best you could argue for tiers, e.g. system language vs GCed language vs Python.
But with things like OxCaml and Numpy Python, even that can be unreliable.
However, it is an innocent idea in theory, just search for &ldquo;OCaml performance benchmarks&rdquo;.</p>
<p>On Google,
the first few results are two 2020 <code>discuss.ocaml.org</code> posts,
one informationless blog post that I hope was AI-generated,
and a proudly vercel benchmarking site, <a href="https://programming-language-benchmarks.vercel.app/ocaml-vs-go">Programming-Language-Benchmarks</a>.
Lower you can also see <a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/measurements/ocaml.html">Benchmarks Game</a>,
Rust vs OCaml Medium article and some resources for benchmarking actual OCaml code.
The article requires an account, so I can&rsquo;t see the actual benchmark,
but it seems to focus on actual language differences like GC in the intro.
We will be looking at the benchmark aggregates in PLB and BG though.</p>
<p>But first, a warmup example.</p>
<h3 id="c-is-slower-than-javascript">C++ is slower than JavaScript</h3>
<p>There are plenty of posts on every kind of tech and tech-adjacent site about how JavaScript somehow outperforms C or C++.
e.g.</p>
<p><a href="https://stackoverflow.com/questions/17036059/why-does-javascript-appear-to-be-4-times-faster-than-c">Why does JavaScript appear to be 4 times faster than C++?</a></p>
<p>The provided sources are simple and feel equivalent (well maybe Date vs clock()):</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>(<span style="color:#66d9ef">function</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">a</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">3.1415926</span>, <span style="color:#a6e22e">b</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">2.718</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">i</span>, <span style="color:#a6e22e">j</span>, <span style="color:#a6e22e">d1</span>, <span style="color:#a6e22e">d2</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span>(<span style="color:#a6e22e">j</span><span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">j</span><span style="color:#f92672">&lt;</span><span style="color:#ae81ff">10</span>; <span style="color:#a6e22e">j</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">d1</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> Date();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span>(<span style="color:#a6e22e">i</span><span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">&lt;</span><span style="color:#ae81ff">100000000</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">a</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">a</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">b</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">d2</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> Date();
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#34;Time Cost:&#34;</span> <span style="color:#f92672">+</span> (<span style="color:#a6e22e">d2</span>.<span style="color:#a6e22e">getTime</span>() <span style="color:#f92672">-</span> <span style="color:#a6e22e">d1</span>.<span style="color:#a6e22e">getTime</span>()) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;ms&#34;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#34;a = &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">a</span>);
</span></span><span style="display:flex;"><span>})();</span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">int</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">double</span> a <span style="color:#f92672">=</span> <span style="color:#ae81ff">3.1415926</span>, b <span style="color:#f92672">=</span> <span style="color:#ae81ff">2.718</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int</span> i, j;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">clock_t</span> start, end;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span>(j<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>; j<span style="color:#f92672">&lt;</span><span style="color:#ae81ff">10</span>; j<span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>        start <span style="color:#f92672">=</span> <span style="color:#a6e22e">clock</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span>(i<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>; i<span style="color:#f92672">&lt;</span><span style="color:#ae81ff">100000000</span>; i<span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>            a <span style="color:#f92672">=</span> a <span style="color:#f92672">+</span> b;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        end <span style="color:#f92672">=</span> <span style="color:#a6e22e">clock</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">printf</span>(<span style="color:#e6db74">&#34;Time Cost: %dms</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>, (end <span style="color:#f92672">-</span> start) <span style="color:#f92672">*</span> <span style="color:#ae81ff">1000</span> <span style="color:#f92672">/</span> CLOCKS_PER_SEC);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">printf</span>(<span style="color:#e6db74">&#34;a = %lf</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>, a);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div><p>So why is JS faster? Some could say V8 magic.
All kinds of tricks are used like pointer tagging, NaN-boxing, type predictions, not to mention just having a great GC.
What should have been interpreted floats in theory magically turn to assembly bits.
Maybe JIT has finally gotten to the point where it makes AOT obsolete.
But it really is just microbenchmarks.
JIT are great at optimizing hot loops.
The real performance though tends to fall off, especially with all the objects and type messes.</p>
<p>Use of the language in the real software,
rather than specialized algorithms or data structures, can be very different.
Hence the term &ldquo;microbenchmarks&rdquo;.
But even benchmarks of real software can hide that the difference is not in language, but in implementation of software itself.
Oftentimes there are differences in architecture, design, usecases.
Not to mention that newer works benefit from mistakes of previous works.
Sometimes rewriting a C codebase in Rust (within reason) is beneficial for the sole reason that you now know what to do.</p>
<p>Of course, in this case, the OP just forgot to use optimizations for C++ whereas Node did it for him in JS.
I am not even talking about any advanced performance sensitive flags, just good old <code>-O2</code>.</p>
<p>Another common issue is non-equivalent programs.
E.g. a C program that is forced to convert a long to float every iteration vs
JS one that simply used floats from the start (in theory everywhere, but reality is different).
There is also a subissue there with &ldquo;idiomatic code&rdquo;.
Idiomatic is not necessarily the most performant, but it is often the expected one for the language.
Thus, there is the issue of when to forsake idiomatic code for performant code</p>
<h2 id="the-numerous-problems">The Numerous Problems</h2>
<h3 id="simd-multi-threading">SIMD, Multi-threading?</h3>
<p>When you use lower languages like C and C++, is it fair to use SIMD?
That is a good question as SIMD can more accurately represent the ceiling of the language.
At the same time, not all languages even provide SIMD intrinsics.</p>
<p>No need to mention Python or Ruby, when even Go required you to just write straight assembly until Go 1.26 (released yesterday) and it is still under an experimental flag!
OCaml by virtue of OxCaml extensions now has SIMD intrinsics, but it did not have them before in any non-C way.
Given that Go and OCaml theoretically have SIMD support,
it is harder to say that comparing them without SIMD to C with SIMD is fair.</p>
<p>You could call C for many of these other languages.
However, at that point, you can lose a lot of performance due to FFI overhead.
The overhead itself also depends on where and how SIMD was used.
And if we are talking about using C FFI, all bets are off anyway.
What is the difference between implementing a small part in C vs just the entire algorithm.
Not ideal.</p>
<p>There is also the question of whether it is realistic. Even in C you don&rsquo;t just reach for SIMD whenever due to lack of abstractions.
But then what should you do if the languages do provide those nice abstractions.
They can make the SIMD a lot easier, portable, and simply accessible.
Zig has fine abstractions for SIMD in the core language.
Rust has them in its nightly builds.
Similar abstractions also seem to be the goal for Go once they are finished with implementing intrinsics.</p>
<p>Using multiple CPU threads is another issue.
Many languages support POSIX threads, but ergonomics can be awful.
Some languages provide better abstractions, many don&rsquo;t.
C and C++ suprisingly make it easy with OpenMP, at least for more embarassing problems.</p>
<h3 id="implementation">Implementation?</h3>
<p>Furthermore, different languages may optimize for different purposes.
Scheme, e.g., must be tail-recursive.
It is even specified in exactly what ways tail-recursion must be handled in the standard.
JavaScript implementations (besides Safari somehow) do not support it.
Yet, it is actually in the ES6 spec (from <em>2015</em>).
C does it, but there is no requirement nor guarantee generally.
Tail-recursion optimization can turn recursion into a while loop,
so it is a very important feature of the language implementation.</p>
<p>These problems relate to issue of equivalence too.
Should a C program using OpenMP, tail-recursion, and SIMD even be considered similar to any program that does not or can not.
Hell, even if you are not using SIMD yourself, GCC automatically enables SSE2 for x86_64 programs,.
SSE2 matters for libc (e.g. memcpy or memset), not to mention potential auto-vectorization.
To go even further, what if a C program uses <code>--ffast-math</code> to optimize floating point?
If the results are the same, does it even matter if that C program ignores IEEE compliance.
Means do matter.</p>
<p>There is an argument for separating language spec and implementation.
In majority of cases there is already one least unpopular choice,
even if it is questionable in performance, like CPython.
The issue is that some languages don&rsquo;t <em>really</em> have a spec or common implementation, see Scheme for latter.
Or, the spec might not be respected in all cases like the tail-recursion in ES6 for example.
As another example, OxCaml provides many performance-related extensions
that vanilla OCaml does not have yet.</p>
<p>Different implementations may suffer from lack of support.
E.g. Pypy is much faster than CPython, benchmarks will show as much.
However, there is a reason why PyPy has not replaced CPython with their incompatibilities.
Another example is tinygo, which uses LLVM for backend.
It can outperform go in some benchmarks, but it lacks certain features and has a different focus from go for real use.</p>
<p>Talking about LLVM, far too many languages are just LLVM frontends.
This is for pragmatic reasons, however, it does make comparing them a bit difficult.
An extra annoyance is that some languages may indeed optimize above LLVM (e.g. Rust), others may not at all.</p>
<p>Note that the issue is not only in that these questions are often unanswered,
but in that answers to them are often controversial.
It is easy to say or enforce one way, like banning SIMD,
but it is hard to convince those who disagree.</p>
<h3 id="exhibit-111">Exhibit #111</h3>
<p>There are plenty of sites and blog posts for ranking languages by performance (as most other metrics are even more pointless).
<a href="https://programming-language-benchmarks.vercel.app">Programming-Language-Benchmarks</a> (I will use PLB for short)
is certainly one of the better ones.
It states that it is influenced by <a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/index.html">Benchmarks Game</a> (BG),
and you can see the similarities.
Their primary purpose is to gather the fastest solutions for different language.</p>
<p>PLB and BG both provide a set of problems and solutions in different languages.
Some of these problems are very basic like &ldquo;helloworld&rdquo;,
others are more complicated (relatively) data structures and algorithms like LRU, &ldquo;Least Recently Used&rdquo;.
Unlike many other benchmarks, these sites do provide the source code, the flags, and everything that they did.
Though you would have to trust them on properly benchmarking these with warmup and whatnot.</p>
<p>PLB and BG do good in mentioning if the code uses multi-threading, SIMD, or in BG&rsquo;s case some other forms of unsafe/horrid optimizations.
PLB has a different name (e.g. <code>1.c</code> vs <code>1-i.c</code>), BG has a star, which I would prefer (as in all bets are off).
The big benefit of BG is that it has a lot more implementations and problems, but PLB has plenty itself.
And these do matter, as some implementation are significantly worse.
For example, n-body problem on BG has <code>* C gcc #9</code> on the top when sorted by time, optimized with 256bit AVX SIMD intrinsics.
On the other hand, <code>* C gcc #4</code> only uses 128bit SSE, so unless its algorithm is a direct upgrade, it simply has to be worse.
And this is the same language and compiler.
Not to mention all the different implementations inbetween in also optimized Rust and C++ code.</p>
<p>There are still plenty of more naive solutions as well, which in some cases can make C slower than (optimized) Java.</p>
<p>Regardless, from these problems it is very hard to tell what language is &ldquo;better&rdquo;.
It is possible that someone wrote a very fast C++ solution with AVX against a poor SSE C solution.
Both have SIMD, but different tiers.</p>
<p>BG does provide some box plots which show you the general floor, ceiling, mean, as well as outliers.
This in theory provides a better description of the language capability.
In reality, there really isn&rsquo;t enough and all the problems I mentioned can even be compounded.</p>
<p>Another issue was, as I mentioned, potentially misleading remarks.
The original PLB page that I shared was to OCaml vs Go comparison (interesting that this was the top result and not OCaml&rsquo;s page).
The first problem for me was that <code>1-m.go</code>, multi-threaded version, was slower than <code>1.go</code>, the base one.
Granted <code>1.go</code> was tinygo and <code>1-m.go</code> was go, but unless go is an order of magnitude slower to cancel out multi-threading, something was off.
Indeed, if you just hover the links you will realize that <code>1-m.go</code> link points to <code>1.go</code>.
Interestingly, all <code>-m.go</code> problems except for spectral norm point to non-m variants, which does track with what numbers we get.
This is in the benchmark yaml file for go, so I am not sure why the website values don&rsquo;t match.</p>
<p>If you look over all the problems you will indeed see that tinygo and go are trading blows in speed and memory use.
OCaml is behind in speed and memory, but usually not too far off, even winning in a few select cases.
If we use C solution <code>2.c</code> (no SIMD) for nbody problem as a baseline, then go&rsquo;s speed is 11% slower, OCaml&rsquo;s is 17% slower.
So now that we have such a comparison, what does it really tell us? Nothing.</p>
<p>C is a good baseline as it is typically the fastest with lowest overhead.
But realistically 11% and 17% are pretty close.
The difference is essentially ~300 ms vs ~350 ms, not something you will even tell apart.
The larger difference is memory usage, being 2MB vs 3.5MB vs 5MB, but you would expected that from GCs.
We are not using floppys as our RAM, so that is extremely negligible.
So C is not fast, Go and OCaml are somewhat close. But that was just one nbody problem.</p>
<p>I could instead pick nsieve, where C is ~250 ms, Go is ~300ms, and OCaml is ~900ms.
Huh, suddenly OCaml looks like a grade below those two.
Now you can suddenly make an argument that OCaml is much worse.
Indeed on most of these problems, OCaml is trailing lightly behind Go, but on others it is nearly an order away.
You could just chuck it up to GC being GC, maybe some optimization differences, maybe boxes since people like <code>ref</code> too much.
Still, how can you make an argument that OCaml is close or far from Go, when the results are so varied.
I guess you could say that because OCaml is varying so much, it is strictly worse.
But what if your usecases align perfectly.
What if you don&rsquo;t pick a language based on a language benchmark.</p>
<p>In the end, the reality is that to make a conclusion you would need to read the source codes,
check the compile flags, etc. to understand the differences, trade-offs, benefits.
This inadvertedly requires at least passing understanding of the languages you look at.
But at that point you could already be familiar with a language enough to gauge its speed.
A beginner, on the other hand, might misinterpret the results completely without any knowledge to guide.</p>
<p>Note that these sites are likely made in jest,
or mostly to collect optimized solutions to problems in a way that is gradable.
I doubt (as in hope this is not the case) the authors genuinely believe that they are fairly comparing languages.
But these sites can be misused, misunderstood, or twisted in ways that are orthogonal to their purpose.
At the same time, a much worse example would be someone with something to prove.</p>
<h3 id="intent-matters">Intent matters</h3>
<p>As for any statistic, often it is not the numbers but the flags.
PLB and BG don&rsquo;t seem to own any horses in the race,
though, it is not impossible for even personal bias to show up.
In a lot of cases, it is on the author of the benchmark to tweak algorithms to make their predetermined winner look better.
Here is a <a href="https://leveluppp.ghost.io/how-to-lie-with-benchmarks/">cool guide</a> if you want to lie better.</p>
<p>It is not rare in this day and age for an innocent blog to be a thinly veiled advertisement for something.
Many Github repos these days are faces of a product to sell or popularize.
Benchmarks far too often are convenient for this, as people look at faster speed and better resource usage in awe.
The same benchmarks that can easily be misleading, manipulated, cherry-picked, and abused.</p>
<h2 id="the-end">The End</h2>
<p>Don&rsquo;t do language benchmarks, kids.
It is a waste of time, memory, and gzipped bytes.</p>
]]></content:encoded></item><item><title>Nicest (small) LCG numbers</title><link>http://dearfortuna.blog/posts/nicest-lcg/</link><pubDate>Mon, 09 Feb 2026 22:00:00 -0400</pubDate><guid>http://dearfortuna.blog/posts/nicest-lcg/</guid><description>&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>Recently I wanted to give a nice small example of a &lt;a href="https://en.wikipedia.org/wiki/Linear_congruential_generator">Linear Congruential Generator&lt;/a> (LCG)
to show how easy and simple &lt;a href="https://en.wikipedia.org/wiki/Pseudorandom_number_generator">PseudoRandom Number Generators&lt;/a> (PRNG) are.&lt;/p>
&lt;p>An LCG is typically &lt;code>x[i] = a * x[i - 1] + c (mod m)&lt;/code>, where &lt;code>x[0]&lt;/code> is the initial seed, &lt;code>a&lt;/code> is multiplier, &lt;code>c&lt;/code> is additive, &lt;code>m&lt;/code> is our space.&lt;/p>
&lt;p>There are many good numbers online, and even plenty of bad numbers have a decent enough period, i.e. when the sequence starts repeating.
You will not notice the issue immediately in many of those cases, even if a computer or a bad actor would.
LCGs are simple and efficient, but are one of the least cryptographically secure after all.&lt;/p></description><content:encoded><![CDATA[<h2 id="intro">Intro</h2>
<p>Recently I wanted to give a nice small example of a <a href="https://en.wikipedia.org/wiki/Linear_congruential_generator">Linear Congruential Generator</a> (LCG)
to show how easy and simple <a href="https://en.wikipedia.org/wiki/Pseudorandom_number_generator">PseudoRandom Number Generators</a> (PRNG) are.</p>
<p>An LCG is typically <code>x[i] = a * x[i - 1] + c (mod m)</code>, where <code>x[0]</code> is the initial seed, <code>a</code> is multiplier, <code>c</code> is additive, <code>m</code> is our space.</p>
<p>There are many good numbers online, and even plenty of bad numbers have a decent enough period, i.e. when the sequence starts repeating.
You will not notice the issue immediately in many of those cases, even if a computer or a bad actor would.
LCGs are simple and efficient, but are one of the least cryptographically secure after all.</p>
<p>The issue is that LCG numbers are frequently large.
They have to be for good randomness over a large space, e.g. 2^32, the typical size of an <code>int</code>.
This does form a good question though, what would be the nicest LCG numbers, i.e. still looks &ldquo;random&rdquo;, yet small (under a 100 preferably).</p>
<h2 id="brute-forcing-into-n">Brute Forcing into N</h2>
<p>Now I am certain that there are analytical methods to find good numbers with the properties that I want.
I would not be surprised if someone did that already either.
But, these numbers are small, I can quite literally just loop over the entire input space and pick the best ones.
We do need a scoring function to check how good the inputs are though.</p>
<p>Let&rsquo;s start with uniqueness as our scoring function.
That is, we just check whether a number occurs for each possible number.
This rewards long periods and using all of our numbers as any future period or identical numbers will not impact the score.</p>
<p>Let us check all numbers up to (but excluding) 12.</p>
<p>The best result we get is <code>m = 11</code>, <code>c = 1</code>, <code>a = 1</code>, <code>x[0] = 1</code> or:</p>





<pre tabindex="0"><code>1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 ...</code></pre><p>Oh how great and random it is, most would almost certainly disagree.
The sequence is not ideal in what we usually think of as &ldquo;random&rdquo;.</p>
<p>Yeah, I should have expected that.
Despite not looking random, it has the best period of 11, known as full period (<code>= m</code>).
It even uses all of our numbers!</p>
<p>What about second or third or fifteenth best?
Just shifted, you get tweaks to <code>c</code> or <code>x[0]</code>, but it is practically the same obvious ordered sequence.</p>
<p>The flaw is in the scoring system as it does not care about anything except uniqueness.
Indeed, to it <code>1 2 3 4 5</code> and <code>4 2 5 3 1</code> are identical as long as same numbers are involved,
even if to us the second is more &ldquo;random&rdquo;.</p>
<h2 id="discouraging-unfair-play">Discouraging Unfair Play</h2>
<p>Well the core issue is that the algorithm can set <code>a = 1</code>, <code>m = 11</code> and
tweak <code>c</code> and <code>x[0]</code> with a simple ordered sequence.
One way to deal with it somewhat is to check whether the difference between numbers changes.
If it does not, reduce the score for each violation.</p>
<p>This system can have trouble tracking changes modulo.
E.g. <code>10 0 1</code>, has differences of <code>10</code> and <code>1</code>, rather than <code>1 mod 11</code> for both.
But we can just ignore those and look at other options. I call this technology Human-In-The-Loop™.</p>
<p>The results look much better:</p>





<pre tabindex="0"><code>x[0] = 5, a = 1, c = 5, m = 11 :: 5 10 4 9 3 8 2 7 1 6 0 5 ...
x[0] = 5, a = 1, c = 6, m = 11 :: 5 0 6 1 7 2 8 3 9 4 10 5 ...
x[0] = 1, a = 2, c = 0, m = 11 :: 1 2 4 8 5 10 9 7 3 6 1 2 ...
x[0] = 1, a = 6, c = 0, m = 11 :: 1 6 3 7 9 10 5 8 4 2 1 6 ...
x[0] = 1, a = 7, c = 0, m = 11 :: 1 7 5 2 3 10 4 6 9 8 1 7 ...
x[0] = 1, a = 8, c = 0, m = 11 :: 1 8 9 6 4 10 3 2 5 7 1 8 ...
x[0] = 1, a = 2, c = 1, m = 11 :: 1 3 7 4 9 8 6 2 5 0 1 3  ...
x[0] = 1, a = 6, c = 1, m = 11 :: 1 7 10 6 4 3 8 5 9 0 1 7 ...
x[0] = 1, a = 7, c = 1, m = 11 :: 1 8 2 4 7 6 10 5 3 0 1 8 ...
x[0] = 1, a = 8, c = 1, m = 11 :: 1 9 7 2 6 5 8 10 4 0 1 9 ...</code></pre><p>The first two do only tweak <code>c</code> and <code>x[0]</code>, but they don&rsquo;t look too bad in comparison to <code>1 2 3 4 ...</code>.
Also <code>x[0]</code> is set to <code>1</code>, but this is more of a first mover advantage (my brute force loop starts with <code>x[0] = 1</code>), and this is a seed to begin with.
I wouldn&rsquo;t mind using these, but it would be useful to test it on more numbers.</p>
<h2 id="larger-numbers">Larger numbers</h2>
<p>Let us then try up to (including) 50, this takes a second, but does find me some interesting results:</p>





<pre tabindex="0"><code>x[0] = 1, a = 11, c = 1, m = 50 :: 1 12 33 14 5 6 17 38 19 10 11 22 ...
x[0] = 1, a = 21, c = 1, m = 50 :: 1 22 13 24 5 6 27 18 29 10 11 32 ...
x[0] = 1, a = 31, c = 1, m = 50 :: 1 32 43 34 5 6 37 48 39 10 11 42 ...
x[0] = 1, a = 41, c = 1, m = 50 :: 1 42 23 44 5 6 47 28 49 10 11 2  ...
x[0] = 1, a = 11, c = 3, m = 50 :: 1 14 7 30 33 16 29 22 45 48 31   ...
x[0] = 1, a = 21, c = 3, m = 50 :: 1 24 7 0 3 16 39 22 15 18 31 4   ...
x[0] = 1, a = 31, c = 3, m = 50 :: 1 34 7 20 23 16 49 22 35 38 31   ...
x[0] = 1, a = 41, c = 3, m = 50 :: 1 44 7 40 43 16 9 22 5 8 31 24   ...
x[0] = 1, a = 11, c = 7, m = 50 :: 1 18 5 12 39 36 3 40 47 24 21    ...
x[0] = 1, a = 21, c = 7, m = 50 :: 1 28 45 2 49 36 13 30 37 34 21   ...</code></pre><p>It of course prefers <code>50</code> as <code>m</code>, since that provides the largest period.
The biggest issue is that <code>a = 11</code> and <code>+10</code> cousins have their issues, especially for <code>c = 1</code>.
It is a little off to see so many multiples of <code>11</code>. Likely due to <code>m = 50</code>.
But with <code>c = 7</code> or even <code>c = 3</code>, it looks decent enough.
I will have to look into a way to improve it by adding more to the score.</p>
<p>I feel that while <code>m = 50</code> is great for long periods, it is not good for &ldquo;randomness&rdquo;.
So that is another avenue for improvement.</p>
<p>We can also look at <code>m = 61</code> instead of <code>m = 50</code> as it might be nicer:</p>





<pre tabindex="0"><code>x[0] = 1, a = 26, c = 0, m = 61 :: 1 26 5 8 25 40 3 17 15 24 14 59 9  ...
x[0] = 1, a = 30, c = 0, m = 61 :: 1 30 46 38 42 40 41 10 56 33 14 54 ...
x[0] = 1, a = 31, c = 0, m = 61 :: 1 31 46 23 42 21 41 51 56 28 14 7  ...
x[0] = 1, a = 35, c = 0, m = 61 :: 1 35 5 53 25 21 3 44 15 37 14 2 9  ...
x[0] = 1, a = 43, c = 0, m = 61 :: 1 43 19 24 56 29 27 2 25 38 48 51  ...
x[0] = 1, a = 44, c = 0, m = 61 :: 1 44 45 28 12 40 52 31 22 53 14 6  ...
x[0] = 1, a = 51, c = 0, m = 61 :: 1 51 39 37 57 40 27 35 16 23 14 43 ...
x[0] = 1, a = 54, c = 0, m = 61 :: 1 54 49 23 22 29 41 18 57 28 48 30 ...
x[0] = 1, a = 55, c = 0, m = 61 :: 1 55 36 28 15 32 52 54 42 53 48 17 ...
x[0] = 1, a = 59, c = 0, m = 61 :: 1 59 4 53 16 29 3 55 12 37 48 26 9 ...</code></pre><p>I look at bigger <code>a</code>s as small ones give obvious powers.
These look alright, but have their issues too.</p>
<h2 id="final-results">Final Results</h2>
<p>My quest continues for the better numbers.
There is also a limit to how many inputs I can handle,
so maybe I should consider using some better state exploration than &ldquo;loop over everything&rdquo;.
Naturally more work required for a better scoring system.</p>
]]></content:encoded></item><item><title>Finding out My Hashtable is Awful</title><link>http://dearfortuna.blog/posts/finding-out-my-hashtable-is-awful/</link><pubDate>Sun, 18 Jan 2026 16:00:00 -0400</pubDate><guid>http://dearfortuna.blog/posts/finding-out-my-hashtable-is-awful/</guid><description>&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>I once found myself bored, though, not quite the useful kind of boredom.
I did not want to do my projects or something nice.
At the same time, I did not want to just spend it watching youtube or similar.
Thus, I thought, might as well do some leetcode.&lt;/p>
&lt;p>I am not particularly fond of leetcode generally.
Some algorithms are nice, most are not, and I rarely learn as opposed to &amp;ldquo;memorize&amp;rdquo; patterns.
Doing union-find on leetcode rarely feels as nice as using it for constant-folding optimizations in a compiler.
Still, leetcode is necessary for a lot of interviews (for now, given AI and grindflation).&lt;/p></description><content:encoded><![CDATA[<h2 id="intro">Intro</h2>
<p>I once found myself bored, though, not quite the useful kind of boredom.
I did not want to do my projects or something nice.
At the same time, I did not want to just spend it watching youtube or similar.
Thus, I thought, might as well do some leetcode.</p>
<p>I am not particularly fond of leetcode generally.
Some algorithms are nice, most are not, and I rarely learn as opposed to &ldquo;memorize&rdquo; patterns.
Doing union-find on leetcode rarely feels as nice as using it for constant-folding optimizations in a compiler.
Still, leetcode is necessary for a lot of interviews (for now, given AI and grindflation).</p>
<p>I went through a couple of algorithms, doing all of them in C to provide a modicum of joy.
Well as close as you can get to joy given extra annoyances.
E.g. leetcode C compiler setup fails on signed integer overflow.
This requires <code>-fsanitize=signed-integer-overflow</code> on my gcc setup.
This setting has its uses, but not when I just wanted to do a quick fnv1a.</p>
<p>Anyway, some problems went well, usually those I knew or those that are obvious to me.
Some did not, and I had to give up and look up the solution and try to understand it.
There were a few that relied on stuff that would take me a while to implement in C too.</p>
<p>One problem I had was <a href="https://leetcode.com/problems/contains-duplicate-ii/description/">&ldquo;261. Contains Duplicate II&rdquo;</a>.
I started with a simple naive double loop, essentially doing sliding window, but it left me wanting.
I was nearing the end of my leetcode energy, so I decided to look up the solution.
Hashtable, obviously. Very simple too, just a get and a put in a loop.</p>
<p>C does not have a hashtable natively.
Leetcode apparently provides <a href="https://support.leetcode.com/hc/en-us/articles/360011833974-What-are-the-environments-for-the-programming-languages">uthash</a>,
though I had never seen it in a wild (becomes obvious why when you see the ergonomics).
There is also the libc <a href="https://linux.die.net/man/3/hsearch">hash table</a>, but that is just one of POSIX April Fools jokes.</p>
<p>Anyway, I implemented one recently while testing out my C build system (Guile script, nothing too fancy, though it does cache).
It even has SSE2 SIMD and 64bit SWAR (SIMD Within A Register).
So I thought it would be good practice to do another one, and it very much was in hindsight.</p>
<h2 id="spec-matters">Spec matters</h2>
<p><code>got</code>, good-old-table as I called it, based itself on Abseil SwissTable.
Except I tried to avoid just reimplementing someone&rsquo;s solution. I only read an overview and skimped on the details.
It sounded simple enough.</p>
<p>There was a point though where I wondered why Abseil seemed to use tombstones.
But, I did not overthink it and thought I will learn it eventually.</p>
<p>I also thought about doing benchmarks to compare to at least C++ STL <code>std::unordered_map</code>.
But, that could have been too much work for a not too serious hashtable.
Especially since it was more useful to compare to more than one library.</p>
<p>Thus, I decided to do something similar for my solution.
But doing SWAR, and especially with <code>int</code>s as opposed to 64 bit values seemed like a waste of time.
A speedup, sure, but not that serious.
As such, I took to just doing linear probing.
It should be good enough, that&rsquo;s the base for a SwissTable anyway.</p>
<h2 id="absolute-failure">Absolute failure</h2>
<p>So straight from memory I implemented a simple linear probing hashtable.</p>
<p>Here is its struct:</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">struct</span> HashMap {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int</span> capacity;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int</span> length;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">int</span> valid, key, val;
</span></span><span style="display:flex;"><span>    } arr[];
</span></span><span style="display:flex;"><span>};</span></span></code></pre></div><p>Nothing too involved besides the zero length array trick. That is just used to allocate everything in a single allocation.</p>
<p>I originally did not include <code>length</code> for funny reasons, but it is necessary.
Well, you can optimize it out given that leetcode tests will never get that bad, but let&rsquo;s not get into that.</p>
<p>So after finishing up and cleaning up any immediate compile-time errors, I ran the solution on basic tests.
Success, and given how simple the solution really is, that was to be expected.</p>
<p>Here is how the &ldquo;solution function&rdquo; looks:</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">bool</span> <span style="color:#a6e22e">containsNearbyDuplicate</span>(<span style="color:#66d9ef">int</span> <span style="color:#f92672">*</span>nums, <span style="color:#66d9ef">int</span> numsSize, <span style="color:#66d9ef">int</span> k) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">struct</span> HashMap <span style="color:#f92672">*</span>map <span style="color:#f92672">=</span> <span style="color:#a6e22e">create_ht</span>(<span style="color:#ae81ff">64</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">int</span> i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; i <span style="color:#f92672">&lt;</span> numsSize; i<span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">int</span> <span style="color:#f92672">*</span>p <span style="color:#f92672">=</span> <span style="color:#a6e22e">get_ht</span>(map, nums[i]);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (p <span style="color:#f92672">&amp;&amp;</span> i <span style="color:#f92672">-</span> <span style="color:#f92672">*</span>p <span style="color:#f92672">&lt;=</span> k) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> true;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">put_ht</span>(<span style="color:#f92672">&amp;</span>map, nums[i], i);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> false;
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div><p>Freeing wastes cycles. Anyway,
satisfied, I hit the submit button&hellip; Timed out.</p>
<p>That was weird. Even if my table is not particularly fast, that was orders of magnitude too slow.
This was on a testcase of only 54500 <code>int</code>s, so it should not take that long.</p>
<p>I tried a couple of optimizations. E.g. valid field is not necessary since a key can never be more than 2^30.
I even tried to just increase the preallocated memory to see if that could improve runtime, even if just for this one.</p>
<p>All of them were bandaids at best. This was a fundamental issue.</p>
<h2 id="evil-assumptions">Evil assumptions</h2>
<p>So what was at the core of my <code>put</code> and <code>get</code> that made this many times slower than it should be.</p>
<p>Well I had a simple assumption. The valid entry could be anywhere after the initial index from hash.
If you do not see the problem immediately, think about it.
What made it worse was that I do not need to delete anything.</p>
<p>Every entry is allocated right after the last one.
So I was forcibly and completely needlessly going through the entire hash table.
For every call to <code>get</code> and most calls to <code>put</code>.
Most calls to <code>put</code> was because I had this useful thing called early return.</p>
<p>Ironic that the problem itself also has an early return.</p>
<p>The fix to <code>get</code> was adding an <code>else return 0;</code>. Immediate improvement.</p>
<p>Here is it so you can see the error of my ways:</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">int</span> i <span style="color:#f92672">=</span> init; i <span style="color:#f92672">&lt;</span> map<span style="color:#f92672">-&gt;</span>capacity; i<span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (map<span style="color:#f92672">-&gt;</span>arr[i].valid) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (map<span style="color:#f92672">-&gt;</span>arr[i].key <span style="color:#f92672">==</span> key) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span>map<span style="color:#f92672">-&gt;</span>arr[i].val;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  } <span style="color:#66d9ef">else</span> { <span style="color:#75715e">// just this part
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div><p>This is also a case of a small optimization hiding a better one.
The original code used <code>map-&gt;arr[i].valid &amp;&amp; map-&gt;arr[i].key == key</code>.
This couples them when they really should have been separate.</p>
<p>The fix to <code>put</code> was slightly longer, but essentially the same.</p>
<p>I only realized this after being very annoyed by it and testing it locally.</p>
<p>Thus, the problem was submitted and solved in reasonable time.</p>
<h2 id="unreasonable-time">Unreasonable time</h2>
<p>Ok, no. The time was ~800ms.
This counts as &ldquo;solved&rdquo;, but realistically extremely slow. This is array search in a loop speeds, if not worse.
The other solutions that leetcode presented were 100ms at worst. I was bottom 0.20%.</p>
<p>This prompted me to do some extra testing. Nothing too precise or involved, but enough to see the problems.
You can find the code for it <a href="https://github.com/Vasyl-Bodnar/contains-duplicate2-shenanigans">here</a>.
I will include the (incomplete) table here anyway to avoid spoilers:</p>
<table>
  <thead>
      <tr>
          <th>Name</th>
          <th>Time</th>
          <th>Time (-O2)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>fine.c</td>
          <td>5 ms</td>
          <td>4 ms</td>
      </tr>
      <tr>
          <td>fine.cpp</td>
          <td>26 ms</td>
          <td>6 ms</td>
      </tr>
      <tr>
          <td>with_got.c</td>
          <td>2227 ms</td>
          <td>691 ms</td>
      </tr>
      <tr>
          <td>awful.c</td>
          <td>8292 ms</td>
          <td>1893 ms</td>
      </tr>
  </tbody>
</table>
<p><code>fine.c</code> is the good solution, <code>awful.c</code> is the original bad solution.
There is also <code>fine.cpp</code> which uses <code>unordered_map</code> and <code>with_got.c</code> which uses my <code>got</code> library.</p>
<p>The only test case I used was the one I timed out on.
Thus, this table is a little useless to compare my table and <code>unordered_map</code> in my opinion.
But it does show you the magnitudes of difference from the bad ones.</p>
<p>Still, it is interesting to see that my <code>fine.c</code> is better or comparable to <code>unordered_map</code>.
Yet, the C++ solution is finished in ~80ms, mine is not.</p>
<p>I then decided to apply the <code>valid</code> field removal I talked about.
I was able to get the time down to ~350ms, which is still far from ideal, but more manageable.</p>
<p>I also tried the <code>uthash</code> that leetcode provides.
That one gave me ~90ms.
The ergonomics and documentation were questionable. Lots of macros, which are not friendly to leetcode.
You have to create your own entry and even include a magic field for a hash handle.
The primary purpose of which seemed to be iteration, but could be more.
Well now that is a little sad.</p>
<p>I then went on to do some stronger optimizations.
I have started using static preallocated memory.
Got rid of length for good with that.
Tested different preallocation sizes.
I found that allocating 256KB is enough to pass the tests with flying colors.
Any more slowed down, any less slowed down.</p>
<p>Done in 2ms, and 16MB of memory per what leetcode reports. C++ uses ~100MB and uthash uses ~60MB for comparison.
Beats ~98% and ~96% respectively.
Pride restored, technically.</p>
<p>I included this as <code>best.c</code> in that <a href="https://github.com/Vasyl-Bodnar/contains-duplicate2-shenanigans">same repo</a>.</p>
<h2 id="what-happened">What happened</h2>
<p>Going from ~800ms to ~350ms by just removing the valid field is not too surprising.
This simply uses less memory which means we have to seek less if we hit a collision.
Better cache usage and whatnot, possibly compiler optimizations too (leetcode uses -O2).</p>
<p>Going from ~350ms to 2ms is a different question.
But the trick is that by having so much capacity (256KB),
I basically turned <code>get</code> and <code>put</code> into array access operations, not &ldquo;amortized&rdquo;, actual O(1).
At that point you could just create buckets for every used number.</p>
<p>I could and probably should do some <code>perf</code> testing to see whether there is another obvious mistake.
E.g. if anything hashes to the end of the array that would always force a resize, a potentially serious issue.
Still, I am satisfied with getting 2ms for now.</p>
<h2 id="what-did-we-learn">What did we learn</h2>
<p>I should go fix my <code>got</code> library.
I call it not too serious, but I cannot allow this level of underperfomance.</p>
<p>The main lesson would probably be an importance of assumptions.
If you take in wrong or expensive assumptions, you may suffer.
On the other hand, if you take in correct assumptions, you can benefit a lot from it.
This often involves a tradeoff with generality like what I did for <code>best.c</code>, but other kinds exist too.</p>
<p>Update: <code>got</code> library should now be fixed (for now, before more bad assumption show up).
I updated the repo by adding the (new) version, it is roughly on par with <code>unordered_map</code> there.
Though, as I said, comparing hash tables based on one testcase and only on sorted integers is not a good idea.</p>
]]></content:encoded></item><item><title>To Debug or Apply AI</title><link>http://dearfortuna.blog/posts/to-debug-or-aai/</link><pubDate>Mon, 25 Aug 2025 23:04:15 -0400</pubDate><guid>http://dearfortuna.blog/posts/to-debug-or-aai/</guid><description>&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>Recently, I have come across a peculiar issue.
A bug most deranged.
A kind of unspoken true evil you would not easily find in your kind and forgiving languages.&lt;/p>
&lt;p>Can you find it in this cleaned-up function with totally all the necessary info?&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">send_data&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> sd, &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>txk, &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>data,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uint64_t&lt;/span> data_len, &lt;span style="color:#66d9ef">struct&lt;/span> header &lt;span style="color:#f92672">*&lt;/span>pack,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">struct&lt;/span> sockaddr_in &lt;span style="color:#f92672">*&lt;/span>saddr) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">struct&lt;/span> header spack;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">uint64_t&lt;/span> max_size &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">sizeof&lt;/span>(pack&lt;span style="color:#f92672">-&amp;gt;&lt;/span>packet) &lt;span style="color:#f92672">-&lt;/span> ABYTES;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pack&lt;span style="color:#f92672">-&amp;gt;&lt;/span>packet.off &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (data_len &lt;span style="color:#f92672">&amp;gt;&lt;/span> max_size) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">memcpy&lt;/span>(&lt;span style="color:#f92672">&amp;amp;&lt;/span>spack, pack, &lt;span style="color:#66d9ef">sizeof&lt;/span>(spack));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">send_pack&lt;/span>(sd, txk, data, max_size, &lt;span style="color:#f92672">&amp;amp;&lt;/span>spack, saddr);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pack&lt;span style="color:#f92672">-&amp;gt;&lt;/span>packet.off &lt;span style="color:#f92672">+=&lt;/span> max_size;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data_len &lt;span style="color:#f92672">-=&lt;/span> max_size;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="to-debug">To Debug&lt;/h2>
&lt;h3 id="is-to-find-wrongdoings">Is to find wrongdoings&lt;/h3>
&lt;p>Ignore the code quality for a bit.&lt;/p></description><content:encoded><![CDATA[<h2 id="intro">Intro</h2>
<p>Recently, I have come across a peculiar issue.
A bug most deranged.
A kind of unspoken true evil you would not easily find in your kind and forgiving languages.</p>
<p>Can you find it in this cleaned-up function with totally all the necessary info?</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">int</span> <span style="color:#a6e22e">send_data</span>(<span style="color:#66d9ef">int</span> sd, <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">char</span> <span style="color:#f92672">*</span>txk, <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">char</span> <span style="color:#f92672">*</span>data,
</span></span><span style="display:flex;"><span>              <span style="color:#66d9ef">uint64_t</span> data_len, <span style="color:#66d9ef">struct</span> header <span style="color:#f92672">*</span>pack,
</span></span><span style="display:flex;"><span>              <span style="color:#66d9ef">struct</span> sockaddr_in <span style="color:#f92672">*</span>saddr) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">struct</span> header spack;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">uint64_t</span> max_size <span style="color:#f92672">=</span> <span style="color:#66d9ef">sizeof</span>(pack<span style="color:#f92672">-&gt;</span>packet) <span style="color:#f92672">-</span> ABYTES;
</span></span><span style="display:flex;"><span>  pack<span style="color:#f92672">-&gt;</span>packet.off <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">while</span> (data_len <span style="color:#f92672">&gt;</span> max_size) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">memcpy</span>(<span style="color:#f92672">&amp;</span>spack, pack, <span style="color:#66d9ef">sizeof</span>(spack));
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">send_pack</span>(sd, txk, data, max_size, <span style="color:#f92672">&amp;</span>spack, saddr);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    pack<span style="color:#f92672">-&gt;</span>packet.off <span style="color:#f92672">+=</span> max_size;
</span></span><span style="display:flex;"><span>    data_len <span style="color:#f92672">-=</span> max_size;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div><h2 id="to-debug">To Debug</h2>
<h3 id="is-to-find-wrongdoings">Is to find wrongdoings</h3>
<p>Ignore the code quality for a bit.</p>
<p>There are only so many things that could fail in these few lines,
but I will spare you a need to write everything around it yourself (somehow).</p>
<p>The issue is an infinite loop, pretty simple.</p>
<p>We only have one loop (ignore <code>send_pack</code> for a moment),
so it&rsquo;s got to be that.
But what could be wrong? <code>max_size</code> is clearly being subtracted from <code>data_len</code>,
and nothing else really matters for this while loop.
We could prove correctness or use a debugger,
but it is easier to just put a few <code>printf</code>s like a true master.</p>
<p>Alright, after sprinkling them like <del>friends</del> mines on a minefield, we find a trace:</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span>data_len: <span style="color:#ae81ff">248</span>
</span></span><span style="display:flex;"><span>max_size: <span style="color:#ae81ff">512</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">memcpy</span>()..
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">send_pack</span>()..
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>data_len: <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>max_size: <span style="color:#ae81ff">512</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>pack<span style="color:#f92672">-&gt;</span>packet.off..
</span></span><span style="display:flex;"><span>data_len..
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>data_len: <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>max_size: <span style="color:#ae81ff">512</span></span></span></code></pre></div><h3 id="a-0-in-search-of-a-better-place">A 0 in search of a better place</h3>
<p>Hmm, that is a <code>0</code> where it was not supposed to be. Now, this would not be surprising if we modified <code>data_len</code> somewhere.
Maybe its address is messed with in some pointer I did not see.
But, it is a <code>const</code>, right?</p>
<p>Checking the full code again.. and no. <code>data_len</code> is a <code>const</code> after all.
There are no assignments or addresses misused.</p>
<p>Alright, there is clearly a change after the <code>memcpy</code> and <code>send_pack</code>. Got to be those then.
Well, the <code>memcpy</code> is fine, those are equal sizes and <code>sizeof</code> is not incorrect thankfully.</p>
<p>What about my <code>send_pack</code>?
I have tested it before and it seemed to work,
but maybe it is completely broken inside for this specific usecase.</p>
<p>Here it is in all its (cleanish) glory:</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">int</span> <span style="color:#a6e22e">send_pack</span>(<span style="color:#66d9ef">int</span> sd, <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">char</span> <span style="color:#f92672">*</span>txk, <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">char</span> <span style="color:#f92672">*</span>data,
</span></span><span style="display:flex;"><span>              <span style="color:#66d9ef">uint64_t</span> data_len, <span style="color:#66d9ef">struct</span> header <span style="color:#f92672">*</span>pack,
</span></span><span style="display:flex;"><span>              <span style="color:#66d9ef">struct</span> sockaddr_in <span style="color:#f92672">*</span>saddr) {
</span></span><span style="display:flex;"><span>  pack<span style="color:#f92672">-&gt;</span>packet.size <span style="color:#f92672">=</span> data_len <span style="color:#f92672">+</span> ABYTES;
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">memcpy</span>(pack<span style="color:#f92672">-&gt;</span>packet.payload, data <span style="color:#f92672">+</span> pack<span style="color:#f92672">-&gt;</span>packet.off, data_len);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">hash</span>(pack<span style="color:#f92672">-&gt;</span>packet.hash, <span style="color:#66d9ef">sizeof</span>(pack<span style="color:#f92672">-&gt;</span>packet.hash), 
</span></span><span style="display:flex;"><span>       pack<span style="color:#f92672">-&gt;</span>packet.payload, data_len, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">encrypt</span>(<span style="color:#f92672">&amp;</span>pack<span style="color:#f92672">-&gt;</span>packet, <span style="color:#ae81ff">0</span>, <span style="color:#f92672">&amp;</span>pack<span style="color:#f92672">-&gt;</span>packet,
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">sizeof</span>(pack<span style="color:#f92672">-&gt;</span>packet) <span style="color:#f92672">-</span> ABYTES, <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, pack<span style="color:#f92672">-&gt;</span>nonce, txk)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">sendto</span>(sd, pack, <span style="color:#66d9ef">sizeof</span>(<span style="color:#f92672">*</span>pack), <span style="color:#ae81ff">0</span>, (<span style="color:#66d9ef">struct</span> sockaddr <span style="color:#f92672">*</span>)saddr, 
</span></span><span style="display:flex;"><span>         <span style="color:#66d9ef">sizeof</span>(<span style="color:#f92672">*</span>saddr));
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div><p>Could look nicer, let&rsquo;s ignore that, not important or related at all.</p>
<p>Well, nothing immediately outstanding, but if you look closer.</p>
<h3 id="const-does-not-mean-const-always">Const does not mean const <em>always</em></h3>
<p><code>data_len</code> is used to decide the packet size and it is even in the <code>memcpy</code> and <code>hash</code>.
Of course, in this case size refers to the payload, but what is <code>max_size</code> set to again?</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">uint64_t</span> max_size <span style="color:#f92672">=</span> <span style="color:#66d9ef">sizeof</span>(pack<span style="color:#f92672">-&gt;</span>packet) <span style="color:#f92672">-</span> ABYTES;</span></span></code></pre></div><p>Hmm, I guess I never showed, but <code>pack-&gt;packet</code> is the entire struct, not the payload.</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">struct</span> packet {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">uint64_t</span> off;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">char</span> payload[<span style="color:#ae81ff">200</span>];
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">struct</span> header {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">struct</span> packet packet;
</span></span><span style="display:flex;"><span>};</span></span></code></pre></div><p>So getting size of that would give you too many bytes to work with (it is larger than just the payload).
Not so much that everything is corrupted, but a handful of things will be.
The end result is this friend:</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#a6e22e">memcpy</span>(pack<span style="color:#f92672">-&gt;</span>packet.payload, data <span style="color:#f92672">+</span> pack<span style="color:#f92672">-&gt;</span>packet.off, data_len);</span></span></code></pre></div><p>It will go too far and overwrite some stack space.
And, of course, what comes after that <code>spack</code> we use inside of <code>send_pack</code> on the stack?</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">struct</span> header spack;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">uint64_t</span> max_size <span style="color:#f92672">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">sizeof</span>(pack<span style="color:#f92672">-&gt;</span>packet) <span style="color:#f92672">-</span> ABYTES;</span></span></code></pre></div><p>So we just write too much. Stack corruption, yay!</p>
<p><code>data</code> has space because <code>packet.off</code> is also incremented by <code>max_size</code>, a.k.a. <code>0</code>.
And of course we blow past <code>spack</code> into <code>max_size</code>,
because both of them are right next to each other on stack.
<code>const</code> is just a compiler hint when you write to arbitrary stack memory.</p>
<p>And here I thought that using the stack was always superiour to <code>malloc</code>. Haha.</p>
<p>Well, this was all in the debugging build without really any optimizing,
so what about that <code>-O2</code>.</p>
<p>It just works. Ok, no it doesn&rsquo;t. But there is no infinite loop,
it just sends the packets as it is supposed to and ends the program.
Those packets are not correct and
the other end kindly lets me know it did not like what it had to receive.</p>
<p>Data is probably corrupted in some other ways (<code>max_size</code> is probably not on the stack anymore).
Thankfully fixing it (i.e. using <code>sizeof(pack-&gt;packet.payload)</code> in <code>max_size</code>)
makes it run perfectly on either build.
At least as far as I am aware and that is good enough for prod (i.e. me).</p>
<p>Thus, lessons learned. <del>Use a safer language</del>, <del>write nicer code</del>, ask AI?</p>
<h2 id="to-apply-ai">To Apply AI</h2>
<h3 id="it-is-not-included-solely-for-joke-purposes">It is not included solely for joke purposes</h3>
<p>Indeed, it did not take me long really,
probably not much longer than what it took you to get to here reading-wise.
Not the hardest bug, nor the most annoying one,
but more protein in code is rarely good.</p>
<p>Now I am not a vibe coder nor do I condone such violence upon the computer.
And I would not really say I use all that much AI tooling (certainly not the latest and greatest).
Still, I find it useful to occasionally indulge in these simple chatbots that most people would use.</p>
<p>Brainstorming, summarizing, finding info, maybe even being of help in debugging sometimes?</p>
<p>So I shall run some basic tests, will it (GPT-5, Claude Son 4) find this bug?</p>
<h3 id="tests-for-testss-sake">Tests for tests&rsquo;s sake</h3>
<p>First the basic basic, copy and paste the entire file (~300 LOC).
Same question to both:</p>
<blockquote>
<p>Can you fix this bug for me please. <code>send_data</code> is in an infinite loop. <strong>pasted code</strong></p></blockquote>
<p>Not the finest prompt engineering, but what do I know of the arcane.
This is realistic in that this is the info I know from just running it.
The total code size should not be an issue, it is not long, and not that dense.
Yet, both of these nice machines reply with essentially the same issue and fixes.</p>
<p>Has to be those offsets. Granted they are done inside <code>send_pack</code>,
so the bots must be confused upon seeing <code>send_data</code> without any offsets in the argument list.
They must assume that offset handling is broken and thus infinite loop somehow goes from there?</p>
<p>If I listened to the voices, I would be led astray.</p>
<p>Except what was that Claude Son 4?
Ah right, it has randomly suggested to use <code>pack-&gt;packet.payload</code>
in place of <code>pack-&gt;packet</code> in that cursed <code>sizeof</code>.</p>
<p>It was an off-hand change, but a correct solution nonetheless.</p>
<blockquote>
<p>Line 9: Also fixed max_size calculation to use sizeof(pack-&gt;packet.payload) instead of sizeof(pack-&gt;packet) - this ensures we&rsquo;re using the actual payload size</p></blockquote>
<p>I did not even notice it immediately.
GPT-5 did not even compete too, it has ignored that entirely.
I had repeated trials, but given the kinds of &ldquo;memory&rdquo; chatbots may use,
I am skeptical of how useful that would be.</p>
<p>Another test I prepared was feeding it just the essentials:
the structs, the <code>send_pack</code>, and the <code>send_data</code>, all you need. Same question.
(Yes, could be this mystical &ldquo;memory&rdquo;, but it is not the exact same code at least)</p>
<p>GPT-5 is doing the same as it did before.</p>
<p>Son 4 is still obsessed with offsets, but it has the right idea about it being <code>sizeof(payload)</code>.
Except that for some reasons it excluded <code>ABYTES</code>, authentication bytes,
which should count given that they are appended to the payload (i.e. in it)?</p>
<p>Ok, last test, what if I introduce it piecemeal.</p>
<p>First the <code>send_data</code>, front and center.
Same good propositions from GPT, those data offsets, underflow,
<code>max_size</code> is 0 but that was without solutions.
Though what do you know, GPT does not know about internals of <code>send_pack</code>.
Let us give it that next then. Same boring stuff about offsets again.
I share the structs, and GPT finally realizes that it indeed was that <code>sizeof</code> thing.</p>
<p>Claude&rsquo;s second dearest does not perform as well as you would think given above two tests.
Obsession over offsets given only <code>send_data</code> is expected.
But then the same happens with <code>send_pack</code>?
Well it probably fixed it for me according to previous function so whatever.
Finally, the structs open its eyes and it fixes the <code>sizeof</code>, while also removing <code>ABYTES</code>. Hmm.</p>
<h3 id="to-be-advised-by-the-enemy">To be advised by the enemy</h3>
<p>From these simple tests,
you could make a quick generalization of &ldquo;Claude likes big code&rdquo;,
and &ldquo;GPT likes chewing slowly&rdquo;.
I would also like to note that GPT-5 does not produce much text at all in comparison to Son 4,
which could be significant and even explain this difference.
But this is no peer reviewed study across three generations.
I would recommend to not dwell on these points too much, get your own conclusions.</p>
<h2 id="and-conclusion">And Conclusion</h2>
<p>The result is not the most impressive,
GPT-5 required specific massaging of pieces to get it right, which is annoying.
Also note that, if I did not know that the struct sizes were the issue,
I would probably not give them to GPT-5. Yet they were the last piece that it needed.</p>
<p>Claude did better, but in weird ways. Sure it got the right answer given the full code,
but that seems like an unnecessary divulgence (I have not hosted it on Github YET).
And when given less information,
it seems to have gotten more lost and started removing <code>ABYTES</code> for some reason.</p>
<p>Both also got heavy on offsets, basically all their tokens were on that.
It probably confused them too much.
Maybe it is wrong, but doubt is an enemy.</p>
<p>This is a hobby project in the end, I don&rsquo;t put too much focus on perfectness,
but I do care about learning and experiencing as much as possible myself.
Will I use AI for debugging here?
Maybe in desperation, after hours of incorrect turns.
I have yet to get there with this project. But soon enough.</p>
]]></content:encoded></item></channel></rss>