Quant Memo
Core

Data-Oriented Design

Data-oriented design lays out data by how it's actually accessed rather than by object identity, packing the fields a hot loop touches together in memory instead of scattering them across separate objects.

Prerequisites: Cache Lines and False Sharing

Object-oriented design groups data by identity: an Order object bundles its price, quantity, side, timestamp, and client ID together because conceptually they all describe one order. That grouping is intuitive to write, but it's not how a hot loop actually accesses the data. A function that sums the quantities of ten thousand orders touches only the quantity field of each one — yet with orders stored as an array of Order objects, every quantity read drags the price, side, timestamp, and client ID along with it into cache, because they're all interleaved in the same memory.

Data-oriented design flips the priority: instead of "group fields by which object they describe" (array-of-structs), lay data out as "group fields by which loop touches them together" (struct-of-arrays) — one contiguous array of all prices, a separate contiguous array of all quantities, and so on. A loop that only needs quantities now streams through a dense array of nothing but quantities, using the full width of every cache line it fetches instead of wasting most of it on fields the loop never reads.

Array-of-structs optimizes for "get me everything about one order." Struct-of-arrays optimizes for "get me one field across every order." Hot loops in quant systems almost always want the second: sum all quantities, find the max price, filter by side — one field, many records — which is exactly the access pattern struct-of-arrays is built for.

Two layouts, one loop

array-of-structs price/qty/side interleaved per order reading qty pulls price+side too struct-of-arrays each field its own array — sum qty touches only qty
Struct-of-arrays lets a loop over one field stream through pure, dense data instead of skipping past unrelated fields packed in between.

Worked example

Summing the quantity field across 1 million orders. With array-of-structs (each Order 48 bytes: price, quantity, side, timestamp, client ID), the loop reads 48 MB total to extract 8 MB of actual quantity data — 40 MB of that traffic is fields the loop never uses, still fetched because they share cache lines with quantity. Benchmarked, this takes roughly 9 milliseconds. With struct-of-arrays, the quantities live in their own contiguous 8 MB array; the loop reads exactly the 8 MB it needs, and the CPU's prefetcher, seeing a purely sequential stream, keeps the pipeline fed almost perfectly. Benchmarked, this takes roughly 2 milliseconds — better than 4x faster, purely from layout, with the summation logic completely unchanged.

What this means in practice

Data-oriented design pays off most in exactly the workloads a market-data or backtesting engine runs constantly: iterating one field across a large collection — every trade's price, every quote's size, every bar's close. It's not free: struct-of-arrays makes "give me everything about order #4821" more awkward (four separate array lookups instead of one), so systems often keep both layouts, an array-of-structs form for per-record logic and a struct-of-arrays form specifically for hot analytical loops.

Reorganizing data into struct-of-arrays for the sake of it, without profiling first, is a common form of premature optimization — the layout genuinely helps for large, sequential, single-field access patterns, but adds real complexity for code that legitimately needs many fields of one record at once. Measure the access pattern before restructuring around it.

Related concepts

Practice in interviews

Further reading

  • Acton, 'Data-Oriented Design and C++' (CppCon)
ShareTwitterLinkedIn