Quant Memo
Core

Zero-Copy I/O and mmap

A normal file read copies data from disk into a kernel buffer and then again into your program's own memory; mmap and other zero-copy techniques skip the second copy by mapping the kernel's buffer directly into your address space.

Prerequisites: System Calls and Context Switches

Reading a file the ordinary way — read() into a buffer — actually involves two copies, not one. The kernel first reads the data from disk into its own internal page cache, then read() copies that data a second time from the kernel's memory into your program's buffer, crossing the boundary between kernel space and user space. For a large file read repeatedly, like a historical tick-data archive replayed for backtesting, that second copy is pure overhead: the bytes are identical, just duplicated into a different address space.

Zero-copy I/O eliminates that redundant copy. mmap() is the most common mechanism: instead of copying file data into a buffer, it maps the kernel's page cache for that file directly into your program's virtual address space. Reading data[1000] after an mmap doesn't call into the kernel at all in the common case — it's a direct memory read, and the kernel's normal virtual-memory machinery quietly handles bringing the right disk page into physical memory the first time it's touched (a page fault, invisible to the program).

mmap turns "copy the file into my memory" into "let my memory point at the same physical pages the kernel already has cached." The data is read from disk exactly once regardless of how many times or how randomly your program accesses it afterward, and re-reading a file that's already in the page cache from a previous run costs almost nothing.

Two copies versus zero

read() disk kernel buf user buf two copies: disk→kernel, kernel→user mmap() disk kernel page user address space points here directly — one copy total
A normal read crosses kernel/user boundary twice; mmap maps the kernel's own cached pages so user code reads them in place.

Worked example

Replaying a 4 GB file of historical tick data for a backtest, sequentially, with buffered read() calls in 1 MB chunks: roughly 1.4 seconds on a warm page cache (the file was already read once, so no disk I/O, but the kernel→user copy still happens every time). The identical replay using mmap() on the same file: roughly 0.35 seconds — the data was already resident in the kernel's page cache from the earlier run, and mapping it avoided ever copying those 4 GB across the kernel/user boundary a second time. Random-access replay (jumping to specific timestamps rather than reading sequentially) shows an even larger relative gap, since mmap also avoids repeated read()/seek() system-call overhead for each jump.

What this means in practice

mmap is a strong fit for large, read-mostly datasets accessed repeatedly or randomly — historical market-data archives, memory-mapped lookup tables, shared configuration that many processes read. It is a poor fit for small files read once (the setup cost of establishing the mapping isn't worth it) and for write-heavy workloads, where the interaction between memory-mapped writes and disk flushing (msync) is easy to get wrong and can silently lose data on a crash if not handled carefully.

A memory-mapped file that another process modifies underneath you changes what your program reads without any explicit notification — there's no error, the bytes at that address simply become different. Code that assumes a memory-mapped region is a stable, private snapshot (as a normal read()-based copy would be) can be silently wrong if the underlying file is being written concurrently; explicit locking or a private (MAP_PRIVATE, copy-on-write) mapping is often required to avoid this.

Related concepts

Practice in interviews

Further reading

  • Kerrisk, The Linux Programming Interface (ch. 49, 'Memory Mappings')
ShareTwitterLinkedIn