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;
}

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

相关推荐
程序员爱钓鱼2 小时前
Node.js 编程实战:文件读写操作
前端·后端·node.js
PineappleCoder2 小时前
工程化必备!SVG 雪碧图的最佳实践:ID 引用 + 缓存友好,无需手动算坐标
前端·性能优化
JIngJaneIL3 小时前
基于springboot + vue古城景区管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
敲敲了个代码3 小时前
隐式类型转换:哈基米 == 猫 ? true :false
开发语言·前端·javascript·学习·面试·web
澄江静如练_3 小时前
列表渲染(v-for)
前端·javascript·vue.js
JustHappy4 小时前
「chrome extensions🛠️」我写了一个超级简单的浏览器插件Vue开发模板
前端·javascript·github
Loo国昌4 小时前
Vue 3 前端工程化:架构、核心原理与生产实践
前端·vue.js·架构
sg_knight4 小时前
拥抱未来:ECMAScript Modules (ESM) 深度解析
开发语言·前端·javascript·vue·ecmascript·web·esm
LYFlied4 小时前
【每日算法】LeetCode 17. 电话号码的字母组合
前端·算法·leetcode·面试·职场和发展