How mmap Improves Sequential Reading in C
Memory-mapped file I/O using the mmap system call is an
efficient technique in C that maps a file's contents directly into a
process's virtual memory address space. This article explains how
mmap works, details the technical reasons it improves
sequential reading performance over traditional file access methods, and
demonstrates how to implement it in a C program.
Understanding mmap
In POSIX-compliant operating systems, mmap() is a system
call defined in <sys/mman.h>. Instead of reading
bytes from a file into an application-allocated buffer using
read() or fread(), mmap() creates
a direct mapping between a file descriptor and the process’s virtual
memory pages.
Once mapped, the operating system assigns a pointer to the beginning of the memory region. The application can then read data directly from the pointer as if the entire file were a contiguous array residing in RAM. Actual physical disk reads are handled transparently by the operating system’s page fault mechanism.
The Bottlenecks of Standard Read Operations
Traditional sequential reading using the read() system
call suffers from two main performance penalties:
- Context Switching Overhead: Every call to
read()requires a transition from user mode to kernel mode and back. Reading a large file in small chunks generates thousands of context switches, which consume significant CPU resources. - Double Buffering: When reading a file traditionally, the operating system first transfers data from the storage device into its own page cache (kernel space), and then copies that data into the buffer provided by the application (user space). This extra memory-to-memory copy consumes CPU cycles and memory bandwidth.
How mmap Enhances Sequential Reading
Memory mapping addresses the inefficiencies of conventional I/O in several specific ways:
- Zero-Copy Access:
mmap()eliminates the intermediate copy between kernel space and user space. The process reads directly from the OS page cache through page table entries, avoiding redundant data movement. - Minimized System Calls: A file is mapped with a single system call. Once mapped, sequential traversal through the memory buffer requires no further system calls, drastically reducing context-switch overhead.
- Kernel Read-Ahead Optimization: Modern operating systems monitor memory access patterns. When a program reads sequentially through a mapped region, the kernel anticipates upcoming reads and initiates asynchronous read-ahead operations to load pages into memory before the program requests them.
- Sequential Access Hints via
madvise(): Developers can use themadvise()system call with theMADV_SEQUENTIALflag to explicitly inform the kernel of sequential access. This prompts the OS to aggressively prefetch data and immediately release pages behind the current read position, keeping the memory footprint low.
Implementing mmap in C
The following example demonstrates how to map a file, read its contents sequentially, and unmap it cleanly:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <file_path>\n", argv[0]);
return EXIT_FAILURE;
}
int fd = open(argv[1], O_RDONLY);
if (fd == -1) {
perror("Error opening file");
return EXIT_FAILURE;
}
// Get file size
struct stat sb;
if (fstat(fd, &sb) == -1) {
perror("Error getting file size");
close(fd);
return EXIT_FAILURE;
}
size_t length = sb.st_size;
if (length == 0) {
close(fd);
return EXIT_SUCCESS;
}
// Map file into memory
char *data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
perror("Error mapping memory");
close(fd);
return EXIT_FAILURE;
}
// Tell kernel to optimize for sequential access
madvise(data, length, MADV_SEQUENTIAL);
// Sequential read traversal
unsigned long long checksum = 0;
for (size_t i = 0; i < length; i++) {
checksum += (unsigned char)data[i];
}
printf("Sequential read complete. Checksum: %llu\n", checksum);
// Cleanup resources
munmap(data, length);
close(fd);
return EXIT_SUCCESS;
}When to Use mmap
mmap() is particularly advantageous for high-throughput
sequential reads of large datasets, such as database storage engines,
index parsers, and binary log analyzers. However, for extremely small
files or simple linear pipelines involving standard input/output
streams, the overhead of setting up page tables and handling page faults
may offset the benefits. For sustained sequential reading of moderately
large to massive files, mmap remains one of the fastest I/O
mechanisms available in C.