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

Posted

技术标签:

【中文标题】VS2012 编译器奇怪的内存释放问题【英文标题】:VS2012 compiler Strange memory deallocation issues 【发布时间】:2013-09-15 18:14:05 【问题描述】:

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;

【问题讨论】:

在堆上分配单个 int 通常不是惯用的 C++ - 如果按值存储它有帮助吗? 这只是一个简单的例子。我最初在结构中有 4 个 uint32_t。这个想法是为了表明结构的大小对于这个问题似乎并不重要。 @Sean 在SIZE 上使用二分搜索是否有性能大幅提升的点? 它肯定与 ide 挂钩有关。切换到 cmd 提示符并从控制台运行您的程序。完全没有问题,内存模型和调试状态没有区别。 连同***.com/questions/6486282/set-no-debug-heap 【参考方案1】:

您看到的性能影响来自 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) );

HeapValidate() 的电话很残酷。

好的,也许我希望在调试版本中做到这一点。但肯定不会释放。事实证明,这会变得更好,但请查看调用堆栈:

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

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

我心想,不知何故调试器正在翻转开关以启用堆调试。在做了一些挖掘之后,我发现“开关”就是调试器本身。如果正在运行的进程是由调试器生成的,Windows 将使用特殊的调试堆函数(RtlDebugAllocHeapRtlDebugFreeHeap)。 This man-page from MSDN on WinDbg 避开了这一点,以及其他有关 Windows 下调试的有趣花絮:

来自使用 WinDbg 调试用户模式进程

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

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

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

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

【讨论】:

以上是关于VS2012 编译器奇怪的内存释放问题的主要内容,如果未能解决你的问题,请参考以下文章

iOS:ARC,不释放内存

Linux 分配器不会释放小块内存

为啥有时会立即释放内存,而有时仅在自动释放池耗尽时才释放内存?

iPhone 内存释放问题

此程序为什么打印带有奇怪字符的字符串?尽管释放了内存泄漏?

在 C 或 C++ 中释放内存 [重复]