VirtualAlloc的内存使用率高于预期; 这是怎么回事?

tim*_*day 4 c++ 64-bit winapi virtualalloc virtual-memory

重要提示:在此处投入太多时间之前,请向下滚动到"最终更新".事实证明,主要的教训是要注意单元测试套件中其他测试的副作用,并在得出结论之前始终将事物单独复制!


从表面上看,以下64位代码使用VirtualAlloc(总共4GByte)分配(并访问)一兆的4k页面:

const size_t N=4;  // Tests with this many Gigabytes
const size_t pagesize4k=4096;
const size_t npages=(N<<30)/pagesize4k;

BOOST_AUTO_TEST_CASE(test_VirtualAlloc) {

  std::vector<void*> pages(npages,0);
  for (size_t i=0;i<pages.size();++i) {
    pages[i]=VirtualAlloc(0,pagesize4k,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);
    *reinterpret_cast<char*>(pages[i])=1;
  }

  // Check all allocs succeeded
  BOOST_CHECK(std::find(pages.begin(),pages.end(),nullptr)==pages.end()); 

  // Free what we allocated
  bool trouble=false;
  for (size_t i=0;i<pages.size();++i) {
    const BOOL err=VirtualFree(pages[i],0,MEM_RELEASE);
    if (err==0) trouble=true;
  }
  BOOST_CHECK(!trouble);
}
Run Code Online (Sandbox Code Playgroud)

但是,在执行它时会增加Windows任务管理器中报告的"工作集" (并通过"峰值工作集"列中的"粘贴"值确认)从基线~200,000K(~200MByte)到超过6,000,000或7,000,000K (在64位Windows7上测试,也在ESX虚拟化的64位Server 2003和Server 2008上测试;遗憾的是我没有注意到观察到的各种数字的系统).

另一个非常相似的测试案例在同一个单元测试可执行文件中测试了一个超级4k的mallocs(后面是frees),并且在运行时只能扩展到预期的4GByte.

我不明白:VirtualAlloc是否有一些相当高的每分配开销?如果是这样,它显然是页面大小的一小部分; 为什么需要这么多额外的东西以及它的用途是什么?或者我误解了"工作集"报道实际上意味着什么?这里发生了什么?

更新:参考Hans的回答,我注意到在第二页访问中出现访问冲突失败,所以无论发生什么都不如分配到64K"粒度"那么简单.

char*const ptr = reinterpret_cast<char*>(
  VirtualAlloc(0, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)
);
ptr[0] = 1;
ptr[4096] = 1;
Run Code Online (Sandbox Code Playgroud)

更新:现在在安装了VisualStudioExpress2013的AWS/EC2 Windows2008 R2实例上,我无法使用这个最小代码(编译为64位)重现问题,该代码最终显示为4,335,816K的明显无开销峰值工作集,这是我原本希望看到的那种数字.所以要么我正在运行的其他机器,或者在之前的测试中使用的基于boost测试的exe有所不同. Bizzaro,继续......

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <vector>

int main(int, char**) {

    const size_t N = 4;
    const size_t pagesize4k = 4096;
    const size_t npages = (N << 30) / pagesize4k;

    std::vector<void*> pages(npages, 0);
    for (size_t i = 0; i < pages.size(); ++i) {
        pages[i] = VirtualAlloc(0, pagesize4k, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
        *reinterpret_cast<char*>(pages[i]) = 1;
    }

    Sleep(5000);

    for (size_t i = 0; i < pages.size(); ++i) {
        VirtualFree(pages[i], 0, MEM_RELEASE);
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

最后更新:道歉!如果可以的话,我会删除这个问题,因为事实证明观察到的问题完全是由于测试套件中的一个紧接的单元测试,它使用TBB的"可扩展分配器"来分配/解除分配几个GB的东西.似乎可伸缩分配器实际上在它自己的池中保留了这样的分配,而不是将它们返回到系统(参见例如这里这里).很明显,一旦我单独运行测试,并且足够跟随Sleep他们观察他们在任务管理器中的完成工作集(对于TBB行为是否可以做任何事情可能是一个有趣的问题,但问题是这里的问题是红色-鲱鱼).

Han*_*ant 5

   pages[i]=VirtualAlloc(0,pagesize4k,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);
Run Code Online (Sandbox Code Playgroud)

您将不会获得4096个字节,它将被舍入到允许的最小分配.这是SYSTEM_INFO.dwAllocationGranularity,它已经很长时间了64KB.这是一个非常基本的地址空间碎片对策.

所以你分配的方式比你想象的要多.