必须读取/proc/[pid]/stat第12列(minflt)和第13列(majflt)获取进程自启动以来的软硬页错误累计值,再通过两次采样差分并除以实际时间间隔计算每秒频率,不可直接使用单次读取值或依赖vmstat、perf聚合统计。

页错误频率高不等于内存不足,但能暴露线程间缓存争用或内存布局问题——直接看 /proc/[pid]/stat 的第12列(minflt)和第13列(majflt)最准,别依赖 vmstat 或 perf 的聚合统计。
怎么看单个C++进程的页错误计数
Linux内核对每个进程维护精确的页错误计数,藏在 /proc/[pid]/stat 里,不是估算值。其中:
-
minflt(第12列):次要页错误数,即仅需分配物理页、无需磁盘I/O(如首次访问新分配的堆内存) -
majflt(第13列):主要页错误数,需从磁盘加载数据(如mmap映射的文件页首次访问、swap-in)
实操建议:
- 先用
pidof your_cpp_service或pgrep -f "your_binary"拿到 PID - 执行
awk '{print "minor:", $12, "major:", $13}' /proc/<code>PID/stat —— 注意PID要替换成真实数字 - 想持续观察:用
watch -n 1 'awk "{print \"minor:\", \$12, \"major:\", \$13}" /proc/<code>PID/stat'
为什么多线程程序的 minflt 会异常高
这不是内存不够,而是线程私有数据(如栈、TLS变量)或频繁分配小对象触发的缓存局部性崩塌。典型场景:
Linux 性能分析与调优专家,覆盖 CPU、内存、磁盘 I/O、网络、内核参数、编译优化、容器/K8s。适用场景:系统卡顿/高负载、内存不足/OOM/Swap 高、CPU 异常/iowait 高。
- 每个线程都调用
new分配几十字节的小块,glibc的malloc在多线程下默认为每线程维护独立arena,导致大量新页映射 - 使用
std::thread_local变量,且初始化开销大(比如构造函数中分配内存),每次新线程启动都触发一批minflt - 线程池频繁创建/销毁线程,而非复用 ——
clone()时内核需为新栈分配页,哪怕只用几KB
验证方式:对比 minflt 增速和线程数变化节奏。如果每启一个线程就跳涨几百 minflt,基本锁定是线程生命周期管理问题。
别被 perf stat -e page-faults 带偏
perf 统计的是整个进程所有线程的总和,且默认采样模式会漏掉短命线程的页错误;更糟的是,它把 minflt 和 majflt 合并成一个事件,无法区分。
- 正确做法:用
perf record -e minor-faults,major-faults -p <code>PID抓取,再perf script看分布,但依然不如直接读/proc/[pid]/stat精确 -
vmstat 1显示的是系统级平均值,对单进程诊断毫无意义——你看到的可能是其他进程刷脏页造成的波动 - 如果程序用了
mmap(MAP_POPULATE)或mlock(),minflt会趋近于0,此时看/proc/[pid]/maps里各段的MMU标志比看计数更有价值
真正要调优时,重点不在压低页错误总数,而在确认这些页错误是否集中在不该出现的地方:比如一个只读配置解析线程,minflt 却随请求量线性上涨,那大概率是误用了线程局部存储或重复构造临时对象。这种细节,/proc/[pid]/stat 是唯一能给你干净数字的入口。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










