【C++】Rusage(一)

本文介绍C++中几个统计程序内存数据的方式:<sys/resource.h>(Unix/Linux)、GetProcessTimes(Windows)

<sys/resource.h>

介绍

<sys/resource.h> 是一个 Unix 或类 Unix 系统(如 Linux、macOS)提供的头文件,用于获取进程资源使用情况等信息。实测它无法在Windows上用,就算你用的MingW也不行。

rusage 结构体的定义如下:

cpp 复制代码
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 */
};

每一个成员都对应一个当前进程的内容,比如:

  1. CPU 时间相关
cpp 复制代码
struct timeval ru_utime
  • 含义:用户模式下进程使用的 CPU 时间。用户模式是指进程在执行用户程序代码时所处的模式,不涉及内核操作。
  • 数据类型:struct timeval,该结构体包含两个成员 tv_sec(秒)和 tv_usec(微秒),用于精确表示时间。
cpp 复制代码
struct timeval ru_stime
  • 含义:内核模式下进程使用的 CPU 时间。当进程执行系统调用(如文件读写、网络操作等)时会进入内核模式。
  • 数据类型:同样是 struct timeval。
  1. 内存相关
cpp 复制代码
long ru_maxrss
  • 含义:进程使用的最大驻留集大小(Resident Set Size,RSS),以千字节(KB)为单位。驻留集是指当前时刻进程在物理内存中实际占用的页面集合。
cpp 复制代码
long ru_ixrss
  • 含义:进程使用的共享内存的积分值。积分值通常是在一段时间内对某个资源使用量的累积。
cpp 复制代码
long ru_idrss
  • 含义:进程使用的非共享数据段的积分值。非共享数据段包含了进程独有的数据,如局部变量等。
cpp 复制代码
long ru_isrss
  • 含义:进程使用的非共享栈段的积分值。栈用于存储函数调用的上下文信息。
  1. 页面错误相关
cpp 复制代码
long ru_minflt
  • 含义:软页面错误(Page Reclaims)的次数。软页面错误发生时,所需的页面虽然不在物理内存中,但可以通过简单的操作(如从交换空间或文件系统缓存中获取)将其调入内存,无需进行磁盘 I/O。
cpp 复制代码
long ru_majflt
  • 含义:硬页面错误(Page Faults)的次数。硬页面错误发生时,所需的页面不仅不在物理内存中,还需要从磁盘读取,会导致明显的性能开销。
  1. 交换相关
cpp 复制代码
long ru_nswap
  • 含义:进程被交换出物理内存的次数。当系统内存不足时,操作系统会将一些不常用的进程或页面交换到磁盘上的交换空间,以腾出物理内存供其他进程使用。
  1. 输入 / 输出相关
cpp 复制代码
long ru_inblock
  • 含义:进程执行的块输入操作次数。块输入操作通常涉及从磁盘等存储设备读取数据块。
cpp 复制代码
long ru_oublock
  • 含义:进程执行的块输出操作次数。块输出操作通常涉及将数据块写入磁盘等存储设备。
  1. 消息传递相关
cpp 复制代码
long ru_msgsnd
  • 含义:进程发送的消息数量。消息传递是一种进程间通信(IPC)机制,用于在不同进程之间交换数据。
cpp 复制代码
long ru_msgrcv
  • 含义:进程接收的消息数量。
  1. 信号相关
cpp 复制代码
long ru_nsignals
  • 含义:进程接收到的信号数量。信号是一种异步通信机制,用于通知进程发生了某些事件,如中断、终止等。
  1. 上下文切换相关
cpp 复制代码
long ru_nvcsw
  • 含义:进程自愿进行上下文切换的次数。自愿上下文切换通常发生在进程主动放弃 CPU 控制权,如等待 I/O 操作完成、调用 sleep 函数等。
cpp 复制代码
long ru_nivcsw
  • 含义:进程非自愿进行上下文切换的次数。非自愿上下文切换通常是由于操作系统调度器为了公平分配 CPU 时间,强制将进程从运行状态切换到就绪状态。

使用

getrusage 函数用于获取指定进程的资源使用信息,其源代码(接口)如下:

cpp 复制代码
/* Return resource usage information on process indicated by WHO
   and 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时失败。可以写个简单的代码测试调用:

cpp 复制代码
#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 时间:

cpp 复制代码
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 值(dwLowDateTimedwHighDateTime)来表示一个 64 位的时间戳。为了方便处理这个 64 位时间戳,通常会将 FILETIME 结构体的值转换为 ULARGE_INTEGER 类型,其定义如下:

cpp 复制代码
typedef struct _FILETIME {
    DWORD dwLowDateTime;
    DWORD dwHighDateTime;
} FILETIME, *PFILETIME, *LPFILETIME;

ULARGE_INTEGER 是 Windows API 中定义的一个结构体,用于表示一个 64 位无符号整数。其定义如下:

cpp 复制代码
typedef union _ULARGE_INTEGER {
    struct {
        DWORD LowPart;
        DWORD HighPart;
    } DUMMYSTRUCTNAME;
    struct {
        DWORD LowPart;
        DWORD HighPart;
    } u;
    ULONGLONG QuadPart;
} ULARGE_INTEGER;

从定义可以看出,ULARGE_INTEGER 是一个联合体(union),这意味着 LowPartHighPartQuadPart 共享同一块内存空间。具体关系如下:

  • 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 位运算。这样可以确保代码在不同的硬件平台和操作系统版本上都能正常工作。

最后我们可以写出如下的代码:

cpp 复制代码
#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;
}

一个运行的结果如下:

相关推荐
阳洞洞1 小时前
c++中的静态多态和动态多态简介
开发语言·c++
vvilkim3 小时前
JavaScript 数据类型和数据结构:从基础到实践
开发语言·前端·javascript
ChoSeitaku5 小时前
NO.22十六届蓝桥杯备战|一维数组|七道练习|冒泡排序(C++)
c++·职场和发展·蓝桥杯
Reese_Cool6 小时前
【洛谷贪心算法题】P2240部分背包问题
c++·算法·贪心算法·蓝桥杯
Yuanymoon6 小时前
【由技及道】模块化战争与和平-论项目结构的哲学思辨【人工智智障AI2077的开发日志】
java·开发语言·spring boot·spring·容器
Luis Li 的猫猫6 小时前
MATLAB分析与仿真白噪声
开发语言·图像处理·人工智能·经验分享·机器学习·matlab
m0_687399846 小时前
C++ Qt login an https server, no use connect
c++·qt·https
王RuaRua6 小时前
1.C语言初识
c语言·开发语言·算法
DogDaoDao6 小时前
指针解剖学:穿透C/C++内存操作的核心密码与避坑指南
c语言·c++·指针
JANGHIGH7 小时前
c++ std::forward_list使用笔记
c++·笔记·list