Just how large can the performance difference caused by page faults be? Let’s write a benchmark to find out.
Simulating Page Fault Behavior
To build this benchmark, we need to understand two low-level Linux syscalls for memory management: mmap and madvise. For memory allocation, mmap can be called with the anonymous and private mapping flags MAP_ANON and MAP_PRIVATE. Memory allocated this way is in a page-fault state — any access to the allocated region will trigger a page fault. We can exploit this to measure the cost of accessing memory through page faults. Meanwhile, madvise can advise the kernel to prefetch memory in advance. By applying prefetching to mmap-allocated memory, we can then measure the cost of accessing already-prefetched memory:
|
|
Using the bench tool, we can run and obtain the following results:
|
|
As allocation size grows, the performance gain from prefetching becomes very significant:

MADV_DONTNEED v.s. MADV_FREE
Worth noting is that we used the MADV_DONTNEED flag to release memory. For the other release mode, MADV_FREE, its lazy-release semantics mean that memory marked for release does not immediately enter a page-fault state, which could affect subsequent memory operations and should in principle yield some performance improvement. We can verify the effect of switching to MADV_FREE with a simple test:
|
|
We can obtain the following results:
|
|
We can see that using MADV_FREE in the page-fault scenario brings little change:
|
|
Conversely, in the prefetch scenario, MADV_FREE does not deliver the better performance it claims — instead it introduces more overhead:
|
|
This is rather interesting. Why does this happen? When kernel support for MADV_FREE was added, there was a mailing list discussion about it:
|
|
Further verifying these claims would likely require digging into the kernel source, but the explanation is theoretically plausible. Since MADV_FREE merely defers the release of memory, it still needs to release memory when pressure is high. Our benchmark repeatedly allocates large blocks of memory, which easily creates the appearance of heavy memory pressure — this is an extreme simulation, and actual behavior may more closely resemble the earlier page-fault test where memory pressure had not yet built up. In most cases MADV_FREE is slightly better than MADV_DONTNEED, but under high pressure MADV_FREE can be worse than MADV_DONTNEED.
Further Reading
缺页错误产生的性能差异究竟能够有多大?不妨做一个基准测试。
模拟缺页行为
想要实现这样的基准测试,需要了解 Linux 下对内存管理的两个底层的系统调用:mmap 和 madvise。
对于内存分配场景,mmap 可以使用匿名、私有映射两个参数 MAP_ANON 和 MAP_PRIVATE,
这时候创建的内存实际上属于缺页状态,任何对其申请到内存区域的访问行为都将导致缺页,利用这一原理,
便可以用来测量缺页时访问内存的成本;而 madvise 能够用来给内核提供建议,提前对内存进行预取,
于是可以利用这一点,对 mmap 的来的内存执行预取操作,进而测量预取后访问内存的成本:
|
|
使用 bench 工具,可以运行得到下面的结果:
|
|
可以看到随着分配内存的增大,预取带来的性能提升是非常可观的:

MADV_DONTNEED v.s. MADV_FREE
值得一提的是这里使用的是 MADV_DONTNEED 参数来释放内存。对于另一种释放模式 MADV_FREE 而言,因为其本质是懒惰释放,使用这个参数宣告释放的内存不会立刻进入缺页状态,进而对后续的内存操作可能带来影响,原则上应该会带来一定的性能提升。那么,但根据同样可以简单的验证换用 MADV_FREE 参数后带来的影响:
|
|
同样的可以得到下面的结果:
|
|
可以看到使用 MADV_FREE 缺页场景下并没有带来多大变化:
|
|
相反,对于已经预取的情况下并没有 MADV_FREE 宣称的那样具有更好的性能,反而带来了更多的性能损耗:
|
|
这就比较有有趣了。为什么会这样呢?在内核增加 MADV_FREE 支持的时候有这样一个邮件列表讨论:
|
|
至于如何进一步验证这些说法可能需要把内核代码拿出来溜了,不过这个解释读起来从理论上分析是比较可信的。
由于从 MADV_FREE 的行为来看只是延缓了释放的行为,实际上当内存紧张时还是需要释放的。
我们上面的基准测试反复申请内存,很容易造成内存紧张的假象,某种程度上属于极端的模拟情况,
实际状态下可能跟此前的 PageFault 测试中还未造成内存高压相似。
即大部分情况下 MADV_FREE 略好于 MADV_DONTNEED,
在面临高压时 MADV_FREE 反逊于 MADV_DONTNEED。