Memory-Mapped Files vs Read/Write System Calls
When your application needs to access file data, you have two fundamental approaches: traditional read/write system calls or memory-mapped files (mmap). The choice between them affects performance, complexity, and reliability in ways that aren’t always obvious.
How They Differ
With read/write system calls, your application explicitly requests data from the kernel. You allocate a buffer, call read(), the kernel copies data from the page cache into your buffer, and you get control back. Writing works similarly: you call write() with your data, the kernel copies it to the page cache, and eventually flushes it to disk.
Memory-mapped files skip the explicit copying. When you mmap a file, the kernel maps file contents directly into your process’s address space. Reading from that memory triggers page faults that bring file pages into the page cache, but no additional copy happens. Your code just dereferences pointers. Writing to mapped memory modifies pages in place, which the kernel flushes back to disk asynchronously.
The Performance Trade-Off
For random access patterns, memory-mapped files often win. If you’re jumping around in a large file—think database index lookups or working with binary file formats—mmap eliminates the overhead of repeated system calls and buffer copying. The page fault mechanism becomes your I/O layer, and the virtual memory system handles caching automatically.
For sequential scans through large files, read/write can be faster. You control the buffer size and can tune it for your workload. With mmap, you’re at the mercy of the OS page size and page fault granularity. A sequential read with a large buffer often achieves better throughput and more predictable performance than faulting in 4KB pages one at a time.
The break-even point depends on access patterns, file size, and available memory. Small files that fit entirely in RAM favor mmap—you pay the mapping cost once and get memory-speed access afterward. Large files with sparse access favor mmap. Large files with dense sequential access often favor buffered reads.
The Hidden Costs
Memory-mapped files introduce complexity that traditional I/O avoids. Error handling becomes trickier: with read/write, errors return immediately through the syscall. With mmap, errors during access trigger segfaults or SIGBUS signals. You need signal handlers or special error-checking logic to deal with I/O failures in mapped regions.
Address space exhaustion matters on 32-bit systems or when mapping many large files. Each mapping consumes virtual address space, even if the pages aren’t resident. This isn’t a problem for 64-bit processes with terabytes of address space, but it constrains certain workloads.
Synchronization gets complicated. When you write to a mapped region, you don’t know when it hits disk unless you call msync(). Crashes can leave files in inconsistent states. With write(), you control exactly when data leaves your process, and you can reason about durability more easily.
When to Use Each
Memory-mapped files shine for random access databases, configuration files loaded at startup, executable code loading, and shared memory between processes. The Linux kernel maps shared libraries with mmap, and most database engines use it for index files where random access dominates.
Traditional read/write works better for streaming data processing, network proxies copying data between sockets, log file rotation, and cases where you need precise control over I/O timing. If you’re reading a file once sequentially, copying bytes from source to destination, or working with untrusted data where you want explicit error handling, stick with read/write.
Some workloads mix both approaches. A database might mmap index files for random lookups but use buffered writes for the write-ahead log where durability and ordering matter more than raw throughput.
Platform Differences
Windows and POSIX systems implement memory-mapped files differently, though the concepts align. Windows CreateFileMapping and MapViewOfFile provide similar semantics to mmap, but with different flags and behaviors around file locking and synchronization.
Linux’s implementation includes optimizations like readahead for mapped files and transparent huge pages that can map large files with fewer TLB entries. These details affect whether mmap delivers its theoretical performance benefits in practice.
The choice between memory-mapped files and traditional I/O isn’t about which is universally better. It’s about matching access patterns to mechanisms and understanding the trade-offs between convenience, performance, and control.