Zero-Copy: How Servers Send Files Without Touching the Data
Sending a file the naive way copies the same bytes four times and crosses the kernel boundary four times, even though the application never reads them. sendfile() and scatter-gather DMA cut that down to zero CPU copies — the reason nginx, Kafka, and Netty can push huge files without breaking a sweat.
Naive file sending (read() then write()) copies the data four times and crosses the kernel-user boundary four times: disk to page cache, page cache to app buffer, app buffer to socket buffer, socket buffer to the network card — two of those copies are data the program never actually reads. sendfile() sends the file straight from the page cache to the socket without ever entering user space, cutting it to three copies and one syscall. Scatter-gather DMA goes further, letting the network card read directly from the page cache — two copies, zero CPU copies, both handled by hardware. That is what "zero-copy" really means: zero CPU copies, not zero copies.
Transcript
A courier drops a package at your door. You carry it through your house to the mail truck out back — but you never opened it. Why route it through your house? That's exactly what a server does sending a file over the network: it drags every byte through your program's memory for nothing. Zero-copy teaches it to skip the house.
Send a file the naive way: read() pulls it off disk into a kernel buffer, then copies it AGAIN into your app's memory. Two copies, one context switch, just to receive it. Then write() copies it into a kernel socket buffer, and the network card sends that to the wire by DMA. Four copies, four kernel crossings — your CPU hand-carried two.
But your program never opened the package — it's just forwarding it. So the sendfile call tells the kernel: send this file straight to this socket. The package skips your house. Now the data flows from disk, to the kernel's page cache, to the socket — and never enters your app's memory at all. One syscall instead of two, and one copy gone.
We can go further. A modern network card can read straight from the page cache using scatter-gather DMA — so the kernel just hands it the package's location, not the package. Now only two copies remain, and your CPU does ZERO of them — both are hardware DMA. THAT'S what zero-copy means: zero CPU copies, not zero copies.
This is why a file server, a video stream, or Kafka can push data at wire speed: the bytes just pass through, never wasting a round trip through user space. nginx, Netty, and Kafka all lean on sendfile. When your app is really just a pipe, the fastest thing it can do is get out of the way.
So the fastest way to move data is to not move it — at least, not through your program. If you're not reading the bytes, don't copy them into your memory. Next time you're just shuttling a file from disk to a socket, remember what your program is: a doorway, not a warehouse. Skip the house.