2026-07-04
Your 10Gbps NIC delivers packets to RAM at line rate. If the kernel processed each 1500-byte packet individually, the network stack would drown in per-packet overhead: an skb allocation, a TCP header parse, a socket lookup, a receive queue enqueue — repeated 800,000 times per second. Generic Receive Offload (GRO) is the softirq-level trick that fixes this: coalesce consecutive TCP segments from the same flow into one giant "super-packet" before handing it up the stack.
GRO runs inside napi_gro_receive(), called by the driver's NAPI poll loop. For each incoming skb, GRO walks a per-NAPI list of in-progress flows (typically 8 buckets, LRU-managed). A packet is mergeable with an existing flow when:
prev_seq + prev_len (in-order, no gap)gro_max_size (default 65536)When a match hits, GRO doesn't memcpy the payload. It appends the new skb's data pages to the head skb's frag_list or skb_shinfo(head)->frags[], updates the TCP header length, and increments gso_segs. The stack later sees one skb representing up to 45 real packets. TCP's receive path processes one header, does one socket lookup, delivers one 64KB chunk to the socket buffer.
Concrete example: A iperf3 stream on a Mellanox ConnectX-6 pushes ~810k packets/sec at 10Gbps with 1500-byte MTU. With GRO on, the stack sees ~18k skbs/sec — a 45× reduction. Turn it off (ethtool -K eth0 gro off) and CPU usage on the receive core jumps from 12% to 78%, with throughput dropping to ~6Gbps because softirq can't keep up.
The flush trigger matters. GRO holds packets only until the current NAPI poll ends, then flushes everything via napi_complete(). Latency-sensitive workloads care: a merged 64KB super-packet delivers its first byte no sooner than its last. That's why gro_flush_timeout exists — set it to 20000 (ns) to force earlier flush on partial batches.
Rule of thumb: Every GRO merge saves ~600ns of stack overhead per coalesced packet. If you're processing bulk TCP at ≥1Gbps, GRO pays for itself in the first 3 packets of any burst. If you're doing per-packet inspection (IDS, load balancers with L7 rules), disable it — you'll re-split what GRO just combined and pay the cost twice.
