Quant developer HFT course · Module 6: The operating system
Virtual memory, pages and the TLB
The question
Your process has a 2 GB order book and reference data in memory. A colleague suggests huge pages. What problem do they solve, and what are the catches?
Addresses are virtual
Every address a program uses is virtual. The processor translates it to a physical address through page tables that the kernel maintains for each process. Memory is managed in pages, 4 KB by default on x86-64 Linux. The translation gives each process its own address space, lets the kernel put pages anywhere in physical memory, and enforces permissions.
On x86-64 the page tables have four levels (five on systems that enable larger address spaces). Translating an address from scratch means reading one entry at each level: four dependent memory reads before the actual load can begin.
The TLB
To avoid that on every access, the core caches recent translations in the translation lookaside buffer. There are small, fast first-level TLBs for data and instructions and a larger second-level TLB, with on the order of a thousand to a few thousand entries in total on current cores.
A TLB hit costs essentially nothing extra. A miss triggers a page walk in hardware. The walk's reads go through the data caches, so for a recently walked region they may hit L1 or L2; for a large, randomly accessed region they miss too, and the walk can cost as much as a memory access, before the access itself.
TLB reach
The amount of memory the TLB can cover is entries times page size. With roughly 1,500 second-level entries and 4 KB pages, that is about 6 MB. A process that touches a 2 GB book randomly is far beyond that: most accesses to cold parts of it need a page walk as well as a cache miss.
That is the problem huge pages solve. With 2 MB pages, the same number of entries covers about 3 GB; with 1 GB pages, more than any process needs. Page walks almost disappear, and the walk that does happen is one level shorter.
Getting huge pages on Linux
- Explicit huge pages (hugetlbfs). Reserve a pool at boot (
hugepages=on the kernel command line, orvm.nr_hugepages), then map from it withmmap(MAP_HUGETLB)or files in a hugetlbfs mount. Predictable: the pages are reserved, contiguous and never split. This is what latency-sensitive systems generally use for their big preallocated regions. - Transparent huge pages (THP). The kernel promotes suitably aligned anonymous memory to 2 MB pages automatically, and a background thread (
khugepaged) collapses small pages into huge ones. Convenient, but the work happens at times you do not choose: compaction to find a free 2 MB region can stall an allocation, and collapsing scans memory in the background. Many trading setups set THP tomadvise(only regions that ask for it withmadvise(MADV_HUGEPAGE)) ornever, and use explicit huge pages instead.
The catches
- Reservation. Explicit huge pages are taken from memory at boot whether used or not, and allocation fails rather than falling back if the pool is empty.
- Granularity. Memory is allocated, faulted and freed 2 MB at a time. Touching one byte of a huge page faults in all of it.
- Fragmentation. After a machine has been up for a long time, finding contiguous 2 MB regions for new huge pages may be impossible, which is another reason to reserve at boot.
- Not a fix for cache misses. Huge pages remove translation misses, not data misses. If the book is accessed randomly, the data still misses in cache.
TLB shootdowns
When the kernel changes or removes a mapping (munmap, mprotect, page migration, memory being returned by free), every core that might hold the old translation must flush it. The kernel sends an inter-processor interrupt to those cores: a TLB shootdown. Each interrupted core stops what it is doing to handle it. A latency-critical thread on an isolated core can be hit by shootdowns caused by another thread in the same process unmapping memory, which is one more reason to map everything at start-up and never unmap on a live system.
Check yourself
Question 1 of 3
What does a TLB miss cost on x86-64?
The walk is done by hardware and needs no kernel involvement, unless the page is not mapped at all, which would be a page fault.
Question 2 of 3
With about 1,500 TLB entries, roughly how much memory can be covered with 2 MB pages instead of 4 KB pages?
Reach is entries times page size. A factor of 512 in page size multiplies the reach by 512.
Question 3 of 3
Why do many latency-sensitive setups disable transparent huge pages or restrict them to madvise?
The benefit is real, but the timing is the kernel's. Explicit, preallocated huge pages give the same benefit predictably.
In the interview
Say your own answer out loud first, then open the one we would give.
What is the TLB?
A cache of virtual-to-physical address translations in the core. A hit costs nothing extra; a miss makes the hardware walk the page tables, up to four dependent reads on x86-64, which can each miss in cache. Large, randomly accessed working sets cause many misses with 4 KB pages.
Why use huge pages?
They multiply how much memory each TLB entry covers, from 4 KB to 2 MB or 1 GB, so large working sets stop paying page walks. Trading systems usually reserve explicit huge pages at boot and map their big preallocated regions from them.
What is a TLB shootdown?
When a mapping changes, the kernel interrupts every core that may cache the old translation so it can flush it. Those interrupts land on whatever those cores are running, including latency-critical threads, which is why live systems avoid unmapping memory after start-up.