Plast interview

33. Avoid Copies

In hot HFT paths, copying large aggregates burns L1 bandwidth and increases tail latency. API choices for parameter passing determine whether copies occur and how the optimizer treats aliasing. Design the interface to minimize copies without sacrificing safety or inlining.

struct Order{alignas(64) char sym[64]; int qty; double px;};
void touch(Order o);
void touch_ref(const Order& o) noexcept;
int main(){
  Order a{};
  touch(a);
  touch_ref(a);
}

Part 1.

For the two calls shown, where do copies of Order occur under typical ABIs, and why? Propose a function signature strategy that avoids unnecessary copies while preserving safety and optimizer friendliness.

Part 2.

(1) When is passing Order by value acceptable under the platform ABI, and when not?

(2) Does declaring touch_ref noexcept impact codegen or inlining on the hot path?

(3) How does const& versus pointer affect aliasing assumptions and optimizer freedom?

(4) When would Order&& parameters reduce copies, and what lifetime hazards appear?

(5) How can copy elision/NRVO reduce copies when returning Order from builders?

Answer

Answer (Part 1)

touch(a) passes by value, so Order is copied (fields moved to registers or memory per ABI), incurring data movement proportional to its size. touch_ref(a) passes an address; no Order copy occurs. Prefer void touch_ref(const Order& o) noexcept for read-only, and void mutate(Order& o) noexcept for mutation; use pointers only when nullability is meaningful or for C interop. This avoids large copies, improves alias analysis versus by-value for big aggregates, and keeps inlining straightforward.

Answer (Part 2)

(1) Acceptable when the aggregate is tiny and passed in registers; costly when it spills to memory due to size/alignment.

(2) noexcept removes unwind paths and can shrink call sites; it rarely changes inlining decisions but may enable more optimization.

(3) Both can alias other objects; references are non-null and stable, giving the optimizer slightly stronger assumptions than pointers.

(4) Useful when you can move-from subobjects (e.g., std::string); for trivially copyable PODs, moves equal copies. Beware moved-from lifetimes.

(5) Returning a prvalue can be elided (C++17 guaranteed in some cases); NRVO often removes the copy of a named local.