VS2012编译器奇怪的内存释放问题

我遇到了VS2012编译器的一个奇怪的问题,似乎没有出现在GCC中。 取消分配过程最终需要几分钟而不是几秒钟。 有没有人对此有任何意见? 在调用RtlpCollectFreeBlocks()时,单步debugging显示明显的挂起。 我在debugging和发布模式下都遇到了这个问题。 我正在运行Windows 7 32位,但我有同样的问题在64位7。

#include "stdafx.h" #include <iostream> #include <stdint.h> #include <cstdlib> #define SIZE 500000 using namespace std; typedef struct { uint32_t* thing1; }collection; /* * VS2012 compiler used. * Scenarios: * 1) Don't allocate thing1. Program runs poorly. * 2) Allocate thing1 but don't delete it. Program runs awesome. * 3) Allocate thing1 and delete it. Program runs poorly. * * Debug or Release mode does not affect outcome. GCC's compiler is fine. */ int _tmain(int argc, _TCHAR* argv[]) { collection ** colArray = new collection*[SIZE]; for(int i=0;i<SIZE;i++) { collection * mine = new collection; mine->thing1 = new uint32_t; // Allocating without freeing runs fine. Either A) don't allocate or B) allocate and delete to make it run slow. colArray[i] = mine; } cout<<"Done with assignment\n"; for(int i=0;i<SIZE;i++) { delete(colArray[i]->thing1); // delete makes it run poorly. delete(colArray[i]); if(i > 0 && i%100000 == 0) { cout<<"100 thousand deleted\n"; } } delete [] colArray; cout << "Done!\n"; int x; cin>>x; } 

你看到的性能影响来自Windows调试堆功能,即使在发布版本中,它也是如此。

我冒昧地构建了一个更简单的程序的64位调试图像,并发现这一点:

  • msvcr110d.dll!_CrtIsValidHeapPointer(const void * pUserData = 0x0000000001a8b540)
  • msvcr110d.dll!_free_dbg_nolock(void * pUserData = 0x0000000001a8b540,int nBlockUse = 1)
  • msvcr110d.dll!_free_dbg(void * pUserData = 0x0000000001a8b540,int nBlockUse = 1)
  • msvcr110d.dll!操作员删除(void * pUserData = 0x0000000001a8b540)

我特别感兴趣的是msvcr110d.dll!_CrtIsValidHeapPointer它的结果是这样的:

 if (!pUserData) return FALSE; // Note: all this does is checks for null if (!_CrtIsValidPointer(pHdr(pUserData), sizeof(_CrtMemBlockHeader), FALSE)) return FALSE; // but this is expensive return HeapValidate( _crtheap, 0, pHdr(pUserData) ); 

HeapValidate()调用是残酷的。

好吧,也许我会期望这在一个调试版本。 但肯定不会释放。 事实证明,这会变得更好,但看看调用堆栈:

  • ntdll.dll中!RtlDebugFreeHeap()
  • ntdll.dll!字符串“启用堆调试选项\ n”()
  • ntdll.dll中!RtlFreeHeap()
  • KERNEL32.DLL!HeapFree()
  • msvcr110.dll!free(void * pBlock)

这很有意思,因为当我第一次运行这个程序时,使用IDE(或WinDbg)附加到正在运行的进程而不允许它控制执行启动环境,这个调用堆栈在ntdll.dll!RtlFreeHeap()处停止。 换句话说,不会调用在IDE RtlDebugFreeHeap之外运行。 但为什么??

我想,自己, 不知何故调试器是翻转开关,以启用堆调试。 在做了一些挖掘之后,我发现“开关”本身就是调试器。 如果正在运行的进程由调试器产生,则Windows将使用特殊的调试堆函数( RtlDebugAllocHeapRtlDebugFreeHeap )。 在WinDbg的MSDN上的这个手册页 ,以及有关在Windows下进行调试的其他有趣的小技巧,

使用WinDbg调试用户模式进程

调试器创建的进程(也称为派生进程)的行为与调试器不创建的进程稍有不同。

调试器创建的进程不使用标准堆API,而是使用特殊的调试堆。 通过使用_NO_DEBUG_HEAP环境变量或-hd命令行选项,可以强制派生进程使用标准堆而不是调试堆。

现在我们正在某处。 为了测试这个,我简单地放了一个sleep()给我一个适当的时间来附加调试器,而不是用它产生过程,然后让它运行在它的快乐的路上。 果然如前所述,它全速前进。

根据那篇文章的内容,我冒昧地更新了我的发布模式版本,以在我的项目文件的执行环境设置中定义_NO_DEBUG_HEAP=1 。 我显然仍然对调试版本中的粒度堆活动感兴趣,所以这些配置保持原样。 这样做后,我发布的版本在VS2012(和VS2010)下运行的整体速度要快得多,我也请你试试。