<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Vasilios Syrakis - performance</title>
    <subtitle>A simple blog made with Zola and Duckquill</subtitle>
    <link rel="self" type="application/atom+xml" href="https://cetanu.github.io/tags/performance/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://cetanu.github.io"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-07-07T00:00:00+00:00</updated>
    <id>https://cetanu.github.io/tags/performance/atom.xml</id>
    <entry xml:lang="en">
        <title>Profit or Poverty: False Sharing</title>
        <published>2026-07-07T00:00:00+00:00</published>
        <updated>2026-07-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Vasilios Syrakis
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://cetanu.github.io/blog/false-sharing/"/>
        <id>https://cetanu.github.io/blog/false-sharing/</id>
        
        <content type="html" xml:base="https://cetanu.github.io/blog/false-sharing/">&lt;p&gt;In previous blogs I talked a little bit about
&lt;a href=&quot;https:&#x2F;&#x2F;cetanu.github.io&#x2F;blog&#x2F;why-numa-is-important-in-trading&#x2F;&quot;&gt;NUMA&lt;&#x2F;a&gt; and the
&lt;a href=&quot;https:&#x2F;&#x2F;cetanu.github.io&#x2F;blog&#x2F;translation-lookaside-buffer&#x2F;&quot;&gt;TLB&lt;&#x2F;a&gt;, if you haven’t read those, I
recommend starting there before reading this.&lt;&#x2F;p&gt;
&lt;p&gt;You should already understand that accessing main memory is relatively slow for
the CPU, especially in a low-latency environment, and you may be familiar with
the fact that the CPU has small caches of varying sizes on the chip, known as
the L1, L2, and L3 caches.&lt;&#x2F;p&gt;
&lt;p&gt;As their name implies, there are levels, or a hierarchy, from fastest (and
smallest) to slowest (and largest). The L1 cache is also typically split into
two caches, one being an instruction cache and the other being a data cache.&lt;&#x2F;p&gt;
&lt;p&gt;Fun fact: these caches are usually made of SRAM and usually they are the
largest part on the chip.&lt;&#x2F;p&gt;
&lt;img class=&quot;no-hover&quot;alt=&quot;relative speed of hardware&quot;src=&quot;&amp;#x2F;img&amp;#x2F;relative-hw-speed.png&quot;&#x2F;&gt;&lt;h2 id=&quot;requesting-data-from-the-cache&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#requesting-data-from-the-cache&quot; aria-label=&quot;Anchor link for: requesting-data-from-the-cache&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Requesting data from the cache&lt;&#x2F;h2&gt;
&lt;p&gt;You may have heard of “cache lines” before (also called cache blocks), maybe on
some article about performance on hackernews.&lt;&#x2F;p&gt;
&lt;p&gt;A cache line can be thought of a single unit of data which is transferred from
RAM to the cache. Usually one cache line is 64 bytes, but this can differ
depending on your CPU architecture.&lt;&#x2F;p&gt;
&lt;p&gt;This introduces the concept of cache locality. When the CPU needs to access the
same memory several times in a row, after it’s already been pulled into the L1
cache, this is referred to as &lt;strong&gt;temporal locality&lt;&#x2F;strong&gt;. When the CPU pulls in a
cache line and all the data it needs is within that block, it’s referred to as
spatial locality. A good example of &lt;strong&gt;spatial locality&lt;&#x2F;strong&gt; is when you have an
array that fits into one cache line, resulting in traversal being extremely
fast. An example of potentially terrible cache performance might be something
like a linked-list.&lt;&#x2F;p&gt;
&lt;p&gt;On systems with multiple CPU cores, they might share the same chunk of memory
in their local caches. Most processors use what is known as the MESI protocol
to prevent overwriting each others data. Jon Gjengset has a &lt;a href=&quot;https:&#x2F;&#x2F;cetanu.github.io&#x2F;blog&#x2F;false-sharing&#x2F;www.youtube.com&#x2F;watch?v=tND-wBBZ8RY&quot;&gt;great
talk&lt;&#x2F;a&gt; about Mutexes which also covers the
MESI protocol.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;where-it-can-go-wrong&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#where-it-can-go-wrong&quot; aria-label=&quot;Anchor link for: where-it-can-go-wrong&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Where it can go wrong&lt;&#x2F;h2&gt;
&lt;p&gt;CPUs can enter what is called a coherence livelock or cache-line pingponging,
where multiple cores fight over the same memory, or the same cache line.&lt;&#x2F;p&gt;
&lt;p&gt;The first way this can happen is called &lt;strong&gt;False Sharing&lt;&#x2F;strong&gt;, where your data may
reside in a cache line, but it may not be occupying that entire cache line by
itself. If your data sits in the first half but some other data sits in the
latter half, it’s possible that two CPUs may continuously write to the same
cache line, requiring them to communicate with each other to gain ownership in
order to modify the cache line. If you watched Jon’s video above you would know
that this can eat up dozens of precious nanoseconds, just for the comms between
CPUs. As you increase the number of cores on a system this problem can be
exacerbated.&lt;&#x2F;p&gt;
&lt;img class=&quot;no-hover&quot;alt=&quot;diagram of false sharing&quot;src=&quot;&amp;#x2F;img&amp;#x2F;false-sharing.png&quot;&#x2F;&gt;
&lt;p&gt;The second way is &lt;strong&gt;True Sharing&lt;&#x2F;strong&gt;, where cores may write to the same variable or
data structure in memory. This could happen during atomic operations or when
using synchronization primitives such as mutexes, semaphores, barriers,
spinlocks, and so on.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-to-protect-against-it&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-to-protect-against-it&quot; aria-label=&quot;Anchor link for: how-to-protect-against-it&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
How to protect against it&lt;&#x2F;h2&gt;
&lt;p&gt;At least for false sharing, the solution seems simple: just pad your data
structure or variable so that it occupies an entire cache line. Obviously it
would be silly to do this with every variable in your program, so you have to
pick the time-critical parts of your application here.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #A9B1D6; background-color: #1A1B26;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;use&lt;&#x2F;span&gt;&lt;span style=&quot;color: #0DB9D7;&quot;&gt; std&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #0DB9D7;&quot;&gt;sync&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #0DB9D7;&quot;&gt;atomic&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C0CAF5;&quot;&gt;AtomicU64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;#[&lt;&#x2F;span&gt;&lt;span&gt;repr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;align&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;))]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt; struct&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C0CAF5;&quot;&gt; PaddedAtomic&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C0CAF5;&quot;&gt; value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C0CAF5;&quot;&gt; AtomicU64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For true sharing, it seems quite a bit more complicated to get right. Sometimes
you really do need atomic operations on global memory.&lt;&#x2F;p&gt;
&lt;p&gt;You could pull data into thread local storage, only merging back at the end of
expensive or time critical execution.&lt;&#x2F;p&gt;
&lt;p&gt;You could reorganize your program to do batching to reduce how often it needs
to write back to the shared memory.&lt;&#x2F;p&gt;
&lt;p&gt;Maybe a lock-free data structure could be used.&lt;&#x2F;p&gt;
&lt;p&gt;The solution most likely depends on exactly what you are willing to accept in
terms of trade-offs.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Like I usually say, for the most part things like this are unnoticeable and
work just fine for the majority of situations. If you’re running a flask webapp
you probably don’t and should not care about this.&lt;&#x2F;p&gt;
&lt;p&gt;When you require low-latency, then you need to understand how to write your
programs such that they exploit cache-lines for your architecture because it
makes the difference between a 1ns L1 cache hit, or falling off a cliff with a
100x slower execution.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Profit or Poverty: TLB</title>
        <published>2026-04-28T00:00:00+00:00</published>
        <updated>2026-04-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Vasilios Syrakis
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://cetanu.github.io/blog/translation-lookaside-buffer/"/>
        <id>https://cetanu.github.io/blog/translation-lookaside-buffer/</id>
        
        <content type="html" xml:base="https://cetanu.github.io/blog/translation-lookaside-buffer/">&lt;h2 id=&quot;the-illusion-of-memory&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-illusion-of-memory&quot; aria-label=&quot;Anchor link for: the-illusion-of-memory&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
The Illusion of Memory&lt;&#x2F;h2&gt;
&lt;p&gt;Applications do not allocate memory directly to physical addresses on the RAM.
They are presented with virtual memory by the operating system, so that it can
pretend that the application has a contiguous block of memory when in reality
its memory is scattered across the physical RAM in fixed-size chunks of memory
called pages, kept track of in a page table.&lt;&#x2F;p&gt;
&lt;p&gt;When a process starts, the operating system allocates a range of virtual
addresses to it in the form of pages, but leaves the entries in the page table
blank until they’re accessed (i.e. they are lazy-loaded)&lt;&#x2F;p&gt;
&lt;p&gt;If the process tries to access memory, the operating system follows the virtual
memory address to the physical memory address and hands back the data. The
actual translation from virtual address to physical address is not performed by
the OS but by a dedicated hardware component on the CPU called a memory
management unit (MMU).&lt;&#x2F;p&gt;
&lt;p&gt;Sometimes, if RAM is running low or hasn’t been accessed in a while, data is
taken out of RAM and swapped to disk. If the process then tries to access that
memory again, a &lt;strong&gt;major&lt;&#x2F;strong&gt; page fault occurs, the operating system has to retrieve the
data from disk, load it into RAM, and then update the page table before the
process can proceed.&lt;&#x2F;p&gt;
&lt;p&gt;A minor page fault is when the data is still in RAM, but the page table for the
process has not been mapped to the physical page yet. By default the operating
system is lazy about mapping page table entries, but there are strategies for
ensuring that all pages are mapped.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-bottleneck&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-bottleneck&quot; aria-label=&quot;Anchor link for: the-bottleneck&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
The Bottleneck&lt;&#x2F;h2&gt;
&lt;p&gt;Every time you’re reading variables, or loading instructions, or saving data,
the system is translating virtual memory addresses to physical addresses.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;a-hardware-page-table-walk&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#a-hardware-page-table-walk&quot; aria-label=&quot;Anchor link for: a-hardware-page-table-walk&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
A hardware page table walk&lt;&#x2F;h3&gt;
&lt;p&gt;This translation can be quite involved if we dig really deep into the detail.
The MMU looks at a register to find the physical address of the root page
table. Then it takes some bits from the virtual address, goes to RAM, and reads
the page table entry at that index. Then it takes the result from that lookup,
goes back to RAM again, and finds the entry point to another intermediate
table, and another intermediate table… until it gets the physical page frame
number. It then combines the address with some of the bits from the original
virtual address to find the exact byte in memory.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;introducing-the-tlb&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#introducing-the-tlb&quot; aria-label=&quot;Anchor link for: introducing-the-tlb&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Introducing the TLB&lt;&#x2F;h3&gt;
&lt;p&gt;The translation look-aside buffer (TLB) is a specialized cache within the MMU
which remembers recent mappings to avoid the CPU having to check the full page
table (in RAM) on every single memory access (billions of times per second)&lt;&#x2F;p&gt;
&lt;p&gt;The consequence of a TLB miss is that the operation takes 100-200x longer. In
reality this equates to about 100 nanoseconds, which is miniscule, but the
volume of these operations causes that time to add up rapidly. Not to mention
that 1 nanosecond would be much preferred.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-to-make-money&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-to-make-money&quot; aria-label=&quot;Anchor link for: how-to-make-money&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
How to make 💵 MONEY 💵&lt;&#x2F;h2&gt;
&lt;p&gt;Since the consequence of TLB cache misses are so dire, it makes sense to avoid
them. Working around the cache is out of the question, so the way forward is to
enhance the effectiveness of the cache by making it have to cache less things,
thereby improving its hit rate.&lt;&#x2F;p&gt;
&lt;p&gt;The TLB has a limit on the number of page table entries it can hold. A page is
typically 4KB but this may differ based on your hardware. So if your system has
32GB of memory, you need 8 million entries to map all the available memory on
the system, which is not possible.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;hugepages&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#hugepages&quot; aria-label=&quot;Anchor link for: hugepages&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Hugepages&lt;&#x2F;h3&gt;
&lt;p&gt;This is where “Hugepages” come in.&lt;&#x2F;p&gt;
&lt;p&gt;Hugepages is a feature in the linux kernel that allows a program to use much
larger page sizes, usually either 2MB or 1GB. By using this option, you can
effectively map all of the memory that you need such that it can fit in the
limited number of entries in the TLB easily.&lt;&#x2F;p&gt;
&lt;p&gt;For example, with the 32GB example from before, using 1GB hugepages, you would
only need 32 entries, rather than 8,000,000.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;enabling-hugepages-for-your-application&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#enabling-hugepages-for-your-application&quot; aria-label=&quot;Anchor link for: enabling-hugepages-for-your-application&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Enabling hugepages for your application&lt;&#x2F;h4&gt;
&lt;p&gt;You could set a sysctl (&lt;code&gt;vm.nr_hugepages&lt;&#x2F;code&gt;), or you could control it from your program, for example…&lt;&#x2F;p&gt;
&lt;p&gt;In C++, when calling &lt;code&gt;mmap&lt;&#x2F;code&gt;, you can pass the &lt;code&gt;MAP_HUGETLB&lt;&#x2F;code&gt; bitflag or &lt;code&gt;MAP_HUGE_1GB&lt;&#x2F;code&gt; for example.&lt;&#x2F;p&gt;
&lt;p&gt;In Rust, there are some crates like &lt;code&gt;memmap2&lt;&#x2F;code&gt; or &lt;code&gt;hugepage-rs&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;memory-pinning&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#memory-pinning&quot; aria-label=&quot;Anchor link for: memory-pinning&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Memory Pinning&lt;&#x2F;h3&gt;
&lt;p&gt;I mentioned earlier that when a process starts, the OS allocates virtual
memory, but it lazily maps the page table entries. So you may have hugepages
enabled, but you can still encounter a page fault if you access memory that
hasn’t been mapped yet.&lt;&#x2F;p&gt;
&lt;p&gt;There is a linux system call, &lt;code&gt;mlockall&lt;&#x2F;code&gt; which can be used to eagerly map all
the virtual memory for your process. It’s advised to call this before entering
the critical part of the program, such as any part of the program which needs
to be real-time (requires deterministic timing).&lt;&#x2F;p&gt;
&lt;p&gt;This system call not only “pre-faults” the pages so that they’re ready, it also
pins them so that the operating system cannot swap them back to disk.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;how-to-pin-memory-from-within-your-program&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-to-pin-memory-from-within-your-program&quot; aria-label=&quot;Anchor link for: how-to-pin-memory-from-within-your-program&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
How to pin memory, from within your program&lt;&#x2F;h4&gt;
&lt;p&gt;In C++ you would include the &lt;code&gt;sys&#x2F;mman.h&lt;&#x2F;code&gt; header, allowing you to call mlockall directly.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #A9B1D6; background-color: #1A1B26;&quot;&gt;&lt;code data-lang=&quot;c&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;#&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt;include&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECE6A;&quot;&gt;sys&#x2F;mman.h&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;#&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt;include&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECE6A;&quot;&gt;iostream&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;#&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt;include&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECE6A;&quot;&gt;cstring&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7AA2F7;&quot;&gt; main&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ABDF5;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ABDF5;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7AA2F7;&quot;&gt;mlockall&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ABDF5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7AA2F7;&quot;&gt;MCL_CURRENT &lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7AA2F7;&quot;&gt; MCL_FUTURE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ABDF5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt; !=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9E64;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ABDF5;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ABDF5;&quot;&gt;        std::cerr &lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;&amp;lt;&amp;lt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECE6A;&quot;&gt;Failed to lock memory: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;&amp;quot; &amp;lt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7AA2F7;&quot;&gt; std::strerror&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ABDF5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7AA2F7;&quot;&gt;errno&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ABDF5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt; &amp;lt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ABDF5;&quot;&gt; std::endl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9E64;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ABDF5;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7AA2F7;&quot;&gt;    munlockall&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ABDF5;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ABDF5;&quot;&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9E64;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ABDF5;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In Rust, you would use the &lt;code&gt;libc&lt;&#x2F;code&gt; crate and call it similarly.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #A9B1D6; background-color: #1A1B26;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;use&lt;&#x2F;span&gt;&lt;span style=&quot;color: #0DB9D7;&quot;&gt; libc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;::{&lt;&#x2F;span&gt;&lt;span&gt;mlockall&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C0CAF5;&quot;&gt; MCL_CURRENT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C0CAF5;&quot;&gt; MCL_FUTURE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;};&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7AA2F7;&quot;&gt; main&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C0CAF5;&quot;&gt; locked&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt; = unsafe {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7AA2F7;&quot;&gt;        mlockall&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9E64;&quot;&gt;MCL_CURRENT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9E64;&quot;&gt; MCL_FUTURE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;    };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C0CAF5;&quot;&gt; locked&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt; !=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9E64;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BB9AF7;&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C0CAF5;&quot;&gt; err&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #0DB9D7;&quot;&gt; std&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #0DB9D7;&quot;&gt;io&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C0CAF5;&quot;&gt;Error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7AA2F7;&quot;&gt;last_os_error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7AA2F7;&quot;&gt;        panic!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECE6A;&quot;&gt;Failed to lock memory: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;{}&amp;quot;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C0CAF5;&quot;&gt; err&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Ultimately, the illusion of memory that modern systems provide is a
double-edged sword. While virtual memory addressing solves major problems
around process isolation for most systems, the introduction of page tables and
the need to perform multi-level page table walks introduces latency that is
unnoticable to humans but harmful to low-latency and real-time applications.&lt;&#x2F;p&gt;
&lt;p&gt;With this small bit of operating systems and linux knowledge you can use
hugepages and memory pinning to reduce memory latency by up to 100 to 200 times
in certain scenarios.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Profit or Poverty: Realtime kernel patch</title>
        <published>2026-04-20T00:00:00+00:00</published>
        <updated>2026-04-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Vasilios Syrakis
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://cetanu.github.io/blog/linux-is-not-a-realtime-system/"/>
        <id>https://cetanu.github.io/blog/linux-is-not-a-realtime-system/</id>
        
        <content type="html" xml:base="https://cetanu.github.io/blog/linux-is-not-a-realtime-system/">&lt;p&gt;&lt;strong&gt;Linux is not a realtime operating system.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This statement confused me at first, but as I looked further, it began to make
sense.&lt;&#x2F;p&gt;
&lt;p&gt;Linux provides an environment where multitasking is possible, facilitated by a
scheduler which has goals such as minimizing wait times and latency, maximizing
throughput, and maximizing fairness.&lt;&#x2F;p&gt;
&lt;p&gt;These goals can sometimes pull in different directions. If you maximize
throughput for one task, you increase latency for others. If you minimize
latency by switching tasks more often, overheads start to pile up. If every
process gets an equal slice of time for fairness, heavy tasks are starved while
light tasks sit idle.&lt;&#x2F;p&gt;
&lt;p&gt;Taking the general approach is the best decision for most usecases like servers
and desktop computing, but it means that Linux is a “soft realtime” system.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;defining-realtime&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#defining-realtime&quot; aria-label=&quot;Anchor link for: defining-realtime&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Defining “realtime”&lt;&#x2F;h2&gt;
&lt;p&gt;What is evoked when you hear someone talk about something happening in
&lt;strong&gt;realtime?&lt;&#x2F;strong&gt; We often think that it is happening immediately, like we’re
describing something in the present.&lt;&#x2F;p&gt;
&lt;p&gt;In computing, realtime refers to something more specific. From Wikipedia:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;A system is said to be realtime if the total correctness of an operation
depends not only upon its logical correctness, but also upon the time in
which it is performed.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;That is to say that it does not mean “fast” necessarily, it’s more to do with
deterministic behavior in relation to time.&lt;&#x2F;p&gt;
&lt;p&gt;Linux is a &lt;strong&gt;soft&lt;&#x2F;strong&gt; realtime system, meaning that if an operation misses its
deadline, it isn’t a hard failure. Your process might wait a few hundred
microseconds before the scheduler gives it a time-slice, and for the vast
majority of cases, this is acceptable and nobody notices. If a deadline is
missed frequently, the user experiences a degradation of service. For example,
a web page might load slower, but still eventually load.&lt;&#x2F;p&gt;
&lt;p&gt;A &lt;strong&gt;hard&lt;&#x2F;strong&gt; realtime system would be something like an assembly line, where if a
part isn’t processed before its deadline, that part is no longer reachable by
the robot, and the next robot won’t be able to process it correctly.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-scheduler&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-scheduler&quot; aria-label=&quot;Anchor link for: the-scheduler&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
The Scheduler&lt;&#x2F;h2&gt;
&lt;p&gt;I mentioned earlier that Linux provides multitasking. It really provides the
illusion of multitasking, at least on a single core. The task scheduler is what
creates this illusion, by allocating slices of time to each task, interleaving
their execution with one another to create the illusion of simultaneous
progression. This is referred to as concurrency, as opposed to parallelism.&lt;&#x2F;p&gt;
&lt;small&gt;
Concurrency comes from the Latin &quot;con&quot; (together) and &quot;currere&quot; (to run,
literally, to move faster than walking).
&lt;&#x2F;small&gt;
&lt;p&gt;Concurrency is great, especially when tasks have to wait for something to
become available - the scheduler can pause that task until it’s ready to be
executed, and give time-slices to other tasks in the meanwhile.&lt;&#x2F;p&gt;
&lt;p&gt;The task itself can communicate to the scheduler that it should be paused to
allow other tasks to progress. Often, a task will do this implicitly by
executing a particular system call, such as &lt;code&gt;read()&lt;&#x2F;code&gt; or &lt;code&gt;sleep()&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;However, the &lt;em&gt;scheduler&lt;&#x2F;em&gt; can also pause a task &lt;em&gt;without any cooperation from the
task.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;img class=&quot;no-hover&quot;alt=&quot;Scheduler standing behind the
application, about to ruin its day&quot;src=&quot;&amp;#x2F;img&amp;#x2F;preemption_meme_1.png&quot;&#x2F;&gt;&lt;h2 id=&quot;preemption&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#preemption&quot; aria-label=&quot;Anchor link for: preemption&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Preemption&lt;&#x2F;h2&gt;
&lt;p&gt;This particular style of multitasking on Linux is called preemptive
multitasking. Tasks can be “preempted”.&lt;&#x2F;p&gt;
&lt;p&gt;Sounds interesting, but what does it mean? &lt;em&gt;What&lt;&#x2F;em&gt; is being preempted?&lt;&#x2F;p&gt;
&lt;p&gt;The current task’s execution time is being preempted - cut short - so that a
more important task can be run.&lt;&#x2F;p&gt;
&lt;p&gt;During preemption, the process state is saved, and another’s is loaded. This
mechanism is what we know to be a context switch.&lt;&#x2F;p&gt;
&lt;p&gt;An example where this is good: you run an application, and that application
chews up all your compute, and you want to cancel it. If it couldn’t be
preempted, you wouldn’t be able to move your cursor to close or cancel it.&lt;&#x2F;p&gt;
&lt;p&gt;Where preemption becomes undesirable is when you’re in a low-latency
environment such as high-frequency trading, and your trading application gets
preempted and has to wait &lt;strong&gt;hundreds of microseconds&lt;&#x2F;strong&gt; to execute.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;switch-it-off&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#switch-it-off&quot; aria-label=&quot;Anchor link for: switch-it-off&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Switch it off!!&lt;&#x2F;h2&gt;
&lt;p&gt;You can use a tool like &lt;code&gt;chrt&lt;&#x2F;code&gt; to change how your running process is treated.
For example, maybe you set its priority very high, in FIFO mode where it runs
until it voluntarily yields, like so:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #A9B1D6; background-color: #1A1B26;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;chrt --pid --fifo 99 &amp;lt;PID&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;small&gt;You can also check the current scheduling attributes with &lt;code&gt;chrt -p &amp;lt;PID&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;small&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This will reduce the worst-case kernel latency but &lt;strong&gt;doesn’t get rid of all
sources of jitter.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;There are still things that can preempt the task. Here’s a few:&lt;&#x2F;p&gt;
&lt;h3 id=&quot;hard-interrupts-irqs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#hard-interrupts-irqs&quot; aria-label=&quot;Anchor link for: hard-interrupts-irqs&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Hard interrupts (IRQs)&lt;&#x2F;h3&gt;
&lt;p&gt;These are signals from hardware - network cards, disk controllers, or system
timers. If your application runs on the same core that is handling interrupts,
the CPU will pause your app to run the handler.&lt;&#x2F;p&gt;
&lt;p&gt;You can examine the interrupts on an existing machine by inspecting
&lt;code&gt;&#x2F;proc&#x2F;interrupts&lt;&#x2F;code&gt;. This will show you devices and their IRQ number.&lt;&#x2F;p&gt;
&lt;p&gt;By default, all cores can service interrupts.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;tip&quot;&gt;
	&lt;p class=&quot;alert-title&quot;&gt;
		&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;Tip&lt;&#x2F;p&gt;
	&lt;p&gt;Non-essential hardware interrupts can be moved to another core or cores with
interrupt affinity (&lt;code&gt;&#x2F;proc&#x2F;irq&#x2F;&amp;lt;IRQ_NUMBER&amp;gt;&#x2F;smp_affinity&lt;&#x2F;code&gt;). See &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.redhat.com&#x2F;en&#x2F;documentation&#x2F;red_hat_enterprise_linux&#x2F;6&#x2F;html&#x2F;performance_tuning_guide&#x2F;s-cpu-irq&quot;&gt;Interrupts and IRQ
Tuning&lt;&#x2F;a&gt;
in the Red Hat tuning guide&lt;&#x2F;p&gt;

&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;non-maskable-interrupts-nmi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#non-maskable-interrupts-nmi&quot; aria-label=&quot;Anchor link for: non-maskable-interrupts-nmi&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Non-maskable interrupts (NMI)&lt;&#x2F;h3&gt;
&lt;p&gt;An NMI is another hardware interrupt which the CPU cannot ignore or disable.
These come from critical events like fatal hardware problems, memory errors,
power failures, and watchdog timer timeouts.&lt;&#x2F;p&gt;
&lt;p&gt;The watchdog is a daemon that keeps track of &#x2F;dev&#x2F;watchdog, which contains a
heartbeat from a hardware timer on the motherboard. This is to detect CPU lockups.&lt;&#x2F;p&gt;
&lt;p&gt;This can be and usually is disabled in HFT environments as far as I’m aware.
This supposedly the most frequent cause of this type of interrupt. The other
NMIs cannot be disabled and usually result in a kernel panic or system restart.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;tip&quot;&gt;
	&lt;p class=&quot;alert-title&quot;&gt;
		&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;Tip&lt;&#x2F;p&gt;
	&lt;p&gt;You can disable the watchdog while the system is running by setting the sysctl
&lt;code&gt;kernel.nmi_watchdog&lt;&#x2F;code&gt; to &lt;code&gt;0&lt;&#x2F;code&gt;, or you can edit your bootloader such as GRUB with
&lt;code&gt;nmi_watchdog=0&lt;&#x2F;code&gt; at the end of the line that starts with
&lt;code&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Occasionally it can show up in BIOS as “IPMI Watchdog Timer” which can be disabled.&lt;&#x2F;p&gt;

&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;system-management-interrupts-smi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#system-management-interrupts-smi&quot; aria-label=&quot;Anchor link for: system-management-interrupts-smi&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
System Management Interrupts (SMI)&lt;&#x2F;h3&gt;
&lt;p&gt;This is also technically a non-maskable hardware interrupt, but it originates
from the BIOS or EFI and pauses the entire OS. They’re particularly insidious
because they cannot be detected via any standard linux observability.&lt;&#x2F;p&gt;
&lt;p&gt;These interrupts occur when the motherboard needs to perform some kind of
thermal throttling or power management, and can take in excess of 100
microseconds to complete.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;tip&quot;&gt;
	&lt;p class=&quot;alert-title&quot;&gt;
		&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;Tip&lt;&#x2F;p&gt;
	&lt;p&gt;There is nothing you can configure in the OS to prevent this, but there may be
BIOS options like disabling “Global SMI” or “C-States”, and these options may
only be present in server hardware that has been specifically optimized for HFT
by the vendor.&lt;&#x2F;p&gt;

&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;software-faults&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#software-faults&quot; aria-label=&quot;Anchor link for: software-faults&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Software faults&lt;&#x2F;h3&gt;
&lt;p&gt;If the trading app tries to access memory and encounters a page fault or a TLB
miss, the kernel needs to take over and fetch the page.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;tip&quot;&gt;
	&lt;p class=&quot;alert-title&quot;&gt;
		&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;Tip&lt;&#x2F;p&gt;
	&lt;p&gt;Use huge-pages and pre-fault all pages. I’ll cover this more in a future
article about the TLB specifically.&lt;&#x2F;p&gt;

&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;validation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#validation&quot; aria-label=&quot;Anchor link for: validation&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Validation&lt;&#x2F;h2&gt;
&lt;p&gt;There are some tools out there which can be used to run tasks and measure interference, such as:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cyclictest&lt;&#x2F;code&gt; can be used to measure kernel scheduling latency&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;hwlatdetect&lt;&#x2F;code&gt; can be used to measure latency caused by NMIs&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;The systems we use daily work seamlessly for us, but under the surface they’re
executing sophisticated multitasking routines to give every process a fair
chance at execution, with low-level hardware events stepping in and taking
priority to avoid abrupt system failures.&lt;&#x2F;p&gt;
&lt;p&gt;In a trading environment, without knowledge of these interactions, your
application would be subject to all kinds of jitter, causing you to miss a
“fill” or to get sniped on a fast-moving order book…&lt;&#x2F;p&gt;
&lt;p&gt;… and that can be the difference between profit and poverty.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Profit or Poverty: NUMA</title>
        <published>2026-04-17T00:00:00+00:00</published>
        <updated>2026-04-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Vasilios Syrakis
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://cetanu.github.io/blog/why-numa-is-important-in-trading/"/>
        <id>https://cetanu.github.io/blog/why-numa-is-important-in-trading/</id>
        
        <content type="html" xml:base="https://cetanu.github.io/blog/why-numa-is-important-in-trading/">&lt;p&gt;While modern software tries to hide hardware complexity behind abstractions,
staff working in HFT must dig down to ensure that every nanosecond is spent
executing trading logic, not chewed up by operating system overheads or noisy
neighbouring processes.&lt;&#x2F;p&gt;
&lt;p&gt;Learning the ins and outs of NUMA is not a micro-optimization, it is a requirement.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;short-history-of-numa&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#short-history-of-numa&quot; aria-label=&quot;Anchor link for: short-history-of-numa&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Short history of NUMA&lt;&#x2F;h3&gt;
&lt;p&gt;Back in the day, CPUs were slower than memory. In the 1960s, processors began
to overtake memory in speed and as a result found themselves stuck waiting for
data to arrive from a memory access.&lt;&#x2F;p&gt;
&lt;p&gt;This was solved by either avoiding memory access, or by adding cache memory
some of which you may have heard of like L1 and L2 cache.&lt;&#x2F;p&gt;
&lt;p&gt;However, as operating systems and applications have grown, these caches are no
longer as effective. Additionally, on modern servers with multiple CPU sockets, only
one processor can access the memory at one time, causing the other to wait.&lt;&#x2F;p&gt;
&lt;p&gt;Non-uniform memory access (NUMA) was created to solve this problem by providing
separate memory to each socket.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-numa-must-be-considered-in-a-high-frequency-trading-hft-environment&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-numa-must-be-considered-in-a-high-frequency-trading-hft-environment&quot; aria-label=&quot;Anchor link for: why-numa-must-be-considered-in-a-high-frequency-trading-hft-environment&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Why NUMA must be considered in a high-frequency trading (HFT) environment&lt;&#x2F;h3&gt;
&lt;p&gt;In HFT, nanoseconds matter. If your application has to wait an extra amount of
nanoseconds every time it needs to access some memory, it quickly adds up into
microseconds and perhaps even milliseconds.&lt;&#x2F;p&gt;
&lt;p&gt;On hardware with NUMA, data could reside in non-local memory aka the memory of
the other socket on the server board. For the local core to access this memory
it has to traverse an interconnect which can take 100-200 nanoseconds.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;how-to-protect-your-application-from-the-downsides-of-numa&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-to-protect-your-application-from-the-downsides-of-numa&quot; aria-label=&quot;Anchor link for: how-to-protect-your-application-from-the-downsides-of-numa&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
How to protect your application from the downsides of NUMA&lt;&#x2F;h3&gt;
&lt;p&gt;Firstly, on Linux, NUMA is modelled as nodes. For example, Node 0 is Socket 0
plus its attached memory, for however many sockets your system has.&lt;&#x2F;p&gt;
&lt;p&gt;You can configure different policies per-thread or per-allocation.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;local&lt;&#x2F;code&gt; (default) - allocate on the node where the thread is running.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;bind&lt;&#x2F;code&gt; - allocate &lt;em&gt;only&lt;&#x2F;em&gt; on specified nodes.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;preferred&lt;&#x2F;code&gt; - try a particular node first but fallback to others.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;interleave&lt;&#x2F;code&gt; - round-robin across nodes.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In HFT you probably want to use &lt;code&gt;bind&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;discover-your-topology&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#discover-your-topology&quot; aria-label=&quot;Anchor link for: discover-your-topology&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Discover your topology&lt;&#x2F;h4&gt;
&lt;p&gt;Even on your laptop or desktop linux computer, you should be able to install
&lt;code&gt;numactl&lt;&#x2F;code&gt;, &lt;code&gt;lscpu&lt;&#x2F;code&gt;, or &lt;code&gt;lstopo&lt;&#x2F;code&gt; and look at the nodes.&lt;&#x2F;p&gt;
&lt;p&gt;This is my desktop for example, which has one socket with eight cores, and 32
GB of memory:&lt;&#x2F;p&gt;
&lt;img class=&quot;no-hover&quot;alt=&quot;CPU topography&quot;src=&quot;&amp;#x2F;img&amp;#x2F;lstopo.png&quot;&#x2F;&gt;
&lt;p&gt;On a server system with multiple cores you should see additional separate NUMA nodes.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;disable-automatic-numa-balancing&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#disable-automatic-numa-balancing&quot; aria-label=&quot;Anchor link for: disable-automatic-numa-balancing&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Disable automatic NUMA balancing&lt;&#x2F;h4&gt;
&lt;p&gt;Automatic balancing is done by &lt;code&gt;autonuma&lt;&#x2F;code&gt; which scans processes’ memory and
migrates pages to try to optimise locality, which is fine for most cases but
can cause latency spikes that are unacceptable in a trading environment.&lt;&#x2F;p&gt;
&lt;p&gt;There are two things that must be done to disable balancing.&lt;&#x2F;p&gt;
&lt;p&gt;Adjust the following sysctl:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #A9B1D6; background-color: #1A1B26;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #0DB9D7;&quot;&gt;echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9E64;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DDFF;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECE6A;&quot;&gt; &#x2F;proc&#x2F;sys&#x2F;kernel&#x2F;numa_balancing&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;and then add &lt;code&gt;numa=off&lt;&#x2F;code&gt; to the boot-loader configuration, or similar, depending
on your boot-loader.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;pin-your-application&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#pin-your-application&quot; aria-label=&quot;Anchor link for: pin-your-application&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Pin your application&lt;&#x2F;h4&gt;
&lt;p&gt;Use &lt;code&gt;numactl --cpunodebind=&amp;lt;node&amp;gt; --membind=&amp;lt;node&amp;gt; &amp;lt;the_program&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It should be noted that the scheduler can still move threads between cores on
that node, so in addition to this you’ll want to pin your application to the
same cores using &lt;code&gt;taskset&lt;&#x2F;code&gt; or similar, like &lt;code&gt;taskset -acp &amp;lt;cores&amp;gt; &amp;lt;pid&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;h4 id=&quot;validate-the-setup&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#validate-the-setup&quot; aria-label=&quot;Anchor link for: validate-the-setup&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Validate the setup&lt;&#x2F;h4&gt;
&lt;p&gt;Check that your process is actually bound correctly with &lt;code&gt;numastat -p &amp;lt;pid&amp;gt;&lt;&#x2F;code&gt;.
This will print out a table detailing its memory usage for each node, where the
node should only be the one you’ve bound the process to.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-if-my-systems-engineers-misconfigure-numa&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-if-my-systems-engineers-misconfigure-numa&quot; aria-label=&quot;Anchor link for: what-if-my-systems-engineers-misconfigure-numa&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
What if my systems engineers misconfigure NUMA?&lt;&#x2F;h3&gt;
&lt;p&gt;If you’re a developer in a trading firm and you’ve deployed your application,
you can still check NUMA at runtime and ensure your allocations land on the
correct node. The library &lt;code&gt;libnuma&lt;&#x2F;code&gt; has an API that allows you to check if NUMA
is available, how many nodes there are, which node a given core belongs to, and
the ability to allocate on a particular node.&lt;&#x2F;p&gt;
&lt;p&gt;In reality, any competent linux&#x2F;production&#x2F;systems&#x2F;sre team in a HFT should
already be working with their developers to ensure everything it set up
correctly.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Conclusion&lt;&#x2F;h3&gt;
&lt;p&gt;In the overwhelming majority of environments NUMA goes by unnoticed, with the
defaults being suitable for most workloads. However in trading, if you ignore
NUMA, the results can be catastrophic. Microseconds of latency across millions
of memory accesses can be the difference between profit and poverty.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
