elouann.dev
FR — version française

ProjectsLow-level & embedded

Hotrace

A C hashmap, sped up with SIMD

Period
Feb 2026 – Mar 2026
Context
42 rush
Team
group
Grade
94/100 · outstanding ×1
  • C
  • Assembly
  • Inline GCC x86-64 AT&T
  • Low-level
  • Algorithms
  • Imperative programming
  • Mathematics

View source

A two-and-a-half-day rush: a key / value lookup engine that reads everything from standard input, as fast as possible. A home-made hashmap, and a few critical functions rewritten in inline assembly.

insertionkey"abc"FNV-1a32-bit hashhash & mask= 201k12k23"abc"45k367slot taken: probe the next onecapacity = 2ⁿ, so modulo = bitwise ANDSSE2 comparisonabhhoottrraacgee\0\0················pcmpeqb11111011········pmovmskb16-bit maskone bit per compared byte16 bytes compared in one instructiononly bits before \0 count
DiagramOn the left, inserting a key into the table. On the right, comparing two strings 16 bytes at a time with SSE2.

The problem

The program reads standard input in two phases. Before a blank line, lines come in pairs: a key, then its value. After the blank line, each line is a key to look up: we print its value, or a message if it does not exist. Ranking is based on execution time, on huge files.

The hashmap

All slots live in a single contiguous array (open addressing). A key is hashed with FNV-1a, and its starting position is hash & mask: since the capacity is always a power of two, the modulo becomes a single bitwise AND. If the slot is taken, we move one slot forward (linear probing), which stays in the same cache line.

Each slot also stores its key’s hash: strings are only compared when the hashes match. When the table goes over 75 % full, it doubles in size. The threshold is computed with integers to avoid floating point.

The assembly

strcmp is rewritten with SSE2: pcmpeqb compares 16 bytes at once, then pmovmskb sums the result up as a 16-bit mask. A guard avoids reading 16 bytes across the end of a memory page, which could crash.

strlen uses the SWAR trick (8 bytes per register) to find the null byte, and bsf / bsr find the first set bit, for instance to compute the next power of two.

What I took away

  • Performance is often about memory (cache, allocations) before computation.
  • SIMD means thinking in bit masks rather than loops.
  • Measure before optimising: we had a benchmark script and a test file generator.