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

时间:2014-01-03 20:06:20

标签: c++ winapi 64-bit virtual-memory virtualalloc

重要:在此处投入太多时间之前,请向下滚动至“最终更新”。事实证明,主要的教训是要小心你的unittest套件中其他测试的副作用,并且总是在跳到结论之前单独复制


从表面上看,以下64位代码使用VirtualAlloc(总共4GByte)分配(并访问)1兆的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);
}

然而,在执行它时,“工作集”reported in Windows Task Manager(并通过“峰值工作集”列中的“粘贴”值确认)从基线~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;

更新:现在,在安装了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;
}

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

2 个答案:

答案 0 :(得分:5)

   pages[i]=VirtualAlloc(0,pagesize4k,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);

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

所以你比你想象的更多地分配方式

答案 1 :(得分:0)

事实证明,由于测试套件中的前一个单元测试使用TBB的“可扩展分配器”来分配/解除分配几个GB的东西,因此观察到的问题完全 。似乎可伸缩分配器实际上在它自己的池中保留了这样的分配,而不是将它们返回给系统(参见例如herehere)。一旦我单独运行了足够的Sleep测试,以便在任务管理器中观察完成后的工作集,就会变得明显。