<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://blog.mattstuchlik.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.mattstuchlik.com/" rel="alternate" type="text/html" /><updated>2025-04-29T17:44:51+00:00</updated><id>https://blog.mattstuchlik.com/feed.xml</id><title type="html">Matt Stuchlik</title><entry><title type="html">HUGLO: Hyper-Ultra-Giga Low-Overhead Tracing Profiler for Ruby</title><link href="https://blog.mattstuchlik.com/2025/04/23/low-overhead-ruby-tracing.html" rel="alternate" type="text/html" title="HUGLO: Hyper-Ultra-Giga Low-Overhead Tracing Profiler for Ruby" /><published>2025-04-23T00:00:00+00:00</published><updated>2025-04-23T00:00:00+00:00</updated><id>https://blog.mattstuchlik.com/2025/04/23/low-overhead-ruby-tracing</id><content type="html" xml:base="https://blog.mattstuchlik.com/2025/04/23/low-overhead-ruby-tracing.html"><![CDATA[<p><img src="/assets/ruby-profiler-overview.png" alt="Profiler Overview" /></p>

<p>I’ve built what I think is a pretty neat Ruby tracing profiler. It captures four event streams: Ruby function calls, system calls, thread-state changes, and garbage-collection activity, while adding less than 30 ns of overhead per Ruby function call, low enough for continuous use in large-scale production systems.</p>

<p>As far as I know, no other Ruby tracer offers this mix of signals at this cost. If you’re aware of one, please let me know and I’ll add a note here (and remove a couple of adjectives from the title).</p>

<p>I haven’t open-sourced the code yet because it is very much in a proof-of-concept state and I’m busy with other projects. If you think it would be valuable to you, let me know. If I see enough interest I’ll move it up my priority list.</p>

<h2 id="overhead-measurement">Overhead Measurement</h2>

<p><img src="/assets/ruby-profiler-histogram.png" alt="Overhead Measurement" /></p>

<p>Above is a runtime histogram of 200 runs of a <a href="https://gist.github.com/s7nfo/6b8c8df58d72775d246ccce4b4f5ad90">sample workload</a> on an i5-13500 system running Ubuntu 24 compared to baseline Ruby and ruby-prof 1.7.1, the profiler that comes closest in terms of overhead. Estimated per-function-call overhead is 23 ns for this profiler and 538 ns for ruby-prof.</p>

<h2 id="why-trace-in-production">Why Trace in Production</h2>
<p>In a word: outliers. Sampling profilers show where a program spends its time <em>on average</em>. However, issues like high tail latency are, by definition, poorly represented in an average view. That’s where a tracing profiler comes in handy.</p>

<p>Let me illustrate this with a sophisticated SaaS simulator that handles a bunch of API calls and records the duration of each one:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>durations = []

100_000.times do
  start_time = Time.now

  api_handler

  durations &lt;&lt; Time.now - start_time
end
</code></pre></div></div>

<p>Let’s assume <code class="language-plaintext highlighter-rouge">api_handler</code> is a black-box function with a P99.9 latency SLO of &lt; 10ms. Running the snippet yields:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>P50 latency:     1.1 ms
P99.9 latency: 101.3 ms
</code></pre></div></div>

<p>Our median (P50) looks fine, but the P99.9 violates the SLO.</p>

<p>If you care about latency, you’re probably already running a sampling profiler, so let’s see what it shows:</p>

<p><img src="/assets/ruby-profiler-sampling.png" alt="Sampling Profiler Example" /></p>

<p>We see that two methods account for most of the runtime: <code class="language-plaintext highlighter-rouge">api_handler-&gt;Object#a</code> and <code class="language-plaintext highlighter-rouge">api_handler-&gt;Object#b</code>. Of those, <code class="language-plaintext highlighter-rouge">a</code> consumes the majority of the time slice. You might therefore conclude that optimizing <code class="language-plaintext highlighter-rouge">a</code> is the right move, but in this case, that would be a waste of effort!</p>

<p>To demonstrate this, let’s record a trace from one of the slow <code class="language-plaintext highlighter-rouge">api_handler</code> executions using our new profiler. First, we instrument the code:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gi">+ require 'tracing'
</span>
 durations = []
 SLA = 0.01

 100_000.times do
   start_time = Time.now
<span class="gi">+   Trace.reset
</span>
   api_handler

   duration = Time.now - start_time
<span class="gi">+  if duration &gt; SLA
+     Trace.save("api_call_trace.json")
+   end
</span>
   durations &lt;&lt; duration
<span class="p">end
</span></code></pre></div></div>

<p>Now let’s look at one of the slow traces:</p>

<p><img src="/assets/ruby-profiler-tracing.png" alt="Tracing Profiler Example" /></p>

<p>Here we see the exact opposite: <code class="language-plaintext highlighter-rouge">b</code> now consumes the majority of the time slice. So who’s right?</p>

<p>Let’s peek inside the <code class="language-plaintext highlighter-rouge">api_handler</code> black-box:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># api_handler always executes a, which sleeps for 1 ms
# 0.1% of the time it also executes b, which sleeps for 100 ms
def api_handler
  a
  if rand &gt;= 0.999
    b
  end
end

def a
  sleep(0.001)
end

def b
  sleep(0.1)
end
</code></pre></div></div>

<p>The sampling profiler correctly shows that <em>over the entire run</em>, we spend more time in <code class="language-plaintext highlighter-rouge">a</code>, but when chasing tail-latency issues, that is not what matters. Here, a tracing profiler is the right tool for the job and <code class="language-plaintext highlighter-rouge">b</code> is the right function to optimize.</p>

<p>One small additional advantage of tracing is that it gives us a true time axis: events actually occur in the order shown, unlike in a sampled flame graph. This can be quite helpful when debugging.</p>

<h2 id="thread-state-changes">Thread-State Changes</h2>

<p>Another event stream I’ll mention here is thread-state changes and why tracking them helps. You might assume that whenever you see a stack trace in a trace view, like the one outlined by the red box below, the code is actively executing.</p>

<p><img src="/assets/ruby-profiler-thread-state.png" alt="Tracing Profiler Thread States Example" /></p>

<p>But that isn’t necessarily the case. Notice the light-green <code class="language-plaintext highlighter-rouge">Runnable</code> slice and the dark-green <code class="language-plaintext highlighter-rouge">Running</code> slice in the example above. That track shows that the Ruby thread was not running at first, later became runnable, and finally began executing. In this case the reason is fairly clear when you inspect the stack trace: the thread is in <code class="language-plaintext highlighter-rouge">sleep</code>, waiting on a <code class="language-plaintext highlighter-rouge">futex</code>, and in the yellow-and-purple <code class="language-plaintext highlighter-rouge">CPU 12</code> track you can see the Swapper process running on the core instead.</p>

<p>But less obvious instances exist as well: if the CPU you’re running on is oversubscribed, the kernel scheduler may boop your thread off its core as it tries to give other threads time to run. If a code you own starts running slowly because someone else is stealing your cycles, you want to know!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Injecting syscall faults in Python and Ruby</title><link href="https://blog.mattstuchlik.com/2024/09/08/injecting-syscall-faults.html" rel="alternate" type="text/html" title="Injecting syscall faults in Python and Ruby" /><published>2024-09-08T00:00:00+00:00</published><updated>2024-09-08T00:00:00+00:00</updated><id>https://blog.mattstuchlik.com/2024/09/08/injecting-syscall-faults</id><content type="html" xml:base="https://blog.mattstuchlik.com/2024/09/08/injecting-syscall-faults.html"><![CDATA[<p>Since syscalls are near the very bottom of any software stack, their misbehavior can be particularly hard to test for. Stuff like running out of disk space, network connections timing out, or bumping into system limits all ultimately manifest as a syscall failing somewhere. If you want your code to be resilient to these kinds of failures, it sure would be nice if you could simulate these situations easily.</p>

<p>Now, you might already know <code class="language-plaintext highlighter-rouge">strace</code> lets you <a href="https://blog.mattstuchlik.com/2024/02/16/counting-syscalls-in-python.html">trace system calls</a>, but did you know it can also change their behavior? You can modify their input and output, inject errors and add time delays (though be aware of the <a href="#limitations">limitations</a>).</p>

<p>To demonstrate how it could be useful, I’ve added the ability to easily use this functionality from Python and Ruby to <a href="https://github.com/s7nfo/Cirron">Cirron</a> (my grab-bag of a project, that can also <a href="https://blog.mattstuchlik.com/2024/02/16/counting-syscalls-in-python.html">trace syscalls</a> and <a href="https://blog.mattstuchlik.com/2024/02/08/counting-cpu-instructions-in-python.html">track performance counters</a>). Now you can do things like:</p>

<p>Test how code handles insufficient space. (I’ll use Python for demonstration purposes here, check out the <a href="https://github.com/s7nfo/Cirron/blob/master/README.md">readme</a> for examples of how to do this in Ruby.)</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">cirron</span> <span class="kn">import</span> <span class="n">Injector</span>

<span class="n">injector</span> <span class="o">=</span> <span class="n">Injector</span><span class="p">()</span>
<span class="c1"># Make the "openat" syscall return the ENOSPC error.
</span><span class="n">injector</span><span class="p">.</span><span class="n">inject</span><span class="p">(</span><span class="s">"openat"</span><span class="p">,</span> <span class="s">"error"</span><span class="p">,</span> <span class="s">"ENOSPC"</span><span class="p">)</span>

<span class="c1"># All "openat" calls will return ENOSPC within this context.
</span><span class="k">with</span> <span class="n">injector</span><span class="p">:</span>
    <span class="c1"># Fails with "No space left on device".
</span>    <span class="n">f</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s">"test.txt"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span>

<span class="c1"># From here on "openat" behaves normally again.
</span></code></pre></div></div>

<p>Inject occasional errors and delays to network operations.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(...)</span>
<span class="c1"># Make every other "connect" syscall return the ETIMEDOUT error.
# when="2+2" means to perform the injection for the second syscall
# invocation and then again every two invocations.
</span><span class="n">injector</span><span class="p">.</span><span class="n">inject</span><span class="p">(</span><span class="s">"connect"</span><span class="p">,</span> <span class="s">"error"</span><span class="p">,</span> <span class="s">"ETIMEDOUT"</span><span class="p">,</span> <span class="n">when</span><span class="o">=</span><span class="s">"2+2"</span><span class="p">)</span>

<span class="c1"># Also add 1s of latency to "send".
</span><span class="n">injector</span><span class="p">.</span><span class="n">inject</span><span class="p">(</span><span class="s">"send"</span><span class="p">,</span> <span class="s">"delay_exit"</span><span class="p">,</span> <span class="s">"1s"</span><span class="p">)</span>

<span class="k">with</span> <span class="n">injector</span><span class="p">:</span>
    <span class="p">(...)</span>
</code></pre></div></div>

<p>Simulate signals being sent before particular syscalls.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(...)</span>
<span class="c1"># Simulate user pressing Ctrl+C before the first "read".
</span><span class="n">injector</span><span class="p">.</span><span class="n">inject</span><span class="p">(</span><span class="s">"read"</span><span class="p">,</span> <span class="s">"signal"</span><span class="p">,</span> <span class="s">"SIGINT"</span><span class="p">,</span> <span class="s">"when=1"</span><span class="p">)</span> 

<span class="k">with</span> <span class="n">injector</span><span class="p">:</span>
    <span class="p">(...)</span>
</code></pre></div></div>

<p>And more! In addition to the <code class="language-plaintext highlighter-rouge">error</code>, <code class="language-plaintext highlighter-rouge">delay_exit</code> and <code class="language-plaintext highlighter-rouge">signal</code> actions demonstrated above, it also supports <code class="language-plaintext highlighter-rouge">retval</code> for changing a return value without making it an error, <code class="language-plaintext highlighter-rouge">delay_enter</code> for delaying entry into a syscall rather than an exit and <code class="language-plaintext highlighter-rouge">poke_enter</code> and <code class="language-plaintext highlighter-rouge">poke_exit</code> for modifying the process memory on syscall entry or exit. See the <a href="https://man7.org/linux/man-pages/man1/strace.1.html">“Tampering” section of strace’s man page</a> for details on all these, including a description of the format of the <code class="language-plaintext highlighter-rouge">when</code> argument.</p>

<p>If you want to try this but are unsure what syscalls your code uses you can get a list of them easily with Cirron too:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">cirron</span> <span class="kn">import</span> <span class="n">Tracer</span>

<span class="c1"># Tracer records all syscalls made within the context.
</span><span class="k">with</span> <span class="n">Tracer</span><span class="p">()</span> <span class="k">as</span> <span class="n">t</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Hello!"</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="n">t</span><span class="p">)</span>
<span class="c1"># [Syscall(name='write', args='1, "Hello!\\n", 7', retval='7', duration='0.000197', timestamp='1725900869.238673', pid='438862')]
</span></code></pre></div></div>

<p><br /></p>

<h1 id="how">How?</h1>

<p>Cirron implements the <code class="language-plaintext highlighter-rouge">Injector</code> in the simplest way possible: it executes <code class="language-plaintext highlighter-rouge">strace</code> with the appropriate inject options and points it to the current process. After leaving the injection context the strace process is killed. If you were to make heavy use of this, it’s probably worth implementing the functionality directly, rather than executing strace every time (let me know if you’d find it useful if Cirron did this more efficiently).</p>

<p>So how does <code class="language-plaintext highlighter-rouge">strace</code> do this? <a href="https://en.wikipedia.org/wiki/Ptrace">Ptrace</a>! It <a href="https://github.com/strace/strace/blob/0f9f46096fa8da84e2e6a6646cd1e326bf7e83c7/src/strace.c#L1388">attaches</a> to (or <a href="https://github.com/strace/strace/blob/0f9f46096fa8da84e2e6a6646cd1e326bf7e83c7/src/strace.c#L569">seizes</a>; dramatic!) a process with <code class="language-plaintext highlighter-rouge">ptrace(PTRACE_ATTACH, ...)</code>. This causes the traced process to stop on (among other things) entry and exit from syscalls. Strace can then inspect and modify the program before letting it continue.</p>

<p>To inject a <a href="https://github.com/strace/strace/blob/0f9f46096fa8da84e2e6a6646cd1e326bf7e83c7/src/delay.c">delay</a>, as with <code class="language-plaintext highlighter-rouge">delay_enter</code> or <code class="language-plaintext highlighter-rouge">delay_exit</code>, strace simply waits before continuing the process.</p>

<p>To modify the syscall’s inputs or output it uses either <code class="language-plaintext highlighter-rouge">PTRACE_POKEDATA</code> (or <a href="https://linux.die.net/man/2/process_vm_writev">process_vm_writev</a>) to mess with the traced process’s memory (<code class="language-plaintext highlighter-rouge">poke_enter</code>, <code class="language-plaintext highlighter-rouge">poke_exit</code>) or <code class="language-plaintext highlighter-rouge">PTRACE_POKEUSER</code> to modify the USER area, containing the process’s registers state, which lets you, for example, change the return value (<code class="language-plaintext highlighter-rouge">error</code>, <code class="language-plaintext highlighter-rouge">retval</code>).</p>

<h1 id="limitations">Limitations</h1>

<p>There’s the obvious performance impact, particularly if simply using <code class="language-plaintext highlighter-rouge">strace</code> instead of using <code class="language-plaintext highlighter-rouge">ptrace</code> directly.</p>

<p>Also consider that making a syscall fail this way does not remove the side effects it might have: making a “write” call return an error will still (possibly) perform the “write”, it will just appear to have failed to the application. Similarly, delay injections occur either before syscall entry or after exit, which may impact the program differently compared to introducing a delay during the syscall itself.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Since syscalls are near the very bottom of any software stack, their misbehavior can be particularly hard to test for. Stuff like running out of disk space, network connections timing out, or bumping into system limits all ultimately manifest as a syscall failing somewhere. If you want your code to be resilient to these kinds of failures, it sure would be nice if you could simulate these situations easily.]]></summary></entry><entry><title type="html">Counting Bytes Faster Than You’d Think Possible</title><link href="https://blog.mattstuchlik.com/2024/07/21/fastest-memory-read.html" rel="alternate" type="text/html" title="Counting Bytes Faster Than You’d Think Possible" /><published>2024-07-21T00:00:00+00:00</published><updated>2024-07-21T00:00:00+00:00</updated><id>https://blog.mattstuchlik.com/2024/07/21/fastest-memory-read</id><content type="html" xml:base="https://blog.mattstuchlik.com/2024/07/21/fastest-memory-read.html"><![CDATA[<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet">
  <a href="https://twitter.com/s7nfo/status/1814337750109237399"></a> 
</blockquote>

<p><a href="https://blog.mattstuchlik.com/2024/07/12/summing-integers-fast.html">“Summing ASCII Encoded Integers on Haswell at the Speed of memcpy”</a> turned out more popular than I expected, which inspired me to take on another challenge on HighLoad: <a href="https://highload.fun/tasks/5">Counting uint8s</a>. I’m currently only #13 on the leaderboard, ~7% behind #1, but I have already learned some interesting things. In this post I’ll describe my complete solution (<a href="#the-source">skip to that</a>) including a surprising memory read pattern that achieves up to ~30% higher transfer rates on fully memory bound, single core workloads compared to naive sequential access, while apparently not being widely known (<a href="#the-magic-sauce">skip to that</a>).</p>

<p>As before, the program is tuned to the input spec and the HighLoad system: Intel Xeon E3-1271 v3 @ 3.60GHz, 512MB RAM, Ubuntu 20.04. It only uses AVX2, no AVX512.</p>

<h2 id="the-challenge">The Challenge</h2>

<blockquote>
  <p>“Print the number of bytes whose value equals 127 in a 250MB file full of bytes uniformly sampled from [0, 255] sent to standard input.”</p>
</blockquote>

<p>Nothing much to it! The solution presented here is ~550x faster than the following naive program.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint64_t</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">uint8_t</span> <span class="n">v</span><span class="p">;</span> <span class="n">std</span><span class="o">::</span><span class="n">cin</span> <span class="o">&gt;&gt;</span> <span class="n">v</span><span class="p">;)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">v</span> <span class="o">==</span> <span class="mi">127</span><span class="p">)</span> <span class="p">{</span>
        <span class="o">++</span><span class="n">count</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">count</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="the-kernel">The Kernel</h2>

<p>You’ll find the full source code of the solution at the end of the post. But first I’ll build up to how it works. The kernel is just three instructions long, so I went straight to an <code class="language-plaintext highlighter-rouge">__asm__</code> block (sorry!).</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">; rax is the base of the input</span>
<span class="c1">; rsi is an  offset to current chunk</span>
<span class="nf">vmovntdqa</span>    <span class="p">(</span><span class="o">%</span><span class="nb">rax</span><span class="p">,</span> <span class="o">%</span><span class="nb">rsi</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="o">%</span><span class="nv">ymm4</span>
<span class="c1">; ymm2 is a vector full of 127</span>
<span class="nf">vpcmpeqb</span>     <span class="o">%</span><span class="nv">ymm4</span><span class="p">,</span> <span class="o">%</span><span class="nv">ymm2</span><span class="p">,</span> <span class="o">%</span><span class="nv">ymm4</span>
<span class="c1">; ymm6 is an accumulator, whose bytes</span>
<span class="c1">; represent a running count of 127s</span>
<span class="c1">; at that position in the input chunk</span>
<span class="nf">vpsubb</span>       <span class="o">%</span><span class="nv">ymm4</span><span class="p">,</span> <span class="o">%</span><span class="nv">ymm6</span><span class="p">,</span> <span class="o">%</span><span class="nv">ymm6</span>
</code></pre></div></div>

<p>With this, we iterate over 32-byte chunks of the input and:</p>
<ul>
  <li>Load the chunk with <code class="language-plaintext highlighter-rouge">vmovntdqa</code> (it’s a non-temporal move just for style points, it doesn’t make a difference to runtime).</li>
  <li>Compare each byte in the chunk with <code class="language-plaintext highlighter-rouge">127</code> using <code class="language-plaintext highlighter-rouge">vpcmpeqb</code>, giving us back <code class="language-plaintext highlighter-rouge">0xFF</code> (aka <code class="language-plaintext highlighter-rouge">-1</code>) where the byte is equal to <code class="language-plaintext highlighter-rouge">127</code> and <code class="language-plaintext highlighter-rouge">0x00</code> elsewhere. For example <code class="language-plaintext highlighter-rouge">[125, 126, 127, 128, ...]</code> becomes <code class="language-plaintext highlighter-rouge">[0, 0, -1, 0, ...]</code>.</li>
  <li>Subtract the result of the comparison from an accumulator. Continuing the example above and assuming a zeroed accumulator, we’d get <code class="language-plaintext highlighter-rouge">[0, 0, 1, 0, ...]</code>.</li>
</ul>

<p>Then, to prevent this narrow accumulator from overflowing, we dump it into a wider one every once in a while with the following:</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">; ymm1 is a zero vector</span>
<span class="c1">; ymm6 is the narrow accumulator</span>
<span class="nf">vpsadbw</span>      <span class="o">%</span><span class="nv">ymm1</span><span class="p">,</span><span class="o">%</span><span class="nv">ymm6</span><span class="p">,</span><span class="o">%</span><span class="nv">ymm6</span>
<span class="c1">; ymm3 is a wide accumulator</span>
<span class="nf">vpaddq</span>       <span class="o">%</span><span class="nv">ymm3</span><span class="p">,</span><span class="o">%</span><span class="nv">ymm6</span><span class="p">,</span><span class="o">%</span><span class="nv">ymm3</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">vpsadbw</code> sums every eight bytes in the accumulator together into four 64-bit numbers, then <code class="language-plaintext highlighter-rouge">vpadddq</code> sums it with a wider accumulator, that we know won’t overflow and that we extract at the end to arrive at the final count.</p>

<p>So far nothing revolutionary. In fact you can find this kind of approach on StackOverflow: <a href="https://stackoverflow.com/questions/54541129/how-to-count-character-occurrences-using-simd">How to count character occurrences using SIMD</a>.</p>

<h2 id="the-magic-sauce">The Magic Sauce</h2>

<p>The thing with this challenge is, we do so little computation it’s significantly memory bound. I was reading through the typo-ridden Intel Optimization Manual looking for anything memory related when, on page 788, I encountered a description of the 4 hardware prefetchers. Three of them seemed to help purely with sequential access (what I was already doing), but one, the “Streamer”, had an interesting nuance:</p>

<blockquote>
  <p>“Detects and maintains up to 32 streams of data accesses. For each 4K byte page, you can maintain one forward and one backward stream can be maintained.”</p>
</blockquote>

<p>“For each 4K byte page”. Can you see where this is going? Instead of processing the whole input sequentially, we’ll interleave the processing of successive 4K pages. In this particular case interleaving 8 pages seems to be the optimum. We also unroll the kernel a bit and process a whole cache line (2x32 bytes) in each block.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define BLOCK(offset) \
    "vmovntdqa    " #offset " * 4096 (%6, %2, 1), %4\n\t" \
    "vpcmpeqb     %4, %7, %4\n\t" \
    "vmovntdqa    " #offset " * 4096 + 0x20 (%6, %2, 1), %3\n\t" \
    "vpcmpeqb     %3, %7, %3\n\t" \
    "vpsubb       %4, %0, %0\n\t" \
    "vpsubb       %3, %1, %1\n\t" \
</span></code></pre></div></div>

<p>We put 8 of these inside the main loop, with the <code class="language-plaintext highlighter-rouge">offset</code> set to 0 through 7.</p>

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 200">
  <style>
    @keyframes sequential {
      0% { transform: translateX(0); }
      100% { transform: translateX(310px); }
    }
    @keyframes interleaved {
      0% { transform: translateX(0); }
      100% { transform: translateX(35px); }
    }
    .sequential { animation: sequential 4s linear infinite; }
    .interleaved { animation: interleaved 4s linear infinite; }
    text { font-family: Arial, sans-serif; font-size: 14px; }
  </style>
  
  <!-- Sequential access -->
  <rect x="40" y="20" width="320" height="40" fill="#e0e0e0" stroke="#000" />
  <rect class="sequential" x="40" y="20" width="10" height="40" fill="#ff6b6b" />
  <text x="200" y="75" text-anchor="middle">Sequential Access</text>
  
  <!-- Interleaved access -->
  <g transform="translate(40, 100)">
    <rect x="0" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="40" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="80" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="120" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="160" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="200" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="240" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="280" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect class="interleaved" x="0" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="40" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="80" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="120" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="160" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="200" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="240" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="280" y="0" width="5" height="40" fill="#4ecdc4" />
  </g>
  <text x="200" y="155" text-anchor="middle">Interleaved Access (8 pages)</text>
</svg>

<p>This improves the score on HighLoad by some 15%, but if your kernel is even more memory bound, let’s say you just <code class="language-plaintext highlighter-rouge">vpaddb</code> the bytes to find their sum modulo 255, you can get up to 30% gain with this. Pretty cool for such a simple change!</p>

<p>Anyway, one other small thing: we add a prefetch for 4 cache lines ahead:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define BLOCK(offset) \
    "vmovntdqa    " #offset " * 4096 (%6, %2, 1), %4\n\t" \
    "vpcmpeqb     %4, %7, %4\n\t" \
    "vmovntdqa    " #offset " * 4096 + 0x20 (%6, %2, 1), %3\n\t" \
    "vpcmpeqb     %3, %7, %3\n\t" \
    "vpsubb       %4, %0, %0\n\t" \
    "vpsubb       %3, %1, %1\n\t" \
    "prefetcht0   " #offset " * 4096 + 4 * 64 (%6, %2, 1)\n\t"
</span></code></pre></div></div>

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 120">
  <style>
    @keyframes interleaved {
      0% { transform: translateX(0); }
      100% { transform: translateX(35px); }
    }
    .interleaved { animation: interleaved 4s linear infinite; }
    .prefetch { animation: interleaved 4s linear infinite; }
    text { font-family: Arial, sans-serif; font-size: 14px; }
  </style>
  
  <!-- Interleaved access with prefetch -->
  <g transform="translate(40, 20)">
    <rect x="0" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="40" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="80" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="120" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="160" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="200" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="240" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect x="280" y="0" width="40" height="40" fill="#e0e0e0" stroke="#000" />
    <rect class="prefetch" x="10" y="0" width="5" height="40" fill="#a9a9a9" opacity="0.5" />
    <rect class="prefetch" x="50" y="0" width="5" height="40" fill="#a9a9a9" opacity="0.5" />
    <rect class="prefetch" x="90" y="0" width="5" height="40" fill="#a9a9a9" opacity="0.5" />
    <rect class="prefetch" x="130" y="0" width="5" height="40" fill="#a9a9a9" opacity="0.5" />
    <rect class="prefetch" x="170" y="0" width="5" height="40" fill="#a9a9a9" opacity="0.5" />
    <rect class="prefetch" x="210" y="0" width="5" height="40" fill="#a9a9a9" opacity="0.5" />
    <rect class="prefetch" x="250" y="0" width="5" height="40" fill="#a9a9a9" opacity="0.5" />
    <svg x="280" y="0" width="40" height="40" overflow="hidden">
      <rect class="prefetch" x="10" y="0" width="5" height="40" fill="#a9a9a9" opacity="0.5" />
    </svg>
    <rect class="interleaved" x="0" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="40" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="80" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="120" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="160" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="200" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="240" y="0" width="5" height="40" fill="#4ecdc4" />
    <rect class="interleaved" x="280" y="0" width="5" height="40" fill="#4ecdc4" />
  </g>
  <text x="200" y="75" text-anchor="middle">Interleaved Access with Prefetch</text>
</svg>

<p>Why 4 lines ahead? I don’t have a good explanation for that, it’s simply what performs best. Below is a plot of runtime of the solution with prefetch strides from 0 to 100 on a different system (hence why the optimum is elsewhere here). As you can see the curve is quite complex.</p>

<p><img src="/assets/prefetch_stride_experiment.png" alt="Prefetch Stride Experiment" /></p>

<h2 id="the-source">The Source</h2>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;cstdint&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/mman.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/stat.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;fcntl.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;unistd.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;immintrin.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;cassert&gt;</span><span class="cp">
</span>
<span class="cp">#define BLOCK_COUNT 8
#define PAGE_SIZE 4096
#define TARGET_BYTE 127
</span>
<span class="cp">#define BLOCKS_8 \
    BLOCK(0)  BLOCK(1)  BLOCK(2)  BLOCK(3) \
    BLOCK(4)  BLOCK(5)  BLOCK(6)  BLOCK(7)
</span>
<span class="cp">#define BLOCK(offset) \
    "vmovntdqa    " #offset "*4096(%6,%2,1),%4\n\t" \
    "vpcmpeqb     %4,%7,%4\n\t" \
    "vmovntdqa    " #offset "*4096+0x20(%6,%2,1),%3\n\t" \
    "vpcmpeqb     %3,%7,%3\n\t" \
    "vpsubb       %4,%0,%0\n\t" \
    "vpsubb       %3,%1,%1\n\t" \
    "prefetcht0   " #offset "*4096+4*64(%6,%2,1)\n\t"
</span>

<span class="k">static</span> <span class="kr">inline</span>
<span class="n">__m256i</span> <span class="nf">hsum_epu8_epu64</span><span class="p">(</span><span class="n">__m256i</span> <span class="n">v</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">_mm256_sad_epu8</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">_mm256_setzero_si256</span><span class="p">());</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="nc">stat</span> <span class="n">sb</span><span class="p">;</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">fstat</span><span class="p">(</span><span class="n">STDIN_FILENO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sb</span><span class="p">)</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
    <span class="kt">size_t</span> <span class="n">length</span> <span class="o">=</span> <span class="n">sb</span><span class="p">.</span><span class="n">st_size</span><span class="p">;</span>

    <span class="kt">char</span><span class="o">*</span> <span class="n">start</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">char</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">mmap</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">,</span> <span class="n">length</span><span class="p">,</span> <span class="n">PROT_READ</span><span class="p">,</span> <span class="n">MAP_PRIVATE</span> <span class="o">|</span> <span class="n">MAP_POPULATE</span><span class="p">,</span> <span class="n">STDIN_FILENO</span><span class="p">,</span> <span class="mi">0</span><span class="p">));</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">start</span> <span class="o">!=</span> <span class="n">MAP_FAILED</span><span class="p">);</span>

    <span class="kt">uint64_t</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">__m256i</span> <span class="n">sum64</span> <span class="o">=</span> <span class="n">_mm256_setzero_si256</span><span class="p">();</span>
    <span class="kt">size_t</span> <span class="n">offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="n">__m256i</span> <span class="n">compare_value</span> <span class="o">=</span> <span class="n">_mm256_set1_epi8</span><span class="p">(</span><span class="n">TARGET_BYTE</span><span class="p">);</span>
    <span class="n">__m256i</span> <span class="n">acc1</span> <span class="o">=</span> <span class="n">_mm256_set1_epi8</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="n">__m256i</span> <span class="n">acc2</span> <span class="o">=</span> <span class="n">_mm256_set1_epi8</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="n">__m256i</span> <span class="n">temp1</span><span class="p">,</span> <span class="n">temp2</span><span class="p">;</span>

    <span class="k">while</span> <span class="p">(</span><span class="n">offset</span> <span class="o">+</span> <span class="n">BLOCK_COUNT</span><span class="o">*</span><span class="n">PAGE_SIZE</span> <span class="o">&lt;=</span> <span class="n">length</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">int</span> <span class="n">batch</span> <span class="o">=</span> <span class="n">PAGE_SIZE</span> <span class="o">/</span> <span class="mi">64</span><span class="p">;</span>
        <span class="k">asm</span> <span class="k">volatile</span><span class="p">(</span>
            <span class="s">".align 16</span><span class="se">\n\t</span><span class="s">"</span>
            <span class="s">"0:</span><span class="se">\n\t</span><span class="s">"</span>

            <span class="n">BLOCKS_8</span>

            <span class="s">"add          $0x40, %2</span><span class="se">\n\t</span><span class="s">"</span>
            <span class="s">"dec          %5</span><span class="se">\n\t</span><span class="s">"</span>
            <span class="s">"jg           0b"</span>
            <span class="o">:</span> <span class="s">"+x"</span> <span class="p">(</span><span class="n">acc1</span><span class="p">),</span> <span class="s">"+x"</span> <span class="p">(</span><span class="n">acc2</span><span class="p">),</span> <span class="s">"+r"</span> <span class="p">(</span><span class="n">offset</span><span class="p">),</span> <span class="s">"+x"</span> <span class="p">(</span><span class="n">temp1</span><span class="p">),</span> <span class="s">"+x"</span> <span class="p">(</span><span class="n">temp2</span><span class="p">),</span> <span class="s">"+r"</span> <span class="p">(</span><span class="n">batch</span><span class="p">)</span>
            <span class="o">:</span> <span class="s">"r"</span> <span class="p">(</span><span class="n">start</span><span class="p">),</span> <span class="s">"x"</span> <span class="p">(</span><span class="n">compare_value</span><span class="p">)</span>
            <span class="o">:</span> <span class="s">"cc"</span><span class="p">,</span> <span class="s">"memory"</span>
        <span class="p">);</span>

        <span class="n">offset</span> <span class="o">+=</span> <span class="p">(</span><span class="n">BLOCK_COUNT</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span><span class="o">*</span><span class="n">PAGE_SIZE</span><span class="p">;</span>

        <span class="n">sum64</span> <span class="o">=</span> <span class="n">_mm256_add_epi64</span><span class="p">(</span><span class="n">sum64</span><span class="p">,</span> <span class="n">hsum_epu8_epu64</span><span class="p">(</span><span class="n">acc1</span><span class="p">));</span>
        <span class="n">sum64</span> <span class="o">=</span> <span class="n">_mm256_add_epi64</span><span class="p">(</span><span class="n">sum64</span><span class="p">,</span> <span class="n">hsum_epu8_epu64</span><span class="p">(</span><span class="n">acc2</span><span class="p">));</span>

        <span class="n">acc1</span> <span class="o">=</span> <span class="n">_mm256_set1_epi8</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
        <span class="n">acc2</span> <span class="o">=</span> <span class="n">_mm256_set1_epi8</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">sum64</span> <span class="o">=</span> <span class="n">_mm256_add_epi64</span><span class="p">(</span><span class="n">sum64</span><span class="p">,</span> <span class="n">hsum_epu8_epu64</span><span class="p">(</span><span class="n">acc1</span><span class="p">));</span>
    <span class="n">sum64</span> <span class="o">=</span> <span class="n">_mm256_add_epi64</span><span class="p">(</span><span class="n">sum64</span><span class="p">,</span> <span class="n">hsum_epu8_epu64</span><span class="p">(</span><span class="n">acc2</span><span class="p">));</span>

    <span class="n">count</span> <span class="o">+=</span> <span class="n">_mm256_extract_epi64</span><span class="p">(</span><span class="n">sum64</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">count</span> <span class="o">+=</span> <span class="n">_mm256_extract_epi64</span><span class="p">(</span><span class="n">sum64</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
    <span class="n">count</span> <span class="o">+=</span> <span class="n">_mm256_extract_epi64</span><span class="p">(</span><span class="n">sum64</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
    <span class="n">count</span> <span class="o">+=</span> <span class="n">_mm256_extract_epi64</span><span class="p">(</span><span class="n">sum64</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>

    <span class="k">for</span> <span class="p">(;</span> <span class="n">offset</span> <span class="o">&lt;</span> <span class="n">length</span><span class="p">;</span> <span class="o">++</span><span class="n">offset</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">start</span><span class="p">[</span><span class="n">offset</span><span class="p">]</span> <span class="o">==</span> <span class="n">TARGET_BYTE</span><span class="p">)</span> <span class="p">{</span>
            <span class="o">++</span><span class="n">count</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">count</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>
<p>The page-interleaved read pattern seems surprisingly under-discussed and I don’t remember ever seeing it used in code in the wild. Curious! If you’re aware of it being used anywhere, let me know, I’d love to see it! And if I’m missing any other memory-based optimization, let me know too. :)</p>]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Summing ASCII Encoded Integers on Haswell at the Speed of memcpy</title><link href="https://blog.mattstuchlik.com/2024/07/12/summing-integers-fast.html" rel="alternate" type="text/html" title="Summing ASCII Encoded Integers on Haswell at the Speed of memcpy" /><published>2024-07-12T00:00:00+00:00</published><updated>2024-07-12T00:00:00+00:00</updated><id>https://blog.mattstuchlik.com/2024/07/12/summing-integers-fast</id><content type="html" xml:base="https://blog.mattstuchlik.com/2024/07/12/summing-integers-fast.html"><![CDATA[<style>
  code {
    font-size: 13px;
    white-space: pre-wrap;
    word-wrap: break-word;
}
</style>

<p>“Print the sum of 50 million ASCII-encoded integers uniformly sampled from [0, 2³¹−1], separated by a single new line and sent to standard input.”</p>

<p>On the surface, a trivial problem. But what if you wanted to go as fast as possible?</p>

<p>I’m currently one of the top ranked competitors in <a href="https://highload.fun/tasks/1">exactly that kind of challenge</a> and in this post I’ll show you a sketch of my best performing solution. I’ll leave out some of the µoptimizations and look-up table generation to keep this post short, easier to understand and to not completely obliterate the HighLoad leaderboard. Still, as far as I know nothing similar has been published yet, so I’m hoping you’ll find it interesting.</p>

<p>On the target system, my program runs about 320x faster than the following naive C++ solution (and is about 1,000,000x more fragile):</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint64_t</span> <span class="n">sum</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">cin</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">uint64_t</span> <span class="n">v</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cin</span> <span class="o">&gt;&gt;</span> <span class="n">v</span><span class="p">;</span>
    <span class="n">sum</span> <span class="o">+=</span> <span class="n">v</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">sum</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
</code></pre></div></div>

<p>I’ll write a companion post later on where I’ll describe one of the techniques used here in more detail: what I think is a fairly novel, though certainly very insane, way of initializing sparse, very-wide, zero-overhead lookup tables. The whole story of how I made it work is a little long to fit into this post.</p>

<h2 id="limitations">Limitations</h2>

<p>The program is over-fit to the input spec and the particular host it runs on (Intel Xeon E3-1271 v3 @ 3.60GHz, 512MB RAM, Ubuntu 20.04). Given the CPU, it only uses SIMD instructions up to AVX2, no AVX512. It assumes the input is exactly according to the spec and hence does zero error handling and even on such input will only produce correct results with probability &lt; 1, though very close to 1, depending on the parameters you choose.</p>

<h2 id="the-algorithm">The Algorithm</h2>

<p>Here’s the high-level overview: forget about parsing the input number-by-number and keeping a running sum! We’ll instead iterate over 32 byte chunks of the input using SIMD, from back to front, keeping track of the sum of the digits in each decimal place. In other words, if the input was “123\n45\n678”, we’ll remember that we’ve seen a total of 1 + 6 = 7 in the “hundreds”, 2 + 4 + 7 = 13 in the “tens” and 3 + 5 + 8 = 16 in the “ones” place. After we’re done processing the whole input, we get the final sum by multiplying these decimal place sums with powers of ten: 7*10² + 13*10¹ + 16*10⁰ = 846. Note that since the highest number we have to deal with is 2³¹−1, we have to track at most ⌈log₁₀(2³¹−1)⌉ = 10 decimal place sums.</p>

<p>How do we identify which byte of our input chunk is which decimal place? A look-up table. The mapping from the byte of an input chunk to its decimal place is determined by just two things: the location of newlines in the chunk and the length of the leftmost number in the previous chunk. In other words in a chunk like “???\n??\n???” that follows “???\n???\n???”, the first byte is always the 3rd decimal place, then the 2nd, etc., and the last byte is always the 4th decimal place, because it follows a number with 3 digits in the previous chunk.</p>

<p>That’s the high level, but of course the details of the implementation matter a lot too, so let’s look at the source code. This is the meat of the post, I’ve commented the code extensively to explain how it works and why it works that way. It might be hard to read on a phone, in which case I recommend bookmarking it and reading it on desktop later.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// First, the variables and constants we'll need:</span>

<span class="c1">// Pointer to the beginning of our input.</span>
<span class="kt">char</span><span class="o">*</span> <span class="n">start</span> <span class="o">=</span> <span class="p">(...)</span>

<span class="c1">// Offset from `start` to the first byte of the current 32 byte chunk.</span>
<span class="kt">uint64_t</span> <span class="n">offset</span> <span class="o">=</span> <span class="p">(...)</span>

<span class="c1">// SIMD vector full of ASCII '\n'.</span>
<span class="k">const</span> <span class="n">__m256i</span> <span class="n">ascii_zero</span> <span class="o">=</span> <span class="n">_m256_set1_epi8</span><span class="p">(</span><span class="mh">0x30</span><span class="p">);</span>

<span class="c1">// The size of the leftmost number in the previous chunk (remember, we iterate</span>
<span class="c1">// input back-to-front), which partially determines the decimal places of the</span>
<span class="c1">// rightmost number in the current chunk. Since we iterate from the end of the</span>
<span class="c1">// input, we can initialize this to 0.</span>
<span class="n">last_number_size</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

<span class="c1">// The decimal place sums we use to reconstruct the final sum as described in</span>
<span class="c1">// the overview.</span>
<span class="kt">uint64_t</span> <span class="n">decimal_sums</span><span class="p">[</span><span class="mi">10</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>

<span class="c1">// For efficiency, we accumulate decimal place sums into this vector and dump</span>
<span class="c1">// them into the `decimal_sums` array every `BATCH_SIZE` iterations.</span>
<span class="c1">// The layout of this vector is below, where a number represents an exponent</span>
<span class="c1">// of the power of ten the byte represents:</span>
<span class="c1">// [5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 | 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]</span>
<span class="c1">//                 ^ this byte accumulates 10^0 = "ones"      ^ this byte accumulates 10^2 = "hundreds"</span>
<span class="c1">// The somewhat unusual layout is motivated by the fact that AVX2 shuffle</span>
<span class="c1">// cannot move bytes across a lane boundary and because you expect to see</span>
<span class="c1">// more low decimal place digits.</span>
<span class="n">__m256i</span> <span class="n">sums_acc</span> <span class="o">=</span> <span class="n">_mm256_set1_epi8</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>

<span class="c1">// The decimal place sums accumulator can only accumulate so many chunks</span>
<span class="c1">// before overflowing. Worst case scenario is a '9' hitting the same</span>
<span class="c1">// accumulator slot twice per iteration of the main loop. Therefore the</span>
<span class="c1">// maximum safe accumulation batch size is 255 / (2 * 9) = 14. In practice</span>
<span class="c1">// you can increase it to almost twice that number without lowering the</span>
<span class="c1">// probability of correct output too much (at least for HighLoad).</span>
<span class="k">const</span> <span class="n">BATCH_SIZE</span> <span class="o">=</span> <span class="mi">14</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">batch</span> <span class="o">=</span> <span class="n">BATCH_SIZE</span><span class="p">;</span>

<span class="c1">// The final sum!</span>
<span class="kt">uint64_t</span> <span class="n">sum</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

<span class="c1">// Next, we ensure our chunks are 64 byte aligned (`start` is already</span>
<span class="c1">// aligned) by processing input byte-by-byte with a simple</span>
<span class="c1">// scalar algorithm until we reach an aligned offset, keeping track</span>
<span class="c1">// of the running sum and the length of the last number encountered.</span>
<span class="kt">int</span> <span class="n">exp</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">while</span> <span class="p">((</span><span class="n">offset</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">uint8_t</span> <span class="n">byte</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uint8_t</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">start</span> <span class="o">+</span> <span class="n">offset</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">byte</span> <span class="o">==</span> <span class="mh">0x0A</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">exp</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="n">last_number_size</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">sum</span> <span class="o">+=</span> <span class="n">exp</span> <span class="o">*</span> <span class="p">(</span><span class="n">byte</span> <span class="o">-</span> <span class="mh">0x30</span><span class="p">);</span>
        <span class="n">exp</span> <span class="o">*=</span> <span class="mi">10</span><span class="p">;</span>
        <span class="n">last_number_size</span><span class="o">++</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">offset</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>

<span class="p">(...)</span>

<span class="c1">// Now for the performance critical section!</span>
<span class="k">while</span> <span class="p">(</span><span class="n">offset</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Prefetch input for future iterations, 11 cache lines forward.</span>
    <span class="c1">// 11 chosen empirically.</span>
    <span class="n">_mm_prefetch</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="kt">char</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">start</span> <span class="o">+</span> <span class="n">offset</span> <span class="o">-</span> <span class="mi">11</span><span class="o">*</span><span class="mi">64</span><span class="p">),</span> <span class="n">_MM_HINT_T0</span><span class="p">);</span>

    <span class="c1">// Load a 32 byte chunk of input.</span>
    <span class="n">__m256i</span> <span class="n">input</span> <span class="o">=</span> <span class="n">_mm256_load_si256</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">__m256i</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">start</span> <span class="o">+</span> <span class="n">offset</span><span class="p">));</span>

    <span class="c1">// Subtract value of ASCII '0' (0x30) from each byte of the chunk.</span>
    <span class="c1">// This accomplishes two things:</span>
    <span class="c1">//  1) Bytes that represent digits will now hold the digit value instead</span>
    <span class="c1">//     of the ASCII code for the digit. i.e. '0' (value 0x30) will now be</span>
    <span class="c1">//     0x00, '1' (value 0x31) will now be 0x01, etc.</span>
    <span class="c1">//  2) Bytes that represent newlines will have their top bit set, because</span>
    <span class="c1">//     newlines are 0x0A, 0x0A - 0x30 = 0b11011010. This will become</span>
    <span class="c1">//     relevant in the next step.</span>
    <span class="n">input</span> <span class="o">=</span> <span class="n">_mm256_sub_epi8</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">ascii_zero</span><span class="p">);</span>

    <span class="c1">// Create a bitmask of newlines in the input chunk, i.e. given a chunk</span>
    <span class="c1">// "123\n456\n" return 0b00010001.</span>
    <span class="c1">// The bitmask is 32 bits, since the input is 32 bytes and we'll store</span>
    <span class="c1">// it in a 64 bit variable with the top half zeroed since we'll need</span>
    <span class="c1">// it in a 64 bit context later on when calculating our look-up table</span>
    <span class="c1">// location (this generates better assembly).</span>
    <span class="c1">// This is where 2) from the comment above comes into play. You might</span>
    <span class="c1">// be tempted to say we need `cmpeq(input, 0x0A)` before this `movemask`,</span>
    <span class="c1">// but `movemask` only looks at the top bit of each byte to</span>
    <span class="c1">// decide the value of the bit in the bitmask, so the fact that the `sub`</span>
    <span class="c1">// sets the top bit for each newline, but not for digits, is sufficient.</span>
    <span class="kt">uint64_t</span> <span class="n">mask</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint32_t</span><span class="p">)</span><span class="n">_mm256_movemask_epi8</span><span class="p">(</span><span class="n">input</span><span class="p">);</span>

    <span class="c1">// The location of the mappings from input bytes to decimal places is</span>
    <span class="c1">// determined by the mask and the leftmost number in the previous chunk.</span>
    <span class="c1">// There are two mappings for each (mask, last_number_size) pair, 32 bytes</span>
    <span class="c1">// each, for one cache line in total and there are 11 of them per newline</span>
    <span class="c1">// mask, because last_number_size can be 0 to 10.</span>
    <span class="c1">// Here you might be tempted to say we should modify our look-up table</span>
    <span class="c1">// structure to let us compute the location as</span>
    <span class="c1">// !16! * 64 * mask + 64 * last_number_size.</span>
    <span class="c1">// Since we'd only multiply by powers of two, this does result in nicer</span>
    <span class="c1">// assembly: `imul` (latency 3), `shl` (latency 1) turns into `shl`, `shl`,</span>
    <span class="c1">// but it's ultimately much slower due to cache associativity</span>
    <span class="c1">// issues (nice overview of the problem is for example here:</span>
    <span class="c1">// https://en.algorithmica.org/hpc/cpu-cache/associativity/)</span>
    <span class="kt">uint64_t</span> <span class="n">lut_idx</span> <span class="o">=</span> <span class="mi">11</span> <span class="o">*</span> <span class="mi">64</span> <span class="o">*</span> <span class="n">mask</span> <span class="o">+</span> <span class="mi">64</span> <span class="o">*</span> <span class="n">last_number_size</span>

    <span class="c1">// Now we dereference the location to get the two mappings.</span>
    <span class="c1">// This is a point where you should be a little confused:</span>
    <span class="c1">// 1) We're dereferencing the index by itself, not as an offset into an</span>
    <span class="c1">//    array base.</span>
    <span class="c1">// 2) The total range of the lut_index variable is very roughly 0 to 2^42,</span>
    <span class="c1">//   42 bits address space or some 4TB. That's more than the 500MB RAM we</span>
    <span class="c1">//   have available and more than you could create with a normal array</span>
    <span class="c1">//   literal.</span>
    <span class="c1">// On the positive side the lookup table is very sparse: thanks to the</span>
    <span class="c1">// specific input distribution, we only have O(1,000s) chunks of mappings</span>
    <span class="c1">// spread over the whole 42 bit address space.</span>
    <span class="c1">// This is the part that I'll explain in more detail in a follow up post,</span>
    <span class="c1">// but for now you can imagine as if we `mmap` and `memcpy` all the mappings</span>
    <span class="c1">// to the right addresses at the start of the program. Let me know if you</span>
    <span class="c1">// think you know how to do it ~zero-cost! :)</span>
    <span class="c1">// Before I came up with this approach I used a derived index based on the</span>
    <span class="c1">// size of the numbers in the chunk using chained `tzcnt`. This shrinks the</span>
    <span class="c1">// index space to (barely) fit in a normal look up table, but the index</span>
    <span class="c1">// computation was one long dependency chain with high latency instructions</span>
    <span class="c1">// and ended up being almost 50% of the runtime.</span>
    <span class="n">__m256i</span> <span class="n">shuffle_ctrl1</span> <span class="o">=</span> <span class="n">_mm256_loadu_si256</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">__m256i</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">lut_idx</span><span class="p">));</span>
    <span class="n">__m256i</span> <span class="n">shuffle_ctrl2</span> <span class="o">=</span> <span class="n">_mm256_loadu_si256</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">__m256i</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">lut_idx</span> <span class="o">+</span> <span class="mi">32</span><span class="p">));</span>

    <span class="c1">// When I talked about a mapping from input bytes to decimal places, it's</span>
    <span class="c1">// really a shuffle control mask that moves input bytes into the same</span>
    <span class="c1">// layout that the `sums_acc` vector has.</span>
    <span class="c1">// This is another one of those places where the specific input distribution</span>
    <span class="c1">// really matters:</span>
    <span class="c1">// There are 4 spots for "ones" in `sums_acc`. If our input chunk consisted</span>
    <span class="c1">// of only 1 digit numbers, "1\n2\n\3...", we'd need to perform the following</span>
    <span class="c1">// `shuffle`, `add` procedure up to 32 / 2 / 4 = 4 times.</span>
    <span class="c1">// Fortunately for us, this turns out to be very unlikely and we are almost</span>
    <span class="c1">// guaranteed to be able to completely accumulate the chunk within</span>
    <span class="c1">// 2 `shuffles`, so that is what we do in exchange for occasionally</span>
    <span class="c1">// producing incorrect results.</span>
    <span class="c1">// One of my solutions had a neat compression scheme here:</span>
    <span class="c1">// AVX2 `shuffle` only shuffles within each 16 byte lane of the full 32 byte</span>
    <span class="c1">// register. It therefore only uses the lower 4 bits of the shuffle control</span>
    <span class="c1">// mask so you can trivially pack two of them into one 32 byte vector.</span>
    <span class="c1">// Sort of -- it also uses the top bit to let you zero out a byte, which</span>
    <span class="c1">// we use a fair bit, (as you can imagine, by the second shuffle we've</span>
    <span class="c1">// accumulated most of the bytes of the input). Fortunately,</span>
    <span class="c1">// we are guaranteed to have at least one newline in each lane and since</span>
    <span class="c1">// we do not care about its value, we can zero it out at the start of each</span>
    <span class="c1">// iteration and point bytes that should to be zero to it, rather than</span>
    <span class="c1">// zeroing them out using the top bit.</span>
    <span class="c1">// I was very excited when I came up with this, but it turned out not</span>
    <span class="c1">// to do much performance-wise :) (Haswell can handle two loads per</span>
    <span class="c1">// cycle and the shuffle control maps are on a single cache line.)</span>
    <span class="n">__m256i</span> <span class="n">shuffled_input1</span> <span class="o">=</span> <span class="n">_mm256_shuffle_epi8</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">shuffle_ctrl1</span><span class="p">);</span>
    <span class="n">__m256i</span> <span class="n">shuffled_input2</span> <span class="o">=</span> <span class="n">_mm256_shuffle_epi8</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">shuffle_ctrl2</span><span class="p">);</span>

    <span class="c1">// Shuffled inputs 1 &amp; 2 are now in the correct layout for us to add them</span>
    <span class="c1">// directly to the decimal place sums accumulator.</span>
    <span class="n">sums_acc</span> <span class="o">=</span> <span class="n">_m256_add_epi8</span><span class="p">(</span><span class="n">sums_acc</span><span class="p">,</span> <span class="n">shuffled_input1</span><span class="p">);</span>
    <span class="n">sums_acc</span> <span class="o">=</span> <span class="n">_m256_add_epi8</span><span class="p">(</span><span class="n">sums_acc</span><span class="p">,</span> <span class="n">shuffled_input2</span><span class="p">);</span>

    <span class="c1">// This stores the size of the leftmost number for the next iteration.</span>
    <span class="c1">// Note that on Haswell this will generate `xor B, B` in addition to</span>
    <span class="c1">// `tzcnt A, B`. This is meant as a fix for a false dependency bug on</span>
    <span class="c1">// bunch of BMI instructions on this µarch.</span>
    <span class="c1">// In our case the fix is counterproductive because we're not bottlenecked</span>
    <span class="c1">// on the latency of this instruction. I don't know of any way to bypass</span>
    <span class="c1">// that `xor` other than using __asm__ directly.</span>
    <span class="c1">// (More on the bug:</span>
    <span class="c1">// https://stackoverflow.com/questions/25078285/replacing-a-32-bit-loop-counter-with-64-bit-introduces-crazy-performance-deviati)</span>
    <span class="n">last_number_size</span> <span class="o">=</span> <span class="n">_tzcnt_u32</span><span class="p">(</span><span class="n">mask</span><span class="p">);</span>

    <span class="c1">// Once we accumulate `BATCH_SIZE` chunks in `sums_acc`, dump them into</span>
    <span class="c1">// the sums array.</span>
    <span class="n">batch</span><span class="o">--</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">batch</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">batch</span> <span class="o">=</span> <span class="n">BATCH_SIZE</span><span class="p">;</span>
        <span class="c1">// Extract all accumulated "ones"...</span>
        <span class="n">decimal_sums</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+=</span> <span class="n">_m256_extract_epi8</span><span class="p">(</span><span class="n">sums_acc</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>
        <span class="n">decimal_sums</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+=</span> <span class="n">_m256_extract_epi8</span><span class="p">(</span><span class="n">sums_acc</span><span class="p">,</span> <span class="mi">15</span><span class="p">);</span>
        <span class="n">decimal_sums</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+=</span> <span class="n">_m256_extract_epi8</span><span class="p">(</span><span class="n">sums_acc</span><span class="p">,</span> <span class="mi">21</span><span class="p">);</span>
        <span class="n">decimal_sums</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+=</span> <span class="n">_m256_extract_epi8</span><span class="p">(</span><span class="n">sums_acc</span><span class="p">,</span> <span class="mi">31</span><span class="p">);</span>
        <span class="p">(...)</span>
        <span class="c1">// ...and up to 10^9's.</span>
        <span class="n">decimal_sums</span><span class="p">[</span><span class="mi">9</span><span class="p">]</span> <span class="o">+=</span> <span class="n">_m256_extract_epi8</span><span class="p">(</span><span class="n">sums_acc</span><span class="p">,</span> <span class="mi">6</span><span class="p">);</span>
        <span class="n">decimal_sums</span><span class="p">[</span><span class="mi">9</span><span class="p">]</span> <span class="o">+=</span> <span class="n">_m256_extract_epi8</span><span class="p">(</span><span class="n">sums_acc</span><span class="p">,</span> <span class="mi">22</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// Move the offset to the next batch.</span>
    <span class="n">offset</span> <span class="o">-=</span> <span class="mi">32</span><span class="p">;</span>
<span class="p">}</span>

<span class="p">(...)</span>

<span class="c1">// All that's left to do is to multiply our accumulated decimal place sums by</span>
<span class="c1">// the right power of ten and sum to get the final sum.</span>
<span class="n">exp</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">sum</span> <span class="o">+=</span> <span class="n">decimal_sums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">exp</span><span class="p">;</span>
    <span class="n">exp</span> <span class="o">*=</span> <span class="mi">10</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// And there it is!</span>
<span class="n">print</span><span class="p">(</span><span class="n">sum</span><span class="p">)</span>
</code></pre></div></div>

<h1 id="fin">Fin</h1>
<p>Let me know if you have any feedback and thank you to the HighLoad community on Telegram, especially gracefu and Jack Frigaard.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">The Syscall Showdown: CRuby writes files with 40% fewer syscalls than CPython?</title><link href="https://blog.mattstuchlik.com/2024/07/07/syscall-showdown.html" rel="alternate" type="text/html" title="The Syscall Showdown: CRuby writes files with 40% fewer syscalls than CPython?" /><published>2024-07-07T00:00:00+00:00</published><updated>2024-07-07T00:00:00+00:00</updated><id>https://blog.mattstuchlik.com/2024/07/07/syscall-showdown</id><content type="html" xml:base="https://blog.mattstuchlik.com/2024/07/07/syscall-showdown.html"><![CDATA[<p>We’ve released a new version of <a href="https://github.com/s7nfo/Cirron">Cirron</a> that can now trace syscalls and record performance counters for individual lines of Ruby code, just like it could already do for Python (more <a href="http://blog.mattstuchlik.com/2024/02/08/counting-cpu-instructions-in-python.html">here</a> and <a href="http://blog.mattstuchlik.com/2024/02/16/counting-syscalls-in-python.html">here</a>). It makes it very easy to quickly inspect what’s happening in any section of your code and even assert what should be happening in tests, for example.</p>

<p>To put it through its paces I’ve compared what syscalls each language uses for several common patterns: File IO, generating random numbers, telling time and even just printing a string.</p>

<h3 id="file-io">File IO</h3>

<p>Let’s start with something surprising right away. Here are the snippets under investigation, simply writing a string to a file (I’ll be omitting the Cirron setup from the snippets later on):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># python
</span><span class="k">with</span> <span class="n">Cirron</span><span class="p">.</span><span class="n">Tracer</span><span class="p">()</span> <span class="k">as</span> <span class="n">t</span><span class="p">:</span>
    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"test.txt"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="s">"Hello, File I/O!"</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ruby</span>
<span class="n">t</span> <span class="o">=</span> <span class="no">Cirron</span><span class="o">::</span><span class="n">tracer</span> <span class="k">do</span>
  <span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="s2">"test.txt"</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span>
    <span class="n">f</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="s2">"Hello, File I/O!"</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>And here then, are the system calls each language makes. I’ve highlighted calls that aren’t made by the other language and added approximate time spent in each call on my system:</p>

<style>
    .highlight { background-color: #ffffcc; }
    .duration {
        align-self: flex-end !important;
        font-size: 10px !important;
        color: #666 !important;
        padding: 1px 3px !important;
        border-radius: 2px !important;
        margin-top: 5px !important;
    }
</style>

<table>
    <tr>
        <th>Python</th>
        <th>Ruby</th>
    </tr>
    <tr>
        <td>
            <a href="https://linux.die.net/man/2/openat">openat</a>(AT_FDCWD, "test.txt", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 3
            <span class="duration">~80µs</span>
        </td>
        <td>
            <a href="https://linux.die.net/man/2/openat">openat</a>(AT_FDCWD, "test.txt", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 5
            <span class="duration">~80µs</span>
        </td>
    </tr>
    <tr>
        <td class="highlight">
            <a href="http://man.he.net/man2/newfstatat">newfstatat</a>(3, "", {st_mode=S_IFREG|0644, st_size=0, ...}, AT_EMPTY_PATH) = 0
            <span class="duration">~20µs</span>
        </td>
        <td></td>
    </tr>
    <tr>
        <td>
            <a href="https://linux.die.net/man/2/ioctl">ioctl</a>(3, TCGETS, 0x7ffe559ef8b0) = -1 ENOTTY
            <span class="duration">~20µs</span>
        </td>
        <td>
            <a href="https://linux.die.net/man/2/ioctl">ioctl</a>(5, TCGETS, 0x7ffe2a8d9e90) = -1 ENOTTY
            <span class="duration">~20µs</span>
        </td>
    </tr>
    <tr>
        <td class="highlight">
            <a href="https://linux.die.net/man/2/lseek">lseek</a>(3, 0, SEEK_CUR) = 0
            <span class="duration">~20µs</span>
        </td>
        <td></td>
    </tr>
    <tr>
        <td class="highlight">
            <a href="https://linux.die.net/man/2/lseek">lseek</a>(3, 0, SEEK_CUR) = 0
            <span class="duration">~20µs</span>
        </td>
        <td></td>
    </tr>
    <tr>
        <td>
            <a href="https://linux.die.net/man/2/write">write</a>(3, "Hello, File I/O!", 16) = 16
            <span class="duration">~30µs</span>
        </td>
        <td>
            <a href="https://linux.die.net/man/2/write">write</a>(5, "Hello, File I/O!", 16) = 16
            <span class="duration">~30µs</span>
        </td>
    </tr>
    <tr>
        <td>
            <a href="https://linux.die.net/man/2/close">close</a>(3) = 0
            <span class="duration">~80µs</span>
        </td>
        <td>
            <a href="https://linux.die.net/man/2/close">close</a>(5) = 0
            <span class="duration">~80µs</span>
        </td>
    </tr>
</table>

<p>Both start off with <a href="https://linux.die.net/man/2/openat">openat</a> to open <code class="language-plaintext highlighter-rouge">test.txt</code> in the current directory.
Python then, unlike Ruby, uses <a href="http://man.he.net/man2/newfstatat">newfstat</a> to figure out what buffer size to use by looking at <code class="language-plaintext highlighter-rouge">st_blksize</code><sup id="fnref:0" role="doc-noteref"><a href="#fn:0" class="footnote" rel="footnote">1</a></sup>.
Both languages then use <a href="https://linux.die.net/man/2/ioctl">ioctl</a> to figure out whether the file descriptor is a TTY.</p>

<p>So far as expected, but then… Python decides to <a href="https://linux.die.net/man/2/lseek">lseek</a> to the start of the file twice. This doesn’t seem strictly necessary and a quick look through the cPython codebase didn’t reveal why this is happening. If you know, let me know!</p>

<p>Anyway: finally both languages use <a href="https://linux.die.net/man/2/write">write</a> to write our string and <a href="https://linux.die.net/man/2/close">close</a> to close the file descriptor.</p>

<p>How about reading a file?</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># python
</span><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"test.txt"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>
</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ruby</span>
<span class="n">content</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="s2">"test.txt"</span><span class="p">)</span>
</code></pre></div></div>

<table>
    <tr>
        <th>Python</th>
        <th>Ruby</th>
    </tr>
    <tr>
        <td>
            <a href="https://linux.die.net/man/2/openat">openat</a>(AT_FDCWD, "test.txt", O_RDONLY|O_CLOEXEC) = 3
            <span class="duration">~30µs</span>
        </td>
        <td>
            <a href="https://linux.die.net/man/2/openat">openat</a>(AT_FDCWD, "test.txt", O_RDONLY|O_CLOEXEC) = 5
            <span class="duration">~30µs</span>
        </td>
    </tr>
    <tr>
        <td class="highlight">
            <a href="http://man.he.net/man2/newfstatat">newfstatat</a>(3, "", {st_mode=S_IFREG|0644, st_size=16, ...}, AT_EMPTY_PATH) = 0
            <span class="duration">~20µs</span>
        </td>
        <td></td>
    </tr>
    <tr>
        <td>
            <a href="https://linux.die.net/man/2/ioctl">ioctl</a>(3, TCGETS, 0x7ffe559ef8b0) = -1 ENOTTY
            <span class="duration">~20µs</span>
        </td>
        <td>
            <a href="https://linux.die.net/man/2/ioctl">ioctl</a>(5, TCGETS, 0x7ffe2a8d9fd0) = -1 ENOTTY
            <span class="duration">~20µs</span>
        </td>
    </tr>
    <tr>
        <td class="highlight">
            <a href="https://linux.die.net/man/2/lseek">lseek</a>(3, 0, SEEK_CUR) = 0
            <span class="duration">~20µs</span>
        </td>
        <td></td>
    </tr>
    <tr>
        <td class="highlight">
            <a href="https://linux.die.net/man/2/lseek">lseek</a>(3, 0, SEEK_CUR) = 0
            <span class="duration">~20µs</span>
        </td>
        <td></td>
    </tr>
    <tr>
        <td>
            <a href="http://man.he.net/man2/newfstatat">newfstatat</a>(3, "", {st_mode=S_IFREG|0644, st_size=16, ...}, AT_EMPTY_PATH) = 0
            <span class="duration">~20µs</span>
        </td>
        <td>
            <a href="http://man.he.net/man2/newfstatat">newfstatat</a>(5, "", {st_mode=S_IFREG|0644, st_size=16, ...}, AT_EMPTY_PATH) = 0
            <span class="duration">~20µs</span>
        </td>
    </tr>
    <tr>
        <td></td>
        <td class="highlight">
            <a href="https://linux.die.net/man/2/lseek">lseek</a>(5, 0, SEEK_CUR) = 0
            <span class="duration">~20µs</span>
        </td>
    </tr>
    <tr>
        <td>
            <a href="https://linux.die.net/man/2/read">read</a>(3, "Hello, File I/O!", 17) = 16
            <span class="duration">~20µs</span>
        </td>
        <td>
            <a href="https://linux.die.net/man/2/read">read</a>(5, "Hello, File I/O!", 16) = 16
            <span class="duration">~20µs</span>
        </td>
    </tr>
    <tr>
        <td>
            <a href="https://linux.die.net/man/2/read">read</a>(3, "", 1) = 0
            <span class="duration">~20µs</span>
        </td>
        <td>
            <a href="https://linux.die.net/man/2/read">read</a>(5, "", 8192) = 0
            <span class="duration">~20µs</span>
        </td>
    </tr>
    <tr>
        <td>
            <a href="https://linux.die.net/man/2/close">close</a>(3) = 0
            <span class="duration">~20µs</span>
        </td>
        <td>
            <a href="https://linux.die.net/man/2/close">close</a>(5) = 0
            <span class="duration">~20µs</span>
        </td>
    </tr>
</table>

<p>Nothing terribly surprising here. Python still doing its double <a href="https://linux.die.net/man/2/lseek">lseek</a> and Ruby, probably not wanting to be left behind, adds an <a href="https://linux.die.net/man/2/lseek">lseek</a> of its own (also not strictly necessary, as far as I can tell). Note how <a href="https://linux.die.net/man/2/openat">openat</a> here takes only ~30µs, but it was ~80µs when opening for with <code class="language-plaintext highlighter-rouge">O_WRONLY|O_CREAT|O_TRUNC</code>.</p>

<h3 id="sneaky-syscalls">Sneaky syscalls</h3>

<p>Let’s look at generating random numbers and telling time:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># python
</span><span class="n">random_int</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">100</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ruby</span>
<span class="n">random_int</span> <span class="o">=</span> <span class="nb">rand</span><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">100</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># python
</span><span class="n">current_time</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ruby</span>
<span class="n">current_time</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">now</span>
</code></pre></div></div>

<p>You might reasonably assume you’d need a system call here too, but not so. Since these operations are frequently used and not privileged, they use <a href="https://man7.org/linux/man-pages/man7/vdso.7.html">vDSO</a> and turn into normal function calls, avoiding paying the price of a syscall. This makes them invisible to <code class="language-plaintext highlighter-rouge">strace</code> but you’d see them pop up in <code class="language-plaintext highlighter-rouge">ltrace</code>.</p>

<h3 id="printing">Printing</h3>

<p>Here’s one I thought would be obvious, but turned out a little surprising too:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># python
</span><span class="k">print</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ruby</span>
<span class="nb">puts</span> <span class="s2">"Hello"</span>
</code></pre></div></div>

<table>
    <tr>
        <th>Python</th>
        <th>Ruby</th>
    </tr>
    <tr>
        <td>
            <a href="https://linux.die.net/man/2/write">write</a>(1, "Hello\n") = 6
            <span class="duration">~50-250µs</span>
        </td>
        <td>
            <a href="https://linux.die.net/man/2/writev">writev</a>(1, [{iov_base=\"Hello\", iov_len=5}, {iov_base=\"\\n\", iov_len=1}], 2) = 6
            <span class="duration">~50-250µs</span>
        </td>
    </tr>
</table>

<p>I expected both languages to just use <a href="https://linux.die.net/man/2/write">write</a>, but apparently some 6 years ago Ruby switched to using <a href="https://linux.die.net/man/2/writev">writev</a> instead in <a href="https://bugs.ruby-lang.org/issues/14042">Feature #14042: IO#puts: use writev if available</a>. Before this, <code class="language-plaintext highlighter-rouge">puts</code> would use an extra <a href="https://linux.die.net/man/2/write">write</a> to output a newline and hence wasn’t atomic.</p>

<h3 id="fin">Fin</h3>
<p>I hope you found this interesting! If you’d find it useful if Cirron supported another language too, let me know!</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:0" role="doc-endnote">
      <p><a href="https://github.com/python/cpython/blob/main/Lib/_pyio.py#L247">https://github.com/python/cpython/blob/main/Lib/_pyio.py#L247</a> <a href="#fnref:0" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[We’ve released a new version of Cirron that can now trace syscalls and record performance counters for individual lines of Ruby code, just like it could already do for Python (more here and here). It makes it very easy to quickly inspect what’s happening in any section of your code and even assert what should be happening in tests, for example.]]></summary></entry><entry><title type="html">Tracing System Calls in Python</title><link href="https://blog.mattstuchlik.com/2024/02/16/counting-syscalls-in-python.html" rel="alternate" type="text/html" title="Tracing System Calls in Python" /><published>2024-02-16T00:00:00+00:00</published><updated>2024-02-16T00:00:00+00:00</updated><id>https://blog.mattstuchlik.com/2024/02/16/counting-syscalls-in-python</id><content type="html" xml:base="https://blog.mattstuchlik.com/2024/02/16/counting-syscalls-in-python.html"><![CDATA[<p>Last time we <a href="http://blog.mattstuchlik.com/2024/02/08/counting-cpu-instructions-in-python.html">counted CPU
instructions</a>,
let’s look at <a href="https://en.wikipedia.org/wiki/System_call">syscalls</a> now!</p>

<p>I’ll show you a little tiny tool I added to
<a href="https://github.com/s7nfo/Cirron">Cirron</a> that lets you see exactly what
syscalls a piece of Python code is calling and how to analyze the trace more effectively.</p>

<p>Let’s start with <code class="language-plaintext highlighter-rouge">print("Hello")</code> as before:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">cirron</span> <span class="kn">import</span> <span class="n">Tracer</span>

<span class="k">with</span> <span class="n">Tracer</span><span class="p">()</span> <span class="k">as</span> <span class="n">t</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">trace</span><span class="p">)</span>
<span class="c1"># write(1, "Hello\n", 6) = 6 &lt;0.000150s&gt;
</span></code></pre></div></div>

<p>You can see<sup id="fnref:0" role="doc-noteref"><a href="#fn:0" class="footnote" rel="footnote">1</a></sup> <code class="language-plaintext highlighter-rouge">print</code> uses only a single
<a href="https://man7.org/linux/man-pages/man2/write.2.html">write</a> to write the string
<code class="language-plaintext highlighter-rouge">"Hello\n"</code> to stdout (that’s what the <code class="language-plaintext highlighter-rouge">1</code> stands for) and asks it to write at
most <code class="language-plaintext highlighter-rouge">6</code> bytes. Write then returns <code class="language-plaintext highlighter-rouge">6</code>, meaning it managed to write all the
bytes we asked it to. You can also see it took 0.00015s or 150μs (that’s just the
<code class="language-plaintext highlighter-rouge">write</code> call, not the whole <code class="language-plaintext highlighter-rouge">print</code> statement).</p>

<p>Pretty cool!</p>

<p>How does <code class="language-plaintext highlighter-rouge">Tracer</code> work? I initially wanted to use the
<a href="https://man7.org/linux/man-pages/man2/ptrace.2.html">ptrace</a> syscall to
implement it, but that turned out to be a little more complicated that what I
wanted, so in the end I just used the <code class="language-plaintext highlighter-rouge">strace</code> tool, which also uses <code class="language-plaintext highlighter-rouge">ptrace</code>
but handles all the complexity. <code class="language-plaintext highlighter-rouge">Tracer</code> simply <a href="https://github.com/s7nfo/Cirron/blob/master/cirron/tracer.py#L138">starts tracing
itself</a> with
it, redirecting output to a file, which is then parsed when it’s asked to stop.</p>

<p>Let’s trace <code class="language-plaintext highlighter-rouge">import seaborn</code> now:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">cirron</span> <span class="kn">import</span> <span class="n">Tracer</span>

<span class="k">with</span> <span class="n">Tracer</span><span class="p">()</span> <span class="k">as</span> <span class="n">t</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">seaborn</span>

<span class="k">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">trace</span><span class="p">))</span>
<span class="c1"># 20462
</span></code></pre></div></div>

<p>Turns out importing Seaborn takes ~20k syscalls! That’s obviously too many to
just print out, so what’s a better way to analyze what it’s doing?</p>

<h2 id="visualizing-traces-with-perfetto">Visualizing traces with Perfetto</h2>

<p><a href="https://ui.perfetto.dev">Perfetto Trace Viewer</a> let’s you visualize all kinds
of traces. It can’t ingest <code class="language-plaintext highlighter-rouge">strace</code> output directly, but I’ve included a
function that converts Tracer output to <a href="https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview">Trace Event
Format</a>,
something Perfetto can load:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">cirron</span> <span class="kn">import</span> <span class="n">to_tef</span>

<span class="p">(...)</span>

<span class="nb">open</span><span class="p">(</span><span class="s">"/tmp/trace"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">).</span><span class="n">write</span><span class="p">(</span><span class="n">to_tef</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">trace</span><span class="p">))</span>
</code></pre></div></div>

<p><br /></p>

<p>This gets you a file you can open with Perfetto. I’m not going to describe all it can do; I uploaded a trace of <code class="language-plaintext highlighter-rouge">import seaborn</code> <a href="https://gist.github.com/s7nfo/4cda90818a07d851fea79c8c17e8eab8">here</a>, go <a href="https://ui.perfetto.dev/">play with it</a>!</p>

<p><br /></p>

<p><img src="/assets/perfetto.png" alt="Perfetto" /></p>

<p><br /></p>

<p>I was surprised to find it uses 4 threads, which mostly spend time looking up files and reading them, but one of the threads seems to be very curious about your CPU details!</p>

<p><strong>Backlinks</strong></p>

<p>Discussions on <a href="https://news.ycombinator.com/item?id=39402868">HackerNews</a> and <a href="https://www.reddit.com/r/Python/comments/1asjcnm/recording_and_visualising_the_20k_system_calls_it/">/r/Python</a>.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:0" role="doc-endnote">
      <p>If you try this yourself you’ll see a couple more calls, but those are related to shutting down <code class="language-plaintext highlighter-rouge">strace</code> after we’re done, not to the traced code itself. <a href="#fnref:0" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[Last time we counted CPU instructions, let’s look at syscalls now!]]></summary></entry><entry><title type="html">TIL: Terrence Tao on Machine Assisted Proofs</title><link href="https://blog.mattstuchlik.com/2024/02/10/TIL-terrence-tao-machine-assisted-proofs.html" rel="alternate" type="text/html" title="TIL: Terrence Tao on Machine Assisted Proofs" /><published>2024-02-10T00:00:00+00:00</published><updated>2024-02-10T00:00:00+00:00</updated><id>https://blog.mattstuchlik.com/2024/02/10/TIL-terrence-tao-machine-assisted-proofs</id><content type="html" xml:base="https://blog.mattstuchlik.com/2024/02/10/TIL-terrence-tao-machine-assisted-proofs.html"><![CDATA[<iframe width="560" height="315" src="https://www.youtube.com/embed/AayZuuDDKP0?si=lNQKfWDVNUPtVLOR" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>

<p>In this TT talks about how mathematicians usually work by themselves or with
colleagues they trust a lot, because otherwise you spend a lot of time checking
everyone’s work for errors.</p>

<p>With <a href="https://lean-lang.org">Lean</a> (a theorem prover) you can automate this checking. At the moment writing Lean proofs takes ~10x longer than doing it by hand, but it gives you greates scalability through lowering the trust barrier. And presumably the cost won’t be 10x forever.</p>

<p>It also simplifies change management. Changing one parameter no longer means having to go through the whole proof by hand.</p>

<p>This pattern matches what large software engineering organizations have been doing for a while. Automated testing, types systems, …</p>

<p>Speaking of the 10x cost of formalizing proofs, this is something LLMs might be helpful for. Instead of formalizing everything by hand, you ask an LLM to do it for you. It’s not going to get it right every time, but at least it’s easy to verify whether it did or not. And even if it does not it might suggest an approach you can work out yourself.</p>

<p>Could software engineering learn from this? Is it finaly <a href="https://en.wikipedia.org/wiki/TLA%2B">TLA+</a>’s time to shine with the help of LLMs? Could other fields, physics, chemistry, have formal languages with automated checking?</p>

<p>I liked the <a href="https://github.com/PatrickMassot/leanblueprint">Blueprint</a> pattern a lot too: a human comes up with a “blueprint” of the proof that is linked to the Lean formalization, breaks down the work needed into smaller lemmas and definitions that can be tackled individually and provides a snapshot of progress.</p>

<p><img src="https://terrytao.files.wordpress.com/2023/11/image.png" alt="blueprint" /></p>]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Counting CPU Instructions in Python</title><link href="https://blog.mattstuchlik.com/2024/02/08/counting-cpu-instructions-in-python.html" rel="alternate" type="text/html" title="Counting CPU Instructions in Python" /><published>2024-02-08T00:00:00+00:00</published><updated>2024-02-08T00:00:00+00:00</updated><id>https://blog.mattstuchlik.com/2024/02/08/counting-cpu-instructions-in-python</id><content type="html" xml:base="https://blog.mattstuchlik.com/2024/02/08/counting-cpu-instructions-in-python.html"><![CDATA[<p>Did you know it takes about 17,000 CPU instructions<sup id="fnref:0" role="doc-noteref"><a href="#fn:0" class="footnote" rel="footnote">1</a></sup> to <code class="language-plaintext highlighter-rouge">print("Hello")</code> in Python? And that it takes ~2 billion of them to import <code class="language-plaintext highlighter-rouge">seaborn</code>?</p>

<p>Today I was playing with <a href="https://man7.org/linux/man-pages/man2/perf_event_open.2.html">perf_event_open</a>, a linux syscall that lets you set up all kinds of performance monitoring. One way to interact with the system is through the <code class="language-plaintext highlighter-rouge">perf</code> CLI tool. The problem?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@X:~# cat print.py
print("Hello")

root@X:~# perf stat -e instructions:u python3 ./print.py
Hello

 Performance counter stats for 'python3 ./print.py':

          45713378      instructions:u

       0.030247842 seconds time elapsed

       0.025787000 seconds user
       0.004282000 seconds sys

</code></pre></div></div>

<p>45,713,378 is how many instructions it takes to initialize Python, print Hello
and tear the whole thing down again. I want more precision! I want to only
measure that single line of code.</p>

<p>Hence this little tool:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">cirron</span> <span class="kn">import</span> <span class="n">Collector</span>

<span class="k">with</span> <span class="n">Collector</span><span class="p">()</span> <span class="k">as</span> <span class="n">c</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="n">counters</span><span class="p">)</span>
<span class="c1"># Sample(instruction_count=17181, time_enabled_ns=92853)
</span></code></pre></div></div>

<p>Why would you want this? I was mostly just curious, but that’s not to say it
can’t be useful: let’s say you really, really care about performance of your
<code class="language-plaintext highlighter-rouge">foo</code> and you want to have a regression test that fails if it gets slower than
a threshold.  So you <code class="language-plaintext highlighter-rouge">time.time()</code> it, assert less than a threshold and… you
realize your CI box is running a ton of concurrent tests and the timing is very
noisy.</p>

<p>Not (as much) the instruction count!</p>

<p><img src="/assets/cirron_plot.png" alt="Cirron plot" /></p>

<p>This graph shows distribution of time measurements of the same piece of code
using <code class="language-plaintext highlighter-rouge">time.time()</code> and <code class="language-plaintext highlighter-rouge">time.perf_counter()</code> and the instruction count
measured with Cirron, scaled to fit with the time measurement<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">2</a></sup>. Note how
<strong>impeccably tight</strong> the instruction count distribution is (also note though
how <a href="https://hackmd.io/sH315lO2RuicY-SEt7ynGA?view#Hardware-performance-counter-support-via-rdpmc">it’s <em>not</em> completely
constant</a>).</p>

<p>This is not to say instruction count is the be-all and end-all. Equal instruction
counts can have wildly different wall clock timings, etc. Still, it’s a useful tool
if you’re aware of its limitations.</p>

<p>Here’s the <a href="https://github.com/s7nfo/Cirron">Github repo</a>, it’s very janky and
only runs on Linux at the moment, thought I’ll probably add Apple Arm support
soon. On the off-chance that there isn’t a tool out there that does this way
better already and you’d find it useful if this was on PyPI or exposed more
perf events or worked with $LANGUAGE, etc., <a href="https://twitter.com/s7nfo">let me
know</a>.</p>

<p><strong>Backlinks</strong></p>

<p>Discussions on <a href="https://www.reddit.com/r/Python/comments/1am6j5w/counting_cpu_instructions_in_python/">/r/python</a> and <a href="https://www.reddit.com/r/programming/comments/1am6m4j/counting_cpu_instructions_in_python/">/r/programming</a> subreddits.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:0" role="doc-endnote">
      <p>Since writting this I have <a href="https://github.com/s7nfo/Cirron/commit/2118a956131f2f65482a84c43953965aa6166f23">upgraded</a> Cirron to substract its own overhead; it now measures <code class="language-plaintext highlighter-rouge">print</code> at ~9,000 instructions. <a href="#fnref:0" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1" role="doc-endnote">
      <p>I’m sure comparing the two this way is completely incorrect, this is just me invoking the <a href="https://meta.wikimedia.org/wiki/Cunningham%27s_Law">Cunningham’s Law</a>. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[Did you know it takes about 17,000 CPU instructions1 to print("Hello") in Python? And that it takes ~2 billion of them to import seaborn? Since writting this I have upgraded Cirron to substract its own overhead; it now measures print at ~9,000 instructions. &#8617;]]></summary></entry><entry><title type="html">TIL: Data Dependency and Performance in Assembly Redux</title><link href="https://blog.mattstuchlik.com/2024/02/06/TIL-data-dependency-performance-assembly-redux.html" rel="alternate" type="text/html" title="TIL: Data Dependency and Performance in Assembly Redux" /><published>2024-02-06T00:00:00+00:00</published><updated>2024-02-06T00:00:00+00:00</updated><id>https://blog.mattstuchlik.com/2024/02/06/TIL-data-dependency-performance-assembly-redux</id><content type="html" xml:base="https://blog.mattstuchlik.com/2024/02/06/TIL-data-dependency-performance-assembly-redux.html"><![CDATA[<p>Unsatisfied with <a href="https://blog.mattstuchlik.com/2024/02/04/TIL-data-dependency-performance-assembly.html">my last investigation into dependency
breaking</a>
I decided to dig a little deeper.</p>

<p>First, instead of using <a href="https://github.com/sharkdp/hyperfine">hyperfine</a> I’ve used Daniel Lemire’s (an absolute SIMD
beast by the way, highly recommend reading his <a href="https://lemire.me/blog/">blog</a>
and marveling at <a href="https://simdjson.org">simdjson</a>) <a href="https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/tree/master/2024/02/04/benchmarks">benchmarking
harness</a>
and instead of a shared CPU I’ve used a dedicated host. I’ve also used C++ with
<code class="language-plaintext highlighter-rouge">-O1</code>, which generates basically the same code as my handwritten assembly, but
is much easier to work with.</p>

<p>As a refresher, we had two implementations of <code class="language-plaintext highlighter-rouge">sum</code>, one I expected to be slower</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">N</span><span class="o">/</span><span class="mi">2</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">result</span> <span class="o">+=</span> <span class="n">arr</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">];</span>
    <span class="n">result</span> <span class="o">+=</span> <span class="n">arr</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">];</span>
<span class="p">}</span>

<span class="k">return</span> <span class="n">result</span><span class="p">;</span>
</code></pre></div></div>

<p>And one I expected to be faster, thanks to the two operations in the loop being <a href="https://en.wikipedia.org/wiki/Data_dependency">independent</a> of each other, allowing <a href="https://en.wikipedia.org/wiki/Out-of-order_execution">out-of-order</a> execution:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">result1</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">result2</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">N</span><span class="o">/</span><span class="mi">2</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">result1</span> <span class="o">+=</span> <span class="n">arr</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">];</span>
    <span class="n">result2</span> <span class="o">+=</span> <span class="n">arr</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">];</span>
<span class="p">}</span>

<span class="k">return</span> <span class="n">result1</span> <span class="o">+</span> <span class="n">result2</span><span class="p">;</span>
</code></pre></div></div>

<p>With hyperfine on a shared CPU it was impossible to distinguish which is
which. With perf events, I’m able to measure a consistent difference: the
single variable implementation finishes in 293ms and the two variable in 261ms, ie. about 12% faster.</p>

<p>What’s more interesting though, is what happens when I unroll the loop even more and use 1, 2 or 4 variables:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">result1</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">result2</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">result3</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">result4</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">N</span><span class="o">/</span><span class="mi">4</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">result1</span> <span class="o">+=</span> <span class="n">arr</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">];</span>
    <span class="n">result2</span> <span class="o">+=</span> <span class="n">arr</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">];</span>
    <span class="n">result3</span> <span class="o">+=</span> <span class="n">arr</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="o">+</span><span class="mi">2</span><span class="p">];</span>
    <span class="n">result4</span> <span class="o">+=</span> <span class="n">arr</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="o">+</span><span class="mi">3</span><span class="p">];</span>
<span class="p">}</span>

<span class="k">return</span> <span class="n">result1</span> <span class="o">+</span> <span class="n">result2</span> <span class="o">+</span> <span class="n">result3</span> <span class="o">+</span> <span class="n">result4</span><span class="p">;</span>
</code></pre></div></div>

<p>The one variable version gets slightly faster at 284ms, presumably due to
lower loop overhead, but the 2 and 4 variable version speed up considerably more to
187ms and 170ms respectively, ie. 35% faster!</p>

<p><img src="/assets/dependency_breaking_graph.png" alt="graph" /></p>

<p>Unsurprisingly these differences disappear when I use <code class="language-plaintext highlighter-rouge">-O3</code>.</p>

<p>Pretty cool! Wish I had a more precise understanding of why this happen though.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Unsatisfied with my last investigation into dependency breaking I decided to dig a little deeper.]]></summary></entry><entry><title type="html">TIL: Data Dependency and Performance in Assembly</title><link href="https://blog.mattstuchlik.com/2024/02/04/TIL-data-dependency-performance-assembly.html" rel="alternate" type="text/html" title="TIL: Data Dependency and Performance in Assembly" /><published>2024-02-04T00:00:00+00:00</published><updated>2024-02-04T00:00:00+00:00</updated><id>https://blog.mattstuchlik.com/2024/02/04/TIL-data-dependency-performance-assembly</id><content type="html" xml:base="https://blog.mattstuchlik.com/2024/02/04/TIL-data-dependency-performance-assembly.html"><![CDATA[<p>I was playing with assembly today, trying to understand performance impact of breaking data dependencies.</p>

<p>Optimizing compilers tend to rewrite the human-obvious <code class="language-plaintext highlighter-rouge">sum</code> implementation to use multiple registers to keep track of parts of the total sum, adding them up at the end. For example here’s an implementation of <code class="language-plaintext highlighter-rouge">sum</code> in Rust 1.75 with <code class="language-plaintext highlighter-rouge">-C opt-level=3</code> and the associated disassembly (shortened to only keep the relevant bits):</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">sum</span><span class="p">(</span><span class="n">arr</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="nb">i32</span><span class="p">])</span> <span class="k">-&gt;</span> <span class="nb">i32</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="k">for</span> <span class="n">n</span> <span class="k">in</span> <span class="n">arr</span><span class="nf">.iter</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">result</span> <span class="o">+=</span> <span class="n">n</span>
    <span class="p">}</span>

    <span class="n">result</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">sum:</span>
        <span class="c1">; Set xmm0 to 0.</span>
        <span class="nf">pxor</span>    <span class="nv">xmm0</span><span class="p">,</span> <span class="nv">xmm0</span>
        <span class="nf">xor</span>     <span class="nb">eax</span><span class="p">,</span> <span class="nb">eax</span>
        <span class="c1">; Set xmm1 to 0.</span>
        <span class="nf">pxor</span>    <span class="nv">xmm1</span><span class="p">,</span> <span class="nv">xmm1</span> <span class="c1">; Set xmm1 to 0</span>
<span class="nl">.loop:</span>
        <span class="c1">; Move 4 i32 values from our array into xmm2</span>
        <span class="nf">movdqu</span>  <span class="nv">xmm2</span><span class="p">,</span> <span class="nv">xmmword</span> <span class="nv">ptr</span> <span class="p">[</span><span class="nb">rdi</span> <span class="o">+</span> <span class="mi">4</span><span class="o">*</span><span class="nb">rax</span><span class="p">]</span>
        <span class="c1">; Add the 4 i32 values in xmm2 to xmm0.</span>
        <span class="nf">paddd</span>   <span class="nv">xmm0</span><span class="p">,</span> <span class="nv">xmm2</span>
        <span class="c1">; Move the next 4 i32 values from our array into xmm2.</span>
        <span class="nf">movdqu</span>  <span class="nv">xmm2</span><span class="p">,</span> <span class="nv">xmmword</span> <span class="nv">ptr</span> <span class="p">[</span><span class="nb">rdi</span> <span class="o">+</span> <span class="mi">4</span><span class="o">*</span><span class="nb">rax</span> <span class="o">+</span> <span class="mi">16</span><span class="p">]</span>
        <span class="c1">; Add the 4 i32 values in xmm2 to xmm1, not xmm0 this time</span>
        <span class="nf">paddd</span>   <span class="nv">xmm1</span><span class="p">,</span> <span class="nv">xmm2</span>

        <span class="c1">; Move index by 8 and if we're not done jump back to loop.</span>
        <span class="nf">add</span>     <span class="nb">rax</span><span class="p">,</span> <span class="mi">8</span>
        <span class="nf">cmp</span>     <span class="nv">r8</span><span class="p">,</span> <span class="nb">rax</span>
        <span class="nf">jne</span>     <span class="nv">.loop</span>

        <span class="c1">; If we are done, add up what's in xmm0 and xmm1 and return.</span>
        <span class="nf">paddd</span>   <span class="nv">xmm1</span><span class="p">,</span> <span class="nv">xmm0</span>
        <span class="nf">pshufd</span>  <span class="nv">xmm0</span><span class="p">,</span> <span class="nv">xmm1</span><span class="p">,</span> <span class="mi">238</span>
        <span class="nf">paddd</span>   <span class="nv">xmm0</span><span class="p">,</span> <span class="nv">xmm1</span>
        <span class="nf">pshufd</span>  <span class="nv">xmm1</span><span class="p">,</span> <span class="nv">xmm0</span><span class="p">,</span> <span class="mi">85</span>
        <span class="nf">paddd</span>   <span class="nv">xmm1</span><span class="p">,</span> <span class="nv">xmm0</span>
        <span class="nf">movd</span>    <span class="nb">eax</span><span class="p">,</span> <span class="nv">xmm1</span>
        <span class="nf">ret</span>
</code></pre></div></div>

<p><br /></p>

<p>As expected the compiler splits the sum between two registers (xmm0, xmm1). My hypothesis, therefore, was that the following hand-written assembly:</p>

<p><br /></p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">sum:</span>
        <span class="nf">lea</span>     <span class="nb">rcx</span><span class="p">,</span> <span class="p">[</span><span class="nb">rdi</span> <span class="o">+</span> <span class="mi">4</span><span class="o">*</span><span class="nb">rsi</span><span class="p">]</span>
        <span class="nf">xor</span>     <span class="nb">eax</span><span class="p">,</span> <span class="nb">eax</span>    
<span class="nl">.loop:</span>
        <span class="c1">; Use EAX for both additions</span>
        <span class="nf">add</span>     <span class="nb">eax</span><span class="p">,</span> <span class="kt">dword</span> <span class="p">[</span><span class="nb">rdi</span><span class="p">]</span>    
        <span class="nf">add</span>     <span class="nb">eax</span><span class="p">,</span> <span class="kt">dword</span> <span class="p">[</span><span class="nb">rdi</span> <span class="o">+</span> <span class="mi">4</span><span class="p">]</span>
        <span class="nf">lea</span>     <span class="nb">rdi</span><span class="p">,</span> <span class="p">[</span><span class="nb">rdi</span> <span class="o">+</span> <span class="mi">8</span><span class="p">]</span> 
        <span class="nf">cmp</span>     <span class="nb">rdi</span><span class="p">,</span> <span class="nb">rcx</span>      
        <span class="nf">jb</span>      <span class="nv">.loop</span>        
        <span class="nf">ret</span>                         
</code></pre></div></div>

<p>Would be measurably slower than this multi-register one.</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">sum:</span>
        <span class="nf">lea</span>     <span class="nb">rcx</span><span class="p">,</span> <span class="p">[</span><span class="nb">rdi</span> <span class="o">+</span> <span class="mi">4</span><span class="o">*</span><span class="nb">rsi</span><span class="p">]</span>
        <span class="nf">xor</span>     <span class="nb">eax</span><span class="p">,</span> <span class="nb">eax</span>
        <span class="nf">xor</span>     <span class="nb">ebx</span><span class="p">,</span> <span class="nb">ebx</span>
<span class="nl">.loop:</span>
        <span class="c1">; Split the addition between EAX and EBX</span>
        <span class="nf">add</span>     <span class="nb">eax</span><span class="p">,</span> <span class="kt">dword</span> <span class="p">[</span><span class="nb">rdi</span><span class="p">]</span>
        <span class="nf">add</span>     <span class="nb">ebx</span><span class="p">,</span> <span class="kt">dword</span> <span class="p">[</span><span class="nb">rdi</span> <span class="o">+</span> <span class="mi">4</span><span class="p">]</span>
        <span class="nf">lea</span>     <span class="nb">rdi</span><span class="p">,</span> <span class="p">[</span><span class="nb">rdi</span> <span class="o">+</span> <span class="mi">8</span><span class="p">]</span>
        <span class="nf">cmp</span>     <span class="nb">rdi</span><span class="p">,</span> <span class="nb">rcx</span>
        <span class="nf">jb</span>      <span class="nv">.loop</span>
        <span class="nf">add</span>     <span class="nb">eax</span><span class="p">,</span> <span class="nb">ebx</span>
        <span class="nf">ret</span>
</code></pre></div></div>

<p>Except… It’s not? If anything the first version is slightly faster.</p>

<p>Is my measuring methodology horrible (hyperfine on shared vCPU isn’t great, but I would expect it to be merely noisy, not biased)? Is this <a href="https://en.wikipedia.org/wiki/Register_renaming">register renaming</a> making things efficient behind the scenes?</p>

<p>Curious!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I was playing with assembly today, trying to understand performance impact of breaking data dependencies.]]></summary></entry></feed>