mmap比fread/fwrite更适合随机访问,因其将文件直接映射至进程虚拟地址空间,跳过内核缓冲区拷贝和系统调用开销;任意偏移读写即内存访问,避免fseek+fread的定位与复制双重开销,但需msync确保落盘,且受虚拟地址空间限制。

mmap 为什么比 fread/fwrite 更适合随机访问?
因为 mmap 把文件直接映射进进程虚拟地址空间,跳过内核缓冲区拷贝和系统调用开销。对任意偏移读写,就是一次内存访问(缺页时由内核按需加载页),没有 fseek + fread 的定位+复制两步开销。
但注意:它不保证数据立刻落盘,msync 才触发回写;且映射区域大小受限于可用虚拟地址空间(尤其 32 位进程)。
- 小文件(
- 大文件(GB 级):建议分块映射(如每次映射 64MB),避免
MAP_FAILED或地址空间碎片 - 只读场景:用
PROT_READ+MAP_PRIVATE,避免写时拷贝开销 - 写入需持久化:映射时用
MAP_SHARED,修改后调msync(MS_SYNC)
如何安全处理 mmap 失败和信号中断?
mmap 可能返回 MAP_FAILED(常因内存不足、文件不可读、offset 非页对齐),且某些系统调用(如 sigwait)可能中断映射过程。
关键点:必须检查返回值,且 offset 必须是 sysconf(_SC_PAGESIZE) 的整数倍——否则 mmap 直接失败,不是自动对齐。
- 用
posix_memalign或aligned_alloc分配对齐 buffer(仅用于MAP_ANONYMOUS场景) - 获取页大小:
long page_size = sysconf(_SC_PAGESIZE);,再对齐offset - 映射失败后,立即
close(fd)并返回错误,不要尝试 fallback 到 fread(语义不同) - 若需信号安全,避免在信号处理函数中调
mmap;改用sigprocmask临时屏蔽信号
C++ 封装 mmap 的常见陷阱
裸用 mmap 容易漏掉资源释放或权限错配。C++ 中常见错误是 RAII 封装没处理 munmap 失败,或析构时未同步。
示例片段(简化):
class MMapFile {
void* addr_;
size_t len_;
int fd_;
public:
MMapFile(const char* path, size_t offset, size_t len)
: addr_(MAP_FAILED), len_(len), fd_(open(path, O_RDONLY)) {
if (fd_ == -1) throw std::runtime_error("open failed");
long page = sysconf(_SC_PAGESIZE);
off_t aligned_off = offset & ~(page - 1); // 向下对齐
addr_ = mmap(nullptr, len + (offset - aligned_off),
PROT_READ, MAP_PRIVATE, fd_, aligned_off);
if (addr_ == MAP_FAILED) {
close(fd_);
throw std::runtime_error("mmap failed");
}
}
~MMapFile() {
if (addr_ != MAP_FAILED) munmap(addr_, len_);
close(fd_);
}
const uint8_t* data() const { return static_cast<const uint8_t>(addr_) + (offset_ % page_size); }
};</const>
- 构造时未检查
open返回值 → 程序崩溃 - 未对齐
offset→mmap失败但误以为成功 -
munmap返回 -1 时忽略 → 内存泄漏(虽罕见但 POSIX 允许) - 未考虑
MAP_POPULATE(预读页)对启动延迟的影响:小文件没必要,大文件可减少首次访问缺页延迟
随机访问性能优化的关键参数
真正影响随机访问速度的不是映射本身,而是页表访问模式和 TLB 命中率。连续访问快,跳着访问慢——这是硬件限制,不是代码问题。
能做的优化很有限,但以下几点实际有效:
- 用
madvise(addr, len, MADV_RANDOM)提示内核“访问模式无局部性”,避免预读浪费带宽 - 若频繁访问固定几个 offset,把它们所在页用
mlock锁住(防止 swap),但慎用——锁太多会耗尽物理内存 - 避免跨页访问单个结构体(如 struct 在页边界上),用
alignas(4096)强制对齐可缓解 - Linux 5.13+ 支持
MADV_WILLNEED按需预热页,比MAP_POPULATE更细粒度
真正卡顿往往来自磁盘 I/O 调度或 SSD 寿命管理,而不是 mmap 本身。测出慢时,先 iostat -x 1 看 %util 和 await,再决定是否换 mmap。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











