本文介绍C++中几个统计程序内存数据的方式:<sys/resource.h>(Unix/Linux)、GetProcessTimes(Windows)
<sys/resource.h>
介绍
<sys/resource.h> 是一个 Unix 或类 Unix 系统(如 Linux、macOS)提供的头文件,用于获取进程资源使用情况等信息。实测它无法在Windows上用,就算你用的MingW
也不行。
rusage
结构体的定义如下:
struct rusage {struct timeval ru_utime; /* user CPU time used */struct timeval ru_stime; /* system CPU time used */long ru_maxrss; /* maximum resident set size */long ru_ixrss; /* integral shared memory size */long ru_idrss; /* integral unshared data size */long ru_isrss; /* integral unshared stack size */long ru_minflt; /* page reclaims (soft page faults) */long ru_majflt; /* page faults (hard page faults) */long ru_nswap; /* swaps */long ru_inblock; /* block input operations */long ru_oublock; /* block output operations */long ru_msgsnd; /* messages sent */long ru_msgrcv; /* messages received */long ru_nsignals; /* signals received */long ru_nvcsw; /* voluntary context switches */long ru_nivcsw; /* involuntary context switches */
};
每一个成员都对应一个当前进程的内容,比如:
- CPU 时间相关
struct timeval ru_utime
- 含义:用户模式下进程使用的 CPU 时间。用户模式是指进程在执行用户程序代码时所处的模式,不涉及内核操作。
- 数据类型:
struct timeval
,该结构体包含两个成员 tv_sec(秒)和 tv_usec(微秒),用于精确表示时间。
struct timeval ru_stime
- 含义:内核模式下进程使用的 CPU 时间。当进程执行系统调用(如文件读写、网络操作等)时会进入内核模式。
- 数据类型:同样是 struct timeval。
- 内存相关
long ru_maxrss
- 含义:进程使用的最大驻留集大小(Resident Set Size,RSS),以千字节(KB)为单位。驻留集是指当前时刻进程在物理内存中实际占用的页面集合。
long ru_ixrss
- 含义:进程使用的共享内存的积分值。积分值通常是在一段时间内对某个资源使用量的累积。
long ru_idrss
- 含义:进程使用的非共享数据段的积分值。非共享数据段包含了进程独有的数据,如局部变量等。
long ru_isrss
- 含义:进程使用的非共享栈段的积分值。栈用于存储函数调用的上下文信息。
- 页面错误相关
long ru_minflt
- 含义:软页面错误(Page Reclaims)的次数。软页面错误发生时,所需的页面虽然不在物理内存中,但可以通过简单的操作(如从交换空间或文件系统缓存中获取)将其调入内存,无需进行磁盘 I/O。
long ru_majflt
- 含义:硬页面错误(Page Faults)的次数。硬页面错误发生时,所需的页面不仅不在物理内存中,还需要从磁盘读取,会导致明显的性能开销。
- 交换相关
long ru_nswap
- 含义:进程被交换出物理内存的次数。当系统内存不足时,操作系统会将一些不常用的进程或页面交换到磁盘上的交换空间,以腾出物理内存供其他进程使用。
- 输入 / 输出相关
long ru_inblock
- 含义:进程执行的块输入操作次数。块输入操作通常涉及从磁盘等存储设备读取数据块。
long ru_oublock
- 含义:进程执行的块输出操作次数。块输出操作通常涉及将数据块写入磁盘等存储设备。
- 消息传递相关
long ru_msgsnd
- 含义:进程发送的消息数量。消息传递是一种进程间通信(IPC)机制,用于在不同进程之间交换数据。
long ru_msgrcv
- 含义:进程接收的消息数量。
- 信号相关
long ru_nsignals
- 含义:进程接收到的信号数量。信号是一种异步通信机制,用于通知进程发生了某些事件,如中断、终止等。
- 上下文切换相关
long ru_nvcsw
- 含义:进程自愿进行上下文切换的次数。自愿上下文切换通常发生在进程主动放弃 CPU 控制权,如等待 I/O 操作完成、调用 sleep 函数等。
long ru_nivcsw
- 含义:进程非自愿进行上下文切换的次数。非自愿上下文切换通常是由于操作系统调度器为了公平分配 CPU 时间,强制将进程从运行状态切换到就绪状态。
使用
getrusage
函数用于获取指定进程的资源使用信息,其源代码(接口)如下:
/* Return resource usage information on process indicated by WHOand put it in *USAGE. Returns 0 for success, -1 for failure. */
extern int getrusage (__rusage_who_t __who, struct rusage *__usage) __THROW;
who
:指定要获取资源使用信息的进程类型,可以是以下值之一:RUSAGE_SELF
:获取当前进程的资源使用信息。
RUSAGE_CHILDREN
:获取当前进程的所有已终止子进程的资源使用信息。
RUSAGE_THREAD
:获取当前线程的资源使用信息(某些系统支持)。usage
:指向rusage
结构体的指针,用于存储获取到的资源使用信息。
返回值为0时调用成功,-1时失败。可以写个简单的代码测试调用:
#include <iostream>
#include <sys/resource.h>
#include <sys/time.h>// 打印 rusage 结构体中的信息
void print_rusage(const struct rusage& usage) {// 用户模式下使用的 CPU 时间std::cout << "用户模式下使用的 CPU 时间:"<< usage.ru_utime.tv_sec << " 秒 "<< usage.ru_utime.tv_usec << " 微秒" << std::endl;// 内核模式下使用的 CPU 时间std::cout << "内核模式下使用的 CPU 时间:"<< usage.ru_stime.tv_sec << " 秒 "<< usage.ru_stime.tv_usec << " 微秒" << std::endl;// 进程使用的最大驻留集大小(以 KB 为单位)std::cout << "进程使用的最大驻留集大小:" << usage.ru_maxrss << " KB" << std::endl;// 进程使用的共享内存的积分值std::cout << "进程使用的共享内存的积分值:" << usage.ru_ixrss << std::endl;// 进程使用的非共享数据段的积分值std::cout << "进程使用的非共享数据段的积分值:" << usage.ru_idrss << std::endl;// 进程使用的非共享栈段的积分值std::cout << "进程使用的非共享栈段的积分值:" << usage.ru_isrss << std::endl;// 软页面错误(Page Reclaims)的次数std::cout << "软页面错误的次数:" << usage.ru_minflt << std::endl;// 硬页面错误(Page Faults)的次数std::cout << "硬页面错误的次数:" << usage.ru_majflt << std::endl;// 进程被交换出物理内存的次数std::cout << "进程被交换出物理内存的次数:" << usage.ru_nswap << std::endl;// 进程执行的块输入操作次数std::cout << "进程执行的块输入操作次数:" << usage.ru_inblock << std::endl;// 进程执行的块输出操作次数std::cout << "进程执行的块输出操作次数:" << usage.ru_oublock << std::endl;// 进程发送的消息数量std::cout << "进程发送的消息数量:" << usage.ru_msgsnd << std::endl;// 进程接收的消息数量std::cout << "进程接收的消息数量:" << usage.ru_msgrcv << std::endl;// 进程接收到的信号数量std::cout << "进程接收到的信号数量:" << usage.ru_nsignals << std::endl;// 进程自愿进行上下文切换的次数std::cout << "进程自愿进行上下文切换的次数:" << usage.ru_nvcsw << std::endl;// 进程非自愿进行上下文切换的次数std::cout << "进程非自愿进行上下文切换的次数:" << usage.ru_nivcsw << std::endl;
}int main() {struct rusage usage;// 获取当前进程的资源使用信息if (getrusage(RUSAGE_SELF, &usage) == 0) {std::cout << "当前进程的资源使用信息:" << std::endl;print_rusage(usage);} else {std::cerr << "获取资源使用信息失败。" << std::endl;return 1;}return 0;
}
一次运行的结果:
<windows.h>
Windows上并没有类似rusage
这种工具,但是也提供了一些工具来辅助分析程序。比如可以使用GetProcessTimes
函数来获取进程的用户模式和内核模式 CPU 时间:
WINBASEAPI
BOOL
WINAPI
GetProcessTimes(_In_ HANDLE hProcess,_Out_ LPFILETIME lpCreationTime,_Out_ LPFILETIME lpExitTime,_Out_ LPFILETIME lpKernelTime,_Out_ LPFILETIME lpUserTime);WINBASEAPI
HANDLE
WINAPI
GetCurrentProcess(VOID);
GetProcessTimes
函数用于获取指定进程的创建时间、退出时间、内核模式 CPU 时间和用户模式 CPU 时间。数据来源是进程的handler指针,可以通过GetCurrentProcess
获得。
FILETIME
结构体用于表示一个特定的时间,使用两个 32 位的 DWORD 值(dwLowDateTime
和 dwHighDateTime
)来表示一个 64 位的时间戳。为了方便处理这个 64 位时间戳,通常会将 FILETIME 结构体的值转换为 ULARGE_INTEGER
类型,其定义如下:
typedef struct _FILETIME {DWORD dwLowDateTime;DWORD dwHighDateTime;
} FILETIME, *PFILETIME, *LPFILETIME;
ULARGE_INTEGER
是 Windows API 中定义的一个结构体,用于表示一个 64 位无符号整数。其定义如下:
typedef union _ULARGE_INTEGER {struct {DWORD LowPart;DWORD HighPart;} DUMMYSTRUCTNAME;struct {DWORD LowPart;DWORD HighPart;} u;ULONGLONG QuadPart;
} ULARGE_INTEGER;
从定义可以看出,ULARGE_INTEGER
是一个联合体(union
),这意味着 LowPart
、HighPart
和 QuadPart
共享同一块内存空间。具体关系如下:
LowPart
:表示 64 位整数的低 32 位。DWORD
类型通常是 32 位无符号整数,用于存储 64 位值的较低部分。HighPart
:表示 64 位整数的高 32 位。同样是DWORD
类型,用于存储 64 位值的较高部分。QuadPart
:表示整个 64 位无符号整数。ULONGLONG
类型是 64 位无符号整数类型,可以直接用来操作整个 64 位的值。
在早期的 Windows 系统中,部分硬件平台可能不直接支持 64 位整数运算。通过将 64 位整数拆分为高 32 位(HighPart)和低 32 位(LowPart),可以在不支持 64 位运算的环境下,使用 32 位运算来模拟 64 位运算。这样可以确保代码在不同的硬件平台和操作系统版本上都能正常工作。
最后我们可以写出如下的代码:
#include <iostream>
#include <windows.h>void printProcessCPUTime() {HANDLE hProcess = GetCurrentProcess();FILETIME creationTime, exitTime, kernelTime, userTime;if (GetProcessTimes(hProcess, &creationTime, &exitTime, &kernelTime, &userTime)) {ULARGE_INTEGER kernelTimeValue, userTimeValue;kernelTimeValue.LowPart = kernelTime.dwLowDateTime;kernelTimeValue.HighPart = kernelTime.dwHighDateTime;userTimeValue.LowPart = userTime.dwLowDateTime;userTimeValue.HighPart = userTime.dwHighDateTime;std::cout << "Kernel mode CPU time: " << kernelTimeValue.QuadPart / 10000000.0 << " seconds" << std::endl;std::cout << "User mode CPU time: " << userTimeValue.QuadPart / 10000000.0 << " seconds" << std::endl;} else {std::cerr << "Failed to get process CPU time." << std::endl;}
}int main() {printProcessCPUTime();return 0;
}
一个运行的结果如下: