Quant developer HFT course · Module 7: The network
Where the network stack spends its time
The question
A packet arrives at the server's network card. What happens before recv() returns it to your code, and where does the time go?
The receive path in Linux
- The card receives the frame, checks it, and writes it by DMA into a buffer from its receive ring in host memory. It may timestamp it in hardware.
- The card signals the host. Traditionally with an interrupt. To avoid an interrupt per packet under load, cards coalesce: they wait a few microseconds or a few packets before interrupting. Coalescing saves CPU and adds latency.
- NAPI polling. The interrupt handler schedules a software interrupt (softirq) which polls the ring and processes a batch of packets, re-enabling interrupts when the ring is empty.
- Protocol processing. The driver builds socket buffers, the IP and UDP layers validate and route each packet, and the packet is matched to a socket and appended to its receive queue.
- Waking the reader. If a thread is blocked in
recv, it is woken and must be scheduled. - The copy.
recvcopies the payload from kernel memory into the application's buffer and returns.
On a well-tuned host this totals a few microseconds; on a busy, untuned host, tens of microseconds with a long tail, from coalescing timers, softirq scheduling, cache misses in the kernel's data structures and wake-up latency.
Tuning the kernel path
- Interrupt coalescing:
ethtool -Cto reduce or disable it for latency (rx-usecs 0), at the cost of more interrupts. - Interrupt affinity: route the card's receive queue interrupts to a core close to the application's thread, on the same socket as the card (PCIe devices belong to a socket), and not to the application's own isolated core.
- Receive-side scaling: cards hash flows to several receive queues; steering the feed's flows to a dedicated queue, and that queue to a chosen core, keeps them away from other traffic.
- Busy polling:
SO_BUSY_POLL(or thenet.core.busy_readsysctl) makes arecvon an empty socket poll the card's ring for a while instead of sleeping, so the reading thread processes its own packets without waiting for an interrupt. It costs CPU and brings the kernel path closer to bypass latency. - Avoid the wake-up: a thread spinning on non-blocking reads never sleeps, so step 5 disappears.
Timestamps
To know where the time goes, you need timestamps at known points:
- Hardware receive timestamps taken by the card when the frame arrives, retrieved with
SO_TIMESTAMPINGand the hardware flags (the card must support it and be enabled with theSIOCSHWTSTAMPioctl). - Software timestamps in the kernel when the packet is processed.
- Application timestamps when your code sees it and when it sends a response.
- Hardware transmit timestamps for outgoing packets, delivered back through the socket's error queue.
The difference between the card's receive timestamp and the application's is the host's receive latency. Between the card's receive and transmit timestamps lies the whole software response, tick-to-trade (Module 8).
Card clocks must be synchronised with the host clock (with phc2sys) and with the outside world (with PTP) for those comparisons to mean anything (Module 10).
Transmit side
Sending goes through the stack in reverse: the send system call copies the data into a socket buffer, the protocol layers build headers, the packet is queued to the card, the driver writes a descriptor and rings a doorbell register, and the card fetches the packet by DMA and sends it. The same tools apply: fewer system calls, no queuing disciplines that delay packets, and kernel bypass for the critical path.
When tuning is not enough
Every step in the kernel path is general-purpose code designed for throughput and fairness across all processes. The lowest latencies come from removing it, which is the subject of the next lesson.
Check yourself
Question 1 of 3
What does interrupt coalescing trade?
The card batches packets before interrupting. That is efficient under load and adds up to the coalescing delay to every packet.
Question 2 of 3
What does SO_BUSY_POLL change?
Busy polling lets the reading thread drive packet processing itself, removing interrupt and wake-up delays at the cost of CPU.
Question 3 of 3
Which pair of timestamps measures the full software response to a market data packet?
Hardware timestamps at both ends put every software stage, including the kernel and the card's queues, inside the measurement.
In the interview
Say your own answer out loud first, then open the one we would give.
What happens between a packet arriving at the NIC and recv returning?
The card DMAs the frame into its receive ring and, after any coalescing delay, interrupts; NAPI polls the ring in a softirq; the driver, IP and UDP layers process the packet and queue it on the socket; a blocked reader is woken and scheduled; recv copies the data out. Each step adds latency and variance.
How would you reduce receive latency without kernel bypass?
Disable or reduce interrupt coalescing, steer the feed to a dedicated receive queue whose interrupts go to a core near the card but not the application's core, use busy polling, keep the reader spinning on non-blocking reads so it is never woken, and use recvmmsg to drain batches.
How do you measure network latency inside a server?
With hardware timestamps from the card on receive and transmit, software timestamps in the kernel and application timestamps in the code, all on synchronised clocks. Differences between them locate the time: host receive path, application processing, and transmit path.