重要提示:在您在这里投入太多时间之前,请向下滚动至“最终更新”。事实证明,主要的教训是要提防单元测试套件中其他测试的副作用,并在得出结论之前始终孤立地重现事物!
从表面上看,下面的 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);
}
但是,在执行它时,Windows 任务管理器中报告的“工作集” (并由“峰值工作集”列中的“粘着”值确认)从基线 ~200,000K (~200MByte) 增加到超过 6,000,000 或 7,000,000K (在 64 位 Windows7 以及 ESX 虚拟化的 64 位 Server 2003 和 Server 2008 上进行了测试;不幸的是,我没有注意到观察到的各种数字发生在哪些系统上)。
同一个 unittest 可执行文件中的另一个非常相似的测试用例测试了一个兆 4k mallocs(随后是 frees),并且在运行时仅扩展了预期的 4GByte 左右。
我不明白:VirtualAlloc 是否有相当高的 per-alloc 开销?如果是这样,它显然是页面大小的很大一部分;为什么需要这么多额外的东西,它有什么用?还是我误解了“工作集”报告的实际含义?这里发生了什么?
更新:参考汉斯的回答,我注意到这在第二页访问中出现访问冲突而失败,所以无论发生什么都不像分配被四舍五入到 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-test 的 exe 有所不同。 比扎罗,待续……
#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 的东西。似乎可扩展分配器实际上将此类分配保留在它自己的池中,而不是将它们返回给系统(参见例如此处或此处)。一旦我单独运行测试并Sleep
在他们之后观察他们在任务管理器中完成时的工作集时变得明显(是否可以对 TBB 行为做任何事情可能是一个有趣的问题,但这里的问题是红色的-鲱鱼)。