【问题标题】:Indirect memory consumption of VirtualAlloc() used with the flag MEM_RESERVE与标志 MEM_RESERVE 一起使用的 VirtualAlloc() 的间接内存消耗
【发布时间】:2021-03-16 05:38:24
【问题描述】:

Microsoft documentation 描述VirtualAlloc() 与标志MEM_RESERVE 一起使用时的行为:

保留进程的虚拟地址空间范围,而不在内存或磁盘上的页面文件中分配任何实际物理存储。

VirtualAlloc()MEM_RESERVE 一起使用是否会产生间接增加进程整体内存消耗的副作用?

即使保留的虚拟页面与物理页面没有关联,关于它们的一些信息仍然可以单独存储在数据结构中,并且每个保留的虚拟页面可能会消耗几个字节的物理内存。如果有人想保留大量虚拟内存(例如 13 TiB),那将是一个问题。

您可以使用此测试程序测试VirtualAlloc()MEM_RESERVE 一起使用是否间接消耗内存:

#include <windows.h>
#include <stdio.h>
#include <stddef.h>

int main(void) {
    void *address = VirtualAlloc(NULL, 13ull << 40ull, MEM_RESERVE, PAGE_NOACCESS);
    if (address == NULL)               
        printf("Reservation failure.\n");
    getchar();
    return 0;
}

如果您运行此程序,请说明您使用的是哪个编译器和 Windows 版本。

【问题讨论】:

  • 不能肯定地说,但我猜 WINE 是在玩任何操作系统调用的把戏。如果您在本机 Windows 平台上运行“测试”会发生什么?
  • 我没有本地 Windows 平台来运行此测试。我只有 GNU/Linux。
  • 好的 - 我正忙着准备去上班。当我有时间时,我会尝试运行你的代码,并让你知道会发生什么(如果可以的话)。
  • 你太好了。谢谢。
  • 我认为 WINE 不会成为 Windows 本身测试的令人满意的替代品。您可以从 Microsoft 获得免费的 Windows VM,用于测试/开发目的,请参阅 developer.microsoft.com/en-us/microsoft-edge/tools/vms

标签: c windows memory memory-management virtual-memory


【解决方案1】:

您知道这是否也发生在本机 Windows 环境中?

在本机 Windows 8 上(由 Mingw64 编译,从任务管理器获取统计信息);您的代码消耗 25.6 MB 的物理内存。通过将其更改为“size_t bytes = 1;”,物理内存消耗变为 0.2 MB;这意味着VirtualAlloc() 本身负责大约 25.4 MB 的物理内存。

请注意,需要一些物理内存来跟踪保留的区域;并且因为您可以独立地释放/分配单个页面,这可能不仅仅是“整个区域的开始和结束”(并且可能是该区域中每 1 GiB 或 2 MiB 或 4 KiB 块的单独标记,以与 CPU 用于“虚拟地址到物理地址”转换的表结构一致)。

编辑

基于 cmets(对不同大小的虚拟内存进行重复测试),我将这个烂摊子拼凑在一起(一个循环,将分配的大小加倍并报告每个有效大小的结果):

#define __USE_MINGW_ANSI_STDIO 1

#include <windows.h>    /* VirtualAlloc() */
#include <psapi.h>
#include <stdio.h>      /* getchar() */
#include <stddef.h>     /* size_t */

int main(void) {
    HANDLE hProcess;
    PROCESS_MEMORY_COUNTERS pmc;
    void *address;
    size_t size = 1;

    hProcess = GetCurrentProcess();
    if (NULL == hProcess) {
        printf( "OpenProcess() failed\n");
        getchar();
        return 1;
    }

    while( (address = VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_NOACCESS)) != NULL) {
        if ( GetProcessMemoryInfo( hProcess, &pmc, sizeof(pmc)) ) {
            printf( "Size %zu = Working set size: %u\n", size, pmc.WorkingSetSize );
        }
        VirtualFree(address, size, MEM_RELEASE);

        if(size > SIZE_MAX / 2) {
            break;
        }
        size += size;
    }

    CloseHandle( hProcess );
    getchar();
    return 0;
}

在我的电脑上输出是:

Size 1 = Working set size: 1560576
Size 2 = Working set size: 1597440
Size 4 = Working set size: 1597440
Size 8 = Working set size: 1597440
Size 16 = Working set size: 1597440
Size 32 = Working set size: 1597440
Size 64 = Working set size: 1597440
Size 128 = Working set size: 1597440
Size 256 = Working set size: 1597440
Size 512 = Working set size: 1597440
Size 1024 = Working set size: 1597440
Size 2048 = Working set size: 1597440
Size 4096 = Working set size: 1597440
Size 8192 = Working set size: 1597440
Size 16384 = Working set size: 1597440
Size 32768 = Working set size: 1597440
Size 65536 = Working set size: 1597440
Size 131072 = Working set size: 1597440
Size 262144 = Working set size: 1597440
Size 524288 = Working set size: 1597440
Size 1048576 = Working set size: 1597440
Size 2097152 = Working set size: 1597440
Size 4194304 = Working set size: 1597440
Size 8388608 = Working set size: 1597440
Size 16777216 = Working set size: 1597440
Size 33554432 = Working set size: 1597440
Size 67108864 = Working set size: 1597440
Size 134217728 = Working set size: 1597440
Size 268435456 = Working set size: 1597440
Size 536870912 = Working set size: 1597440
Size 1073741824 = Working set size: 1601536
Size 2147483648 = Working set size: 1605632
Size 4294967296 = Working set size: 1613824
Size 8589934592 = Working set size: 1630208
Size 17179869184 = Working set size: 1662976
Size 34359738368 = Working set size: 1728512
Size 68719476736 = Working set size: 1859584
Size 137438953472 = Working set size: 2121728
Size 274877906944 = Working set size: 2646016
Size 549755813888 = Working set size: 3694592
Size 1099511627776 = Working set size: 5791744
Size 2199023255552 = Working set size: 9986048
Size 4398046511104 = Working set size: 18374656
Size 8796093022208 = Working set size: 35151872
Size 17592186044416 = Working set size: 68706304
Size 35184372088832 = Working set size: 135815168

【讨论】:

  • 我更改了问题中的测试程序。作为记录,您给出的值(25.6 MB 的内存消耗)是在使用 VirtualAlloc() 保留 13008 GiB 的虚拟内存时获得的。
  • 你的回答很好!您可以通过执行另外几次执行来使其很棒,例如使用以下大小:1 TiB 和 50 TiB。它可以让我们看到当保留区域的大小增加时开销的百分比是否增加。例如,如果你说预留 13008 GiB 的虚拟内存消耗 25.6 MB 的物理内存,那么开销百分比是25.6 / (13008*1024) == 0.00019%
  • @RalphS:完成 :-)
【解决方案2】:

保留内存的成本为每 64 KiB 1 位。即使我们释放该区域,它也永远不会被释放。注:VirtualAlloc(_, _, MEM_RESERVE, _)的粒度为64 KiB。

提交内存的成本为每 4 KiB 8 字节。即使内存未被触及,它也会被消耗,只有当我们释放整个保留区域时才会释放它(decommit 是不够的)。备注:指针大小为8字节,VirtualAlloc(_, _, MEM_COMMIT, _)的粒度为4 KiB(页面大小)。

这已经在 Windows 8.1(64 位)和 Windows 10(64 位)上进行了测试,使用 Mingw-w64 作为编译器。这让我感到惊讶,因为在 GNU/Linux 上,我们可以使用 mmap() 保留和提交整个虚拟地址空间,而不会注意到内存使用量的任何增加。

【讨论】:

    猜你喜欢
    • 2015-10-18
    • 2014-11-19
    • 2016-07-21
    • 2022-06-14
    • 2012-02-27
    • 1970-01-01
    • 2017-10-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多