【问题标题】:How to query amount of allocated memory on Linux (and OSX)?如何在 Linux(和 OSX)上查询分配的内存量?
【发布时间】:2016-11-24 05:24:00
【问题描述】:

虽然这可能看起来与其他问题重复,但让我解释一下为什么不是。

我希望在达到某个内存限制时让我的应用程序的特定部分正常降级。我可以使用基于剩余可用物理内存的标准,但这并不安全,因为操作系统可能会在达到标准之前开始分页我的应用程序使用的内存,这会认为还有一些物理内存,并保留分配等。出于同样的原因,我不能使用进程当前使用的物理内存量,因为一旦操作系统开始将我换出,我就会继续分配操作系统页面内存,所以数字会不再增长。

出于这个原因,我根据我的应用程序分配的内存量选择了一个标准,即非常接近虚拟内存大小。

这个问题 (How to determine CPU and memory consumption from inside a process?) 提供了查询当前进程使用的虚拟内存量的好方法,我认为这正是我所需要的。

在 Windows 上,我使用 GetProcessMemoryInfo()PrivateUsage 字段,效果很好。

在 Linux 上,我尝试了几件事(如下所列),但都不起作用。虚拟内存使用对我不起作用的原因是因为在 Linux 上的 NVidia 硬件上创建 OpenCL 上下文时会发生一些事情。驱动程序保留了一个足够大的虚拟内存空间区域以容纳所有 RAM、所有交换和所有视频内存。我的猜测是它对统一地址空间和一切都是如此。但这也意味着该进程报告使用了大量内存。例如,在我的系统上,top 将在 VIRT 列中报告 23.3 Gb(12 Gb 的 RAM、6 Gb 的交换空间、2 Gb 的视频内存,这为 NVidia 驱动程序保留了 20 Gb)。

在 OSX 上,通过使用 task_info()virtual_size 字段,我也得到了比预期更大的数字(对于在 Windows 上甚至不接近 1 Gb 的应用程序来说,只有几 Gb),但没有那么大Linux。

所以这是一个大问题:我如何才能获得应用程序分配的内存量?我知道这是一个有点模糊的问题(“分配的内存”是什么意思?),但我很灵活:

  • 我更愿意包含应用程序静态数据、代码部分和所有内容,但我可以没有。
  • 我更愿意包含为堆栈分配的内存,但我可以不用。
  • 我更愿意包含共享库使用的内存,但我可以不用。
  • 我真的不关心 mmap 的东西,我可以在那时有或没有。

真正重要的是这个数字随着动态分配(new、malloc、任何东西)而增长,而在释放内存时缩小(我知道这可能取决于实现)。

我尝试过的事情

这是我尝试和/或想到的几个解决方案,但对我不起作用。

  1. 从 /proc/self/status 读取

    这是 how-to-determine-cpu-and-memory-consumption-from-inside-a-process 建议的方法。但是,如上所述,这会返回虚拟内存量,这对我不起作用。

  2. 从 /proc/self/statm 读取

    非常轻微最差:根据http://kernelnewbies.kernelnewbies.narkive.com/iG9xCmwB/proc-pid-statm-doesnt-match-with-status,它指的是Linux内核代码,这两个值之间的唯一区别是第二个值没有减去reserved_vm到虚拟量记忆。我希望reserved_vm 包含 OpenCL 驱动程序保留的内存,但它没有。

  3. 使用mallinfo()uordblks 字段

    这似乎不包括所有分配(我猜news 丢失了),因为虚拟内存空间增长了+2Gb(在做了一些内存繁重的工作并仍然持有内存之后) ,我只看到mallinfo() 返回的数字增长了大约 0.1Gb。

  4. 从 /proc/self/smaps 读取 [heap] 部分大小

    这个值从大约 336,760 Kb 开始,峰值为 1,019,496 Kb,用于将虚拟内存空间增加 +2Gb 的工作,然后它永远不会下降,所以我不确定我是否真的不能依赖这个数字。 .

  5. 监控我的应用程序中的所有内存分配

    是的,在理想的世界中,我可以控制所有分配内存的人。然而,这是一个遗留应用程序,使用大量不同的分配器,一些mallocs,一些news,一些特定于操作系统的例程等。有一些插件可以做任何他们想做的事情,他们可能是使用不同的编译器等编译。因此,虽然这对于真正控制内存非常有用,但这在我的上下文中不起作用。

  6. 读取OpenCL上下文初始化前后的虚拟内存大小

    虽然这可能是解决问题的一种“hacky”方式(我可能不得不回退到它),但我真的希望有一种更可靠的方式来查询内存,因为 OpenCL 上下文可以在我的某个地方初始化控制,以及其他类似但非 OpenCL 特定的问题可能会蔓延,我不会知道。

所以这就是我所拥有的。还有一件事我还没有尝试过,因为它只适用于OSX,但它是使用Why does mstats and malloc_zone_statistics not show recovered memory after free?中描述的方法,即使用malloc_get_all_zones()malloc_zone_statistics(),但我认为这可能是同一个问题如mallinfo(),即不考虑所有分配。

那么,任何人都可以建议一种方法来查询 Linux(以及 OSX,即使它是不同的方法)中给定进程的内存使用情况(这个术语很模糊,请参阅上面的精确度)?

【问题讨论】:

标签: c++ c linux macos memory


【解决方案1】:

这是我最终使用的。我扫描 /proc/self/maps 并将所有符合我条件的地址范围的大小相加,即:

  • 仅包括从 inode 0 开始的范围(即没有设备、没有映射文件等)
  • 仅包含至少为可读、可写或可执行之一的范围
  • 仅包含私有内存
    • 在我的实验中,我没有看到来自 inode 0 的共享内存实例。可能是进程间共享内存...?

这是我的解决方案的代码:

size_t getValue()
{
    FILE* file = fopen("/proc/self/maps", "r");
    if (!file)
    {
        assert(0);
        return 0;
    }

    size_t value = 0;

    char line[1024];
    while (fgets(line, 1024, file) != NULL)
    {
        ptrdiff_t start_address, end_address;
        char perms[4];
        ptrdiff_t offset;
        int dev_major, dev_minor;
        unsigned long int inode;
        const int nb_scanned = sscanf(
            line, "%16tx-%16tx %c%c%c%c %16tx %02x:%02x %lu",
            &start_address, &end_address,
            &perms[0], &perms[1], &perms[2], &perms[3],
            &offset, &dev_major, &dev_minor, &inode
            );
        if (10 != nb_scanned)
        {
            assert(0);
            continue;
        }

        if ((inode == 0) &&
            (perms[0] != '-' || perms[1] != '-' || perms[2] != '-') &&
            (perms[3] == 'p'))
        {
            assert(dev_major == 0);
            assert(dev_minor == 0);
            value += (end_address - start_address);
        }
    }

    fclose(file);

    return value;
}

由于这是循环遍历 /proc/self/maps 中的所有行,因此以这种方式查询内存比使用 How to determine CPU and memory consumption from inside a process? 中的“当前进程当前使用的虚拟内存”要慢得多。

但是,它提供的答案更接近我的需要。

【讨论】:

  • 不适用于 Mac OS X,fopen("/proc/self/maps" 返回 NULL。
  • 此解决方案适用于 Linux,它需要 proc 文件系统。
  • 你找到OSX的解决方案了吗(问题标题也是关于OSX的)?
【解决方案2】:

您是否尝试过上面第 (5) 节的 Linux 共享库插入器?只要您的应用程序不是静态链接 malloc 函数,您就可以在程序和内核 malloc 之间插入一个新函数。我已经多次使用这种策略来收集有关内存使用情况的统计信息。

它确实需要在运行程序之前设置 LD_PRELOAD,但不需要更改源代码或二进制文件。在许多情况下,这是一个理想的答案。

这是一个 malloc 插入器的示例:

http://www.drdobbs.com/building-library-interposers-for-fun-and/184404926

您可能还想做 calloc 和 free。对 new 的调用通常以对 malloc 的调用结束,因此也涵盖了 C++。

OS X 似乎有类似的功能,但我没有尝试过。

http://tlrobinson.net/blog/2007/12/overriding-library-functions-in-mac-os-x-the-easy-way-dyld_insert_libraries/

--马特

【讨论】:

    【解决方案3】:

    您可以尝试使用getrusage()返回的信息:

    #include <sys/time.h>
    #include <sys/resource.h>
    
    int getrusage(int who, struct rusage *usage);
    
    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;        /* IPC messages sent */
        long   ru_msgrcv;        /* IPC messages received */
        long   ru_nsignals;      /* signals received */
        long   ru_nvcsw;         /* voluntary context switches */
        long   ru_nivcsw;        /* involuntary context switches */
    };
    

    如果内存信息不符合您的目的,观察页面错误计数可以帮助监控内存压力,这是您想要检测的。

    【讨论】:

    • 感谢您的建议。我试过了(阅读ru_ixrssru_idrssru_isrss)并且所有字段都为0。根据手册页,“并非所有字段都已完成;未维护的字段被内核设置为零。”和“在 Linux 2.4 中,仅维护 ru_utime、ru_stime、ru_minflt 和 ru_majflt 字段。从 Linux 2.6 开始,还维护 ru_nvcsw 和 ru_nivcsw。”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-11
    • 2019-02-19
    • 2015-09-06
    • 1970-01-01
    • 1970-01-01
    • 2010-11-18
    • 1970-01-01
    相关资源
    最近更新 更多