bigywater.tech
Rust · 136

内存映射文件

mmap 把文件当内存来读写,听起来很美。它到底惰性到什么程度?顺序读与随机读各快多少?为什么在 Rust 里它必须是 unsafe?这篇用 RSS 观察、计时和一次真实的 SIGBUS 回答。

2.0 → 1026.0 MB
映射 1 GiB 文件后与读遍所有页后的进程 RSS
约 179×
随机读:mmap 对 seek + read_exact
SIGBUS
映射的文件被截断后访问被截掉的页的结果

映射与读写

实测 · 本机执行输出
  映射 16 字节,当作 &[u8] 读:"mmap"
  通过 MmapMut 改前 5 字节并 flush 后,用普通 fs::read 读到:"HELLO mmap world"

memmap2 0.9 从 crates.io 下载(需要联网)。普通文件读写见std::fs 文件读写;缓冲读取见BufReader 与缓冲读取。

惰性加载

实测 · 本机执行输出
  映射 1 GiB(set_len 创建的稀疏文件)之后 RSS 从 1.9 MB 变成 2.0 MB(映射本身不占物理内存)
  写了 2 个字节(分别在文件头和 500 MiB 处)之后 RSS = 2.0 MB
  每 4 KiB 读一个字节,遍历全部 262144 个页之后 RSS = 1026.0 MB

RSS 通过 ps 读取,是系统报告值。

顺序读

实测 · 本机执行输出
  mmap 映射(含 map 调用)后直接遍历:   47.8 ms  (校验 33552486656)
  read_to_end 读进 Vec 再遍历:       65.8 ms  (校验 33552486656)
  1 MiB 缓冲循环读边读边算:          49.7 ms  (校验 33552486656)

页缓存已热,没有测冷读。注意:基准里每一轮的结果都要 black_box,否则优化器会删掉没人用的求和,取多轮最小值时会选中被删掉的那一轮,得到接近 0 的耗时(见 Benchmark 一篇)。

随机读

实测 · 本机执行输出
  mmap 随机读 20 万次:           0.86 ms  (校验 10133616913503485778)
  seek + read_exact 20 万次:   153.93 ms  (校验 10133616913503485778)

SIGBUS

实测 · 本机执行输出
  退出码 -10(负数 = 被信号终止:10),stdout:
    映射后读 m[60000] = 7
    把文件截断到 4096 字节(另一个句柄/进程也可能这么做)

unsafe 的含义见unsafe 的安全边界。

分步演示

mmap
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。memmap2 0.9;计时 release
    • 没有验证:冷页缓存下的读取、madvise / MAP_POPULATE、大文件写入时的脏页回写性能、Linux 上的 RSS 与 SIGBUS 信号编号、Windows 的映射语义、mmap 与 O_DIRECT 的比较
    京ICP备2026067502号-1京公网安备11010502063487号