You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

零初始化内存访问为何产生双倍数量的页错误?

Rust数组零初始化与非零初始化的性能差异解析

测试代码

use std::time::Instant;

fn main() {
    let mut array = vec![0i32; 64 * 1024 * 1024];

    let start = Instant::now();
    workload(&mut array);
    let dur = start.elapsed().as_millis();

    println!("duration: {}ms", dur);
}

fn workload(arr: &mut [i32]) {
    for i in 0..arr.len() {
        arr[i] *= 3;
    }
}

测试现象

使用--release模式运行时,零初始化数组的耗时为278ms;若将数组初始化值改为1,耗时骤降至17ms。

Perf分析结果

通过perf工具统计到:

  • 零初始化场景:131K次minor page faults
  • 1初始化场景:65K次minor page faults

注:在4KiB页大小的机器上,256MB(6410241024个i32元素)的数组共需65536个物理页。


1. 零初始化场景为何会产生双倍数量的页错误?

这是内核零页优化与**写时复制(COW)**机制共同作用的结果:

  • 当使用vec![0; N]创建数组时,Rust会调用alloc_zeroed分配内存,内核会将虚拟地址空间映射到只读共享零页,此时并未实际分配私有物理页,也不会触发页错误。
  • 进入workload执行写操作时,每个物理页会触发两次minor page fault:
    1. 第一次访问页内元素时,读操作会触发minor fault,将虚拟页正式映射到共享零页;
    2. 执行arr[i] *=3的写操作时,由于共享零页是只读的,内核会触发第二次minor fault,为该页分配私有物理页、复制零页内容并标记为可写(COW机制)。
  • 而非零初始化的vec![1; N]会在数组创建阶段,通过memset直接向每个物理页写入初始值1,此时每个页仅触发一次minor fault(分配并初始化物理页),后续workload的写操作无需再触发页错误。

2. 页错误差异是速度大幅提升的核心原因吗?

是的,页错误处理是典型的内核态耗时操作:每次页错误需要陷入内核,执行页表修改、物理页分配/复制等逻辑,单次处理耗时可达微秒级。

  • 零初始化场景中,131K次页错误的处理时间占据了278ms耗时的绝大部分;
  • 非零初始化场景中,workload仅需执行纯用户态的内存写操作,256MB内存的连续写入耗时仅需十数毫秒,与测试结果一致。

3. 如何测量页错误处理消耗的时间?

可以通过以下工具和方法:

  • time命令:使用/usr/bin/time -v ./your_program,输出中的System time字段对应内核态执行时间,其中大部分为页错误处理耗时;同时可直接查看Minor (reclaiming a frame) page faults的次数。
  • perf工具:
    • 统计页错误次数及耗时占比:perf stat -e minor-faults,page-faults,cpu-clock ./your_program,可直观看到页错误次数与总耗时的关联;
    • 分析内核态调用:通过perf record ./your_program捕获采样数据,再用perf report查看内核函数的耗时占比,其中与页错误相关的内核函数(如handle_mm_fault)的耗时即为页错误处理时间。
  • 内核跟踪工具:若需更精确的单次页错误耗时,可使用ftrace跟踪内核的page_fault事件,结合时间戳计算单次处理的耗时(该方法需内核支持,操作复杂度较高)。

内容的提问来源于stack exchange,提问作者tverghis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 03:02:34