orderbook

A price-time priority limit order book and matching engine in C++20, with no heap allocation on the hot path. A flat-array price ladder runs at about 94M operations a second and beats std::map by 1.6x on a narrow book and 3.8x on a wide one.

What changed

  • Measured about 94M operations a second (10.7 ns per operation) on 5M mixed orders, against about 57M for the std::map version on the same order flow.
  • Showed the gap grows with book width, from 1.5x at 40 price levels to 3.8x at about 6,000, and explained why with cache behavior.

What I worked on

  • I wrote both price ladders behind one template parameter, so the benchmark compares them on identical order flow.
  • I backed it with 36 tests, including a differential test that runs both books on 200k random operations and requires identical trades and book state after every one, clean under AddressSanitizer and UndefinedBehaviorSanitizer.

orderbook matches buy and sell orders the way an exchange does: best price first, and first come, first served at the same price. It supports limit, market, cancel, reduce and modify orders. It is header-only C++20 with no dependencies, and the hot path has no heap allocation, no virtual calls and no floating point.

Two ladders, one benchmark

The book stores each side's price levels in a "ladder", and there are two versions behind the same template parameter:

  • ArrayLadder: a flat array with one slot per price tick, plus a bitmap of non-empty levels. Finding the next best price scans 64 levels per instruction.
  • MapLadder: a std::map keyed by price.

On 5M operations (70% add, 27% cancel, 3% market) with about 10k resting orders over about 170 price levels, ArrayLadder runs at about 94M ops/s against about 57M for MapLadder, with a market-order p99 of 42 ns against 84 ns.

Why the gap grows

At about 170 levels, the whole map fits in L1 cache, so it holds up well. Spread the same orders over more price levels and the tree gets deeper and falls out of cache, while the array's direct indexing barely changes:

Throughput against number of price levels: the array stays near 90M ops/s up to about 1,100 levels while std::map falls from 66M to 17M

The array ladder is 1.5x faster at 40 levels and 3.8x faster at about 6,000. The trade-off is that it needs a bounded price band (65,536 ticks by default).

Built with C++20, CMake, and AddressSanitizer and UndefinedBehaviorSanitizer for testing.