Process Explorer、windows 任务管理器以及系统 API 之间的关系

结论

先上结论,这里的系统 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;
}

输出如下:与任务管理器显示的数据相同。

相关推荐
天若有情67314 小时前
【c++】手撸C++ Promise:从零实现通用异步回调组件,支持链式调用+异常安全
开发语言·前端·javascript·c++·promise
抱琴_14 小时前
【Vue3】大屏性能优化黑科技:Vue 3 中实现请求合并,让你的大屏飞起来!
前端·vue.js
不会玩电脑的Xin.14 小时前
HTML + CSS
前端·css·html
hadage23315 小时前
--- JavaScript 的一些常用语法总结 ---
java·前端·javascript
彭于晏爱编程15 小时前
🍭🍭🍭升级 AntD 6:做第一个吃螃蟹的人
前端
掘金一周15 小时前
大部分人都错了!这才是chrome插件多脚本通信的正确姿势 | 掘金一周 11.27
前端·人工智能·后端
_瑶瑶_15 小时前
浅记一下ElementPlus中的虚拟化表格(el-table-v2)的简单使用
前端·javascript
Drift_Dream15 小时前
ResizeObserver:轻松监听元素尺寸变化
前端
拉不动的猪15 小时前
Axios 请求取消机制详解
前端·javascript·面试