Interview Notes & Systems Engineering Appendix: Aegis
This appendix serves as a preparation guide for technical interviews, detailing the "Why" behind the core low-latency systems decisions implemented in Aegis.
1. Why Ring Buffer?
- Answer: A ring buffer (circular queue) uses a fixed-size array to implement a FIFO queue. It avoids dynamic allocation during runtime. Its indexing uses bitwise masks (if size is a power of 2, i.e.,
idx & (Capacity - 1)), which executes in 1 CPU cycle, avoiding division operations.
2. Why Polling (Spin-Waiting) instead of Block/Signal?
- Answer: Blocking/signaling (using mutexes or condition variables) puts the calling thread into a block state. When a new item is available, the OS kernel must wake up the thread, causing context switches and scheduler latency (up to several microseconds). Polling keeps the thread active on the CPU core, ensuring immediate packet consumption (sub-microsecond response).
3. Why Zero Copy?
- Answer: Copying network payloads (using
memcpyor dynamic vector copies) consumes memory bus bandwidth and pollutes CPU caches (L1/L2/L3). By reading packets directly into a pre-allocated pool buffer and passing pointers/offsets, the payload remains in place. This reduces packet processing from thousands of clock cycles to hundreds.
4. Why Open Addressing in Flow Tables?
- Answer: Open addressing (e.g. linear probing) stores hash map keys and values directly in a contiguous flat array. Standard closed addressing (like
std::unordered_mapbucket lists) uses pointers to separate nodes on the heap, causing "pointer chasing" and CPU cache misses. Linear probing stores nodes sequentially, which takes advantage of the CPU's cache-line prefetching (grabbing 64 bytes at a time).
5. Why Thread Pinning (CPU Affinity)?
- Answer: OS schedulers dynamically balance threads across CPU cores. Moving a thread to another core destroys L1/L2 cache locality, because the caches on the new core do not contain the thread's active flow table or packet data. Pinning locks the thread to a specific hardware execution unit, ensuring warm caches and eliminating core migration costs.
6. Why Cache Alignment (alignas(64))?
- Answer: CPU cores read memory in 64-byte blocks called cache lines. If two variables (e.g., the read index and write index of a queue) are located in the same 64-byte block, writing to one invalidates the cache line for the other core, causing it to reload the line from main memory. This is called False Sharing. Aligning variables to separate 64-byte boundaries prevents this bouncing.
7. Why std::string_view?
- Answer:
std::stringallocates heap memory to store characters (unless short-string optimization applies) and copies the bytes.std::string_viewis a lightweight structure containing a pointer (const char*) and a length (size_t). It provides read-only access to substrings (like the SNI extension inside a packet buffer) without allocating or copying memory.
8. Why Atomics and Memory Orders (instead of Mutex)?
- Answer: Atomics are executed directly by the CPU hardware (e.g.,
LOCK CMPXCHGon x86). They do not require operating system intervention. Memory ordering (likestd::memory_order_acquireandstd::memory_order_release) instructs the compiler and CPU not to reorder read/write instructions across threads, establishing a sync boundary without locking execution.
9. Why No Virtual Functions on the Fast Path?
- Answer: Virtual functions resolve at runtime using a virtual table (vtable) pointer look-up, which adds an extra memory indirection (cache-miss potential). It also prevents the compiler from inlining the function. We use static polymorphism (templates, CRTP) or compile-time resolution to ensure clean inline paths.
10. Why No Exceptions (noexcept) on the Fast Path?
- Answer: When an exception is thrown, the runtime must perform stack unwinding, which involves metadata lookup, type matching, and stack frame destruction. This introduces massive, non-deterministic latency spikes. By marking fast-path functions
noexcept, the compiler can optimize registers and instruction paths, knowing no stack unwinding will occur.