内存页错误是操作系统内存管理中的常见机制,当进程访问的虚拟内存页尚未加载到物理内存时,会触发页错误异常,系统需要将对应的页从磁盘加载到物理内存中。过高的页错误频率通常意味着进程存在频繁的内存换入换出、内存访问局部性差等问题,通过C++实时监控当前进程的页错误频率,可以帮助开发者快速定位内存相关的性能瓶颈。

方案一:使用Windows API获取进程内存信息
Windows系统提供了GetProcessMemoryInfo函数,可以直接获取指定进程的内存统计信息,其中包含进程从启动到当前的页错误总数,我们可以通过两次采样计算时间差内的页错误增量,从而得到页错误频率。
实现步骤
- 获取当前进程的句柄,使用
GetCurrentProcess函数即可获取当前进程的伪句柄,无需手动关闭 - 定义
PROCESS_MEMORY_COUNTERS结构体用于存储内存统计信息 - 定时调用
GetProcessMemoryInfo获取两次页错误总数,计算单位时间内的增量 - 根据采样时间间隔计算页错误频率,单位为次/秒
完整代码示例
#include <windows.h>
#include <iostream>
#include <thread>
#include <chrono>
// 采样间隔,单位毫秒
const int SAMPLE_INTERVAL_MS = 1000;
int main() {
HANDLE hProcess = GetCurrentProcess();
PROCESS_MEMORY_COUNTERS pmcPrev, pmcCurr;
// 第一次采样
if (!GetProcessMemoryInfo(hProcess, &pmcPrev, sizeof(pmcPrev))) {
std::cerr << "获取内存信息失败" << std::endl;
return 1;
}
while (true) {
// 等待采样间隔
std::this_thread::sleep_for(std::chrono::milliseconds(SAMPLE_INTERVAL_MS));
// 第二次采样
if (!GetProcessMemoryInfo(hProcess, &pmcCurr, sizeof(pmcCurr))) {
std::cerr << "获取内存信息失败" << std::endl;
break;
}
// 计算页错误增量,PageFaultCount是进程启动到当前的页错误总数
ULONG pageFaultDelta = pmcCurr.PageFaultCount - pmcPrev.PageFaultCount;
// 计算频率,次/秒
double pageFaultRate = static_cast<double>(pageFaultDelta) / (SAMPLE_INTERVAL_MS / 1000.0);
std::cout << "当前页错误频率: " << pageFaultRate << " 次/秒" << std::endl;
// 更新上一次采样数据
pmcPrev = pmcCurr;
}
return 0;
}
方案二:使用性能计数器轮询统计
Windows性能计数器提供了更细粒度的系统性能统计能力,我们可以通过性能计数器接口获取进程的页错误相关数据,这种方式可以获取到更丰富的统计维度,适合需要对接系统性能监控体系的场景。
核心逻辑说明
我们需要查询Process性能对象下的Page Faults/sec计数器,对应目标进程的实例,通过定时查询计数器的值得到实时的页错误频率。需要注意的是性能计数器的实例名称默认是进程名加上进程ID,需要正确匹配当前进程的实例。
完整代码示例
#include <windows.h>
#include <iostream>
#include <thread>
#include <chrono>
#include <string>
// 采样间隔,单位毫秒
const int SAMPLE_INTERVAL_MS = 1000;
int main() {
// 构造当前进程的实例名,格式为 进程名_进程ID
char processName[MAX_PATH] = { 0 };
DWORD processNameSize = MAX_PATH;
GetProcessImageFileNameA(GetCurrentProcess(), processName, processNameSize);
// 提取进程名(去掉路径)
std::string instanceName = processName;
size_t pos = instanceName.find_last_of("\");
if (pos != std::string::npos) {
instanceName = instanceName.substr(pos + 1);
}
instanceName += "_" + std::to_string(GetCurrentProcessId());
// 打开性能计数器查询
HQUERY hQuery;
if (PdhOpenQueryA(NULL, 0, &hQuery) != ERROR_SUCCESS) {
std::cerr << "打开性能查询失败" << std::endl;
return 1;
}
// 添加页错误计数器
HCOUNTER hCounter;
std::string counterPath = "\Process(" + instanceName + ")\Page Faults/sec";
if (PdhAddCounterA(hQuery, counterPath.c_str(), 0, &hCounter) != ERROR_SUCCESS) {
std::cerr << "添加计数器失败,请检查实例名是否正确" << std::endl;
PdhCloseQuery(hQuery);
return 1;
}
// 初始化查询
PdhCollectQueryData(hQuery);
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(SAMPLE_INTERVAL_MS));
// 收集最新数据
if (PdhCollectQueryData(hQuery) != ERROR_SUCCESS) {
std::cerr << "收集计数器数据失败" << std::endl;
break;
}
// 获取计数器值
PDH_FMT_COUNTERVALUE counterValue;
if (PdhGetFormattedCounterValue(hCounter, PDH_FMT_DOUBLE, NULL, &counterValue) == ERROR_SUCCESS) {
std::cout << "当前页错误频率: " << counterValue.doubleValue << " 次/秒" << std::endl;
} else {
std::cerr << "获取计数器值失败" << std::endl;
}
}
// 清理资源
PdhCloseQuery(hQuery);
return 0;
}
两种方案对比
| 对比维度 | Windows API方案 | 性能计数器方案 |
|---|---|---|
| 实现复杂度 | 低,仅需少量API调用 | 较高,需要处理计数器实例匹配逻辑 |
| 数据精度 | 基于累计值计算,精度依赖采样间隔 | 系统直接提供频率值,精度更高 |
| 适用场景 | 轻量级进程内监控,快速排查问题 | 对接系统性能监控体系,需要标准化指标 |
注意事项
- 两种方案均仅适用于Windows平台,Linux平台需要使用
/proc/[pid]/stat文件或者getrusage函数实现类似功能 - 采样间隔不宜设置过短,否则会导致计算出的频率波动过大,建议设置在1秒及以上
- 如果进程刚启动,页错误总数较少,短时间内的频率计算可能存在较大误差,建议运行一段时间后再开始统计
C++_Page_Faults进程内存监控GetProcessMemoryInfo性能计数器内存页错误统计修改时间:2026-06-23 07:51:31