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

Sea*_*ean 6 c++ memory windows memory-management visual-studio-2012

我有一个VS2012编译器的奇怪问题,似乎没有出现在GCC中.重新分配过程最终需要几分钟而不是几秒钟.有人对此有任何意见吗?步调试显示在调用RtlpCollectFreeBlocks()时显着挂起.我在调试和发布模式下都有这个问题.我正在运行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;
}
Run Code Online (Sandbox Code Playgroud)

Who*_*aig 8

您所看到的性能影响来自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!operator delete(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 e-x-p-e-n-s-i-v-e
return HeapValidate( _crtheap, 0, pHdr(pUserData) );
Run Code Online (Sandbox Code Playgroud)

这个HeapValidate()电话是残酷的.

好的,也许我会在调试版本中期待这个.但肯定不会发布.事实证明,这会变得更好,但看看调用堆栈:

  • ntdll.dll中!RtlDebugFreeHeap()
  • ntdll.dll!string"启用堆调试选项\n"()
  • ntdll.dll中!RtlFreeHeap()
  • KERNEL32.DLL!HeapFree()
  • msvcr110.dll!free(void*pBlock)

这很有趣,因为当我首先运行它时,然后使用IDE(或WinDbg)连接到正在运行的进程而不允许它控制执行启动环境,此调用栈停止ntdll.dll!RtlFreeHeap().换句话说,RtlDebugFreeHeap不会调用在IDE外部运行.但为什么??

我心想,不知何故调试器扳动开关,使堆调试.在做了一些挖掘后,我发现"switch"是调试器本身.如果正在运行的进程由调试器生成,Windows将使用特殊的调试堆函数(RtlDebugAllocHeapRtlDebugFreeHeap).这篇来自MSDN on WinDbg的man-page以及其他有关Windows下调试的有趣花絮:

使用WinDbg调试用户模式进程

调试器创建的进程(也称为生成进程)的行为与调试器未创建的进程略有不同.

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

现在我们到了某个地方.为了测试这个,我简单地删除了sleep()一个适当的时间让我附加调试器而不是用它生成进程,然后让它以它的快乐方式运行.果然,如前所述,它全速前进.

基于该文章的内容,我可以自由更新我的发布模式构建,以_NO_DEBUG_HEAP=1在其执行环境中定义项目文件的设置.我显然仍然对调试版本中的粒度堆活动感兴趣,因此这些配置保持原样.执行此操作后,我在VS2012(和VS2010)下运行的版本的整体速度大大加快,我邀请您尝试.