Skip to content
Work Practice Products Coding interviews Machine learning coding Quant developer HFT course Blog Method Why me About Enquire Book a call

Quant developer HFT course · Module 1: Reading hot code

Copies, moves and object layout

Lesson · 10 min

The question

What is sizeof this struct, and could you make it smaller without removing a field? Then: where in this function is the struct copied?

Alignment and padding

Every type has an alignment: the address of an object must be a multiple of it. On mainstream 64-bit platforms a double or int64_t aligns to 8, an int32_t to 4, a char to 1. The compiler lays fields out in declaration order and inserts padding so that each one lands on a valid address. It also pads the end of the struct, so that in an array every element's fields stay aligned.

struct A { char c1; double p; char c2; };   // sizeof 24 on x86-64
struct B { double p; char c1; char c2; };   // sizeof 16
081624as declaredc···ppppc·······reorderedppppcc··
The same three fields in two orders, two bytes per square. Declared as char, double, char the struct is 24 bytes, ten of them padding; with the double first it is 16.

In A, seven bytes of padding follow c1 so that p starts at offset 8, and seven more follow c2 so the size is a multiple of 8. B holds exactly the same data in 16 bytes. The rule of thumb is to order fields from the largest alignment to the smallest; offsetof and sizeof in a static_assert confirm it.

Why care about eight bytes? Because the hot path rarely reads one struct. It reads arrays of them, and a third smaller means a third fewer cache lines for the same scan. It also matters when a struct is copied into a message or a ring buffer slot, where size is bandwidth.

Hot and cold fields

Size is not the only layout question. An order object might carry fifteen fields, of which the matching path reads four: price, remaining quantity, side, and the next order at that level. The rest (client id, timestamps, the text tag) are read only for reporting. Putting the four hot fields together, at the front of the struct or in a separate array, means one cache line per order on the hot path instead of two or three. This is the same idea as the vector and the list, applied inside one object.

Copies you did not mean

The other half of object cost is copying it. Several common forms of C++ copy without saying so:

void route(Order o);                         // copies the order at every call
for (auto q : quotes) { ... }                // copies every quote
std::vector<Order> snap = book.orders();     // copies if orders() returns by value,
                                             // or a const& that you then copy into snap
auto [px, qty] = best_bid();                 // fine if best_bid returns a small pair by value

For a 24-byte quote a copy is cheap: a few register moves. For an object holding a std::string and a std::vector, a copy allocates and copies both. The cost of a copy is the cost of copying everything the object owns.

Rules that keep copies under control:

  • Pass large or owning types by const& when the function only reads them.
  • Pass small, trivially copyable types (a few machine words) by value; references to them can cost more than the copy.
  • Use const auto& in range-for unless you need a copy.
  • When a function needs its own copy, take the parameter by value and std::move it into place, so callers with temporaries pay nothing (Module 2).

Seeing the layout

Two habits are worth mentioning in an interview. Put static_assert(sizeof(Quote) == 24) next to any struct that crosses a boundary (shared memory, a file, the network), so a change in layout fails the build rather than corrupting data. And use a tool such as pahole, or the compiler's own layout dump, to see the padding in a large struct.

Check yourself

Question 1 of 3

On x86-64, what is sizeof(S) for struct S { char a; int32_t b; char c; };?

Question 2 of 3

Why does field order inside a struct matter for a hot-path scan over an array of them?

Question 3 of 3

Which of these makes an unintended copy of each element?

In the interview

Say your own answer out loud first, then open the one we would give.

How would you shrink this struct without removing fields?

Reorder the fields from largest alignment to smallest so the compiler needs no padding between them, and check with sizeof and offsetof in a static_assert. If some fields are rarely read, I would also consider moving them to a separate cold structure so the hot one fits in fewer cache lines.

Should I always pass by const reference?

For types that own resources or are larger than a couple of machine words, yes, when the function only reads. For small trivially copyable types such as a price, an id or a small struct of two integers, passing by value is as cheap or cheaper, and avoids aliasing questions for the compiler.

Why put a static_assert on sizeof?

Structures that go into shared memory, files or network messages must match on both sides. An assert turns an accidental layout change, such as a new field or a different compiler setting, into a build failure instead of corrupted data in production.