结论
先上结论,这里的系统 API 指的是 GetProcessMemoryInfo。整篇文章围绕下面的内容展开,看懂可以跳过文章具体内容的阅读。

PE 中 Private Bytes 和 Working Set
Process Explorer 简称 PE,是微软官方提供的一个资源管理可视化工具,相比于 windows 自身任务管理器来说更专业。进入 PE 界面后,可以观察到这两个指标。

- Private Bytes
在 PE 中 Private Bytes 表示进程专门分配的内存量,这个些内存是进程独占的,不与其他进程共享。它包括了进程请求的所有内存,无论这些内存是否实际被使用。
- Working Set
在 PE 中 Working Set 表示进程当前在物理内存中使用的内存页集合,包含私有和共享的内存页。
下面从虚拟内存和物理内存的角度分析下 Private Bytes 和 Working Set。可以看到 Private Bytes 属于虚拟内存的范畴,Working Set 属于物理内存的范畴。

在 Windows 的任务管理器中也有一个内存的指标,对于每一个进程来说,该内存指的是 Working Set 中的私有部分,也就是上图中的 Private Working Set。

GetProcessMemoryInfo
通过下 GetProcessMemoryInfo 来获取 PageFileUsage(驻留在 PageFile 中的私有页)和 Working Set。
- 测试1
代码如下,每个 5s 申请 1M 的内存,然后观察下 PagefileUsage 和 WorkingSetSize。
cpp
#include <iostream>
#include <thread>
#include <Windows.h>
#include <Psapi.h>
#include <iomanip>
void printCurrentProcessMemInfo()
{
double mb = 1024 * 1024;
HANDLE currentProcess = GetCurrentProcess();
PROCESS_MEMORY_COUNTERS pps;
BOOL ret = GetProcessMemoryInfo(currentProcess, &pps, sizeof(pps));
std::cout << "####################################" << std::endl;
std::cout << "WorkingSetSize: " << pps.WorkingSetSize / mb << std::endl;
std::cout << "PagefileUsage: " << pps.PagefileUsage / mb << std::endl;
std::cout << "####################################" << std::endl;
}
int main()
{
while (true)
{
char* p = new char[1024 * 1024];
std::cout << "alloc 1024 * 1024" << std::endl;
printCurrentProcessInfo();
std::this_thread::sleep_for(std::chrono::seconds(5));
}
return 0;
}
Release 模式下输出如下,执行,观察 WorkingSetSize 和 PagefileUsage 的变化,下面是测试的输出, WorkingSetSize 基本变化不大, PagefileUsage 在按照我们预期的方式 1M 左右大小在增长。
plain
####################################
WorkingSetSize: 3304
PagefileUsage: 1636
####################################
####################################
WorkingSetSize: 3400
PagefileUsage: 2668
####################################
####################################
WorkingSetSize: 3404
PagefileUsage: 3696
####################################
- 测试2
下面对申请的内存进行 memset。
cpp
int main()
{
while (true)
{
char* p = new char[1024 * 1024];
std::cout << "alloc 1024 * 1024" << std::endl;
printCurrentProcessInfo();
std::this_thread::sleep_for(std::chrono::seconds(5));
memset(p, '2', 1024 * 1024); //初始化内存
printCurrentProcessInfo();
}
return 0;
}
可以看到在未使用 memset 之前,WorkingSetSize 的大小是没有变化的,初始化内存后,WorkingSetSize 按照预期增加了 1M 左右的大小。
cpp
####################################
WorkingSetSize: 3292 //初始化内存之前
PagefileUsage: 1632
####################################
####################################
WorkingSetSize: 4408 //初始化内存之后
PagefileUsage: 1632
####################################
####################################
WorkingSetSize: 4420 //初始化内存之前
PagefileUsage: 2676
####################################
####################################
WorkingSetSize: 5444 //初始化内存之后
PagefileUsage: 2676
####################################
在观察下任务管理器中的内存信息,同一时间 WorkingSetSize 的输出(8528k)与任务管理器上面的对比(5.4M)中间差了 3M。

- 测试3
我们知道任务管理器中获取的内存信息是私有内部的内存。 QueryWorkingSet 函数用于查询某个进程使用的 WorkingSet ,与 GetProcessMemoryInfo 中返回的 WorkingSetSize 基本相同。函数入参如下,关注下输出参数。
cpp
BOOL QueryWorkingSet(
[in] HANDLE hProcess,
[out] PVOID pv,
[in] DWORD cb
);
输出参数的结构如下,有两个属性:1)NumberOfEntries 使用的页数,2)WorkingSetInfo 每个页的属性
cpp
typedef struct _PSAPI_WORKING_SET_INFORMATION {
ULONG_PTR NumberOfEntries; //使用的页数
PSAPI_WORKING_SET_BLOCK WorkingSetInfo[1];
} PSAPI_WORKING_SET_INFORMATION, *PPSAPI_WORKING_SET_INFORMATION;
通过 WorkingSetInfo 中的 Shared 属性可以区分出当前页是否共享。

cpp
void printPrivateWorkSet()
{
DWORD workingSetSize = 0;
// 首先尝试一个预估的小尺寸
DWORD infoSize = sizeof(PSAPI_WORKING_SET_INFORMATION);
PSAPI_WORKING_SET_INFORMATION* pWorkingSetInfo = (PSAPI_WORKING_SET_INFORMATION*)malloc(infoSize);
SYSTEM_INFO info;
//获取每个页的大小
GetSystemInfo(&info);
if (pWorkingSetInfo) {
while (true) {
if (QueryWorkingSet(GetCurrentProcess(), pWorkingSetInfo, infoSize)) {
// 遍历返回的页面,计算工作集大小
int counts = 0;
for (int i = 0; i < pWorkingSetInfo->NumberOfEntries; i++) {
PSAPI_WORKING_SET_BLOCK block = pWorkingSetInfo->WorkingSetInfo[i];
//判断是否共享
if (!block.Shared) {
counts++;
}
}
workingSetSize = counts * info.dwPageSize;
break;
}
else {
if (GetLastError() == ERROR_BAD_LENGTH) {
// 缓冲区太小,扩展缓冲区并重试
infoSize = sizeof(PSAPI_WORKING_SET_INFORMATION) +
(pWorkingSetInfo->NumberOfEntries * sizeof(PSAPI_WORKING_SET_BLOCK));
pWorkingSetInfo = (PSAPI_WORKING_SET_INFORMATION*)realloc(pWorkingSetInfo, infoSize);
}
else {
// QueryWorkingSet 调用失败,处理错误信息
std::cerr << "QueryWorkingSet failed with error " << GetLastError() << std::endl;
break;
}
}
}
free(pWorkingSetInfo);
}
// 打印工作集大小
double mb = 1024 * 1024;
//与任务管理器的精度相同
std::cout << std::fixed << std::setprecision(2) << "Estimated working set size: " << workingSetSize / mb << std::endl;
}
输出如下:与任务管理器显示的数据相同。
