Windows malloc替代方案(例如tcmalloc)和动态crt链接。

24

一个使用多个DLL和QT的C++程序应该配备一个malloc替代方案(如tcmalloc),以解决可以验证是由Windows malloc引起的性能问题。对于Linux,没有问题,但在Windows上,有几种方法,我发现都不令人满意:

1. 在lib中添加新的malloc,并确保将其链接至最前(其他SO问题)

这样做的缺点是,例如strdup仍将使用旧的malloc,可能会导致程序崩溃

2. 使用lib.exe从静态libcrt库中删除malloc (Chrome)

这被测试/用于chrome/chromium,但缺点是它只适用于静态链接crt。静态链接存在问题,如果一个系统库动态链接到msvcrt,就可能出现堆分配/释放不匹配的情况。如果我理解正确,tcmalloc可以被动态链接,使得所有自编译的dll都拥有一个共同的堆(很好)。

3. 修改crt源代码(firefox)

Firefox的jemalloc显然会修改Windows CRT源代码并构建新的CRT。这再次出现了上面提到的静态/动态链接问题。

我们可以考虑使用这个功能来生成动态MSVCRT,但我认为这不可能,因为许可证禁止提供具有相同名称的修补程序MSVCRT。

4. 在运行时动态修改已加载的CRT

一些商业内存分配器可以做到这一点。tcmalloc也可以,但这似乎相当丑陋。它曾经存在一些问题,但已经解决了。目前,在64位Windows下使用tcmalloc无法正常工作。

有更好的方法吗?任何评论?


1
那么你使用了哪种方法?你用哪种方法验证了备用分配器比CRT malloc提供的更好的断言?你使用了哪个版本的CRT,它是更好/更差/与新版本相同? - Adrian McCarthy
为什么不尝试替换全局的C++ new?这样做不就可以匹配共享库+应用程序主二进制文件+ms crt作为共享库设置吗? - mlvljr
4个回答

9

问:一个被分割成几个dll的C++程序应该:

A)替换malloc函数?

B)确保在同一dll模块中进行分配和释放?

答:正确答案是B。一个包含多个DLL的C++应用程序设计应确保存在机制以确保在一个DLL中分配的内容由同一DLL模块释放。


为什么要将C++程序拆分为多个DLL?这里的C++程序指的是您正在处理的对象和类型是C++模板、STL对象、类等。如果没有经过非常小心的设计和大量编译器特定的技巧,或者遭受各种DLL中大量对象代码的重复,那么您就无法跨DLL边界传递C++对象,并且因此会产生极度版本敏感的应用程序。任何对类定义的微小更改都将强制重新构建所有可执行文件和DLL文件,从而删除DLL方法开发应用程序的至少一个主要优势。

要么坚持在应用程序和DLL之间采用纯C接口,要么忍受痛苦,或者只需将整个C++应用程序编译为一个可执行文件。


6

一份C++程序声称“应该配备一个malloc替代品(如tcmalloc)来解决性能问题……”这是一个大胆的说法。

“在8个流行基准测试中有6个真实大小的应用程序,将人们投入了大量时间和金钱的自定义分配器替换回系统提供的愚蠢分配器,表现更好……只有针对非常特殊情况进行调整的最简单的自定义分配器才能提供收益。” --Andrei Alexandrescu

大多数系统分配器都与通用分配器一样好。只有当您具有非常特定的分配模式时,才能做得更好。

通常,这种特殊模式仅适用于程序的一部分,在这种情况下,将自定义分配器应用于可以受益的特定部分比全局替换分配器更好。

C++提供了一些方法来有选择性地替换分配器。例如,您可以向STL容器提供分配器,或者可以逐个类地覆盖new和delete。这两种方法都比全局替换分配器的任何hack更好地控制了分配器。

请注意,替换malloc和free不一定会改变操作符new和delete使用的分配器。虽然全局new操作符通常使用malloc实现,但并没有要求它这样做。因此,替换malloc可能甚至不会影响大部分分配。

如果您正在使用C语言,很有可能您可以将关键的malloc和free调用包装或替换为您的自定义分配器,仅在需要的地方使用它,并让程序的其余部分使用默认分配器。(如果不是这种情况,您可能需要考虑进行一些重构。)

系统分配器已经经历了几十年的开发。它们稳定且经过充分测试。它们在一般情况下表现非常出色(在原始速度、线程争用和碎片化方面)。它们具有用于泄漏检测的调试版本,并支持跟踪工具。有些甚至通过提供针对堆缓冲区溢出漏洞的防御机制来提高应用程序的安全性。您想要使用的库很有可能只与系统分配器进行了测试。

大多数替换系统分配器的技术都会放弃这些好处。在某些情况下,它们甚至会增加内存需求(因为它们无法与其他进程可能使用的DLL运行时共享)。它们还往往在编译器版本、运行时版本甚至操作系统版本发生变化时非常脆弱。使用修改过的运行时版本会阻止用户从操作系统供应商那里获得运行时更新的好处。既然可以通过仅将自定义分配器应用于可以从中受益的程序的特殊部分来保留这些好处,为什么要放弃它们呢?

1
你的前提“一个使用多个DLL和QT的C++程序应该配备一个malloc替换”是从哪里得来的?
在Windows上,如果所有的dll都使用共享的MSVCRT,则无需替换malloc。Qt默认构建针对共享的MSVCRT dll。
如果出现以下问题,则会遇到问题:
1)混合使用静态链接的dll与使用共享VCRT
2)并且释放不是从其分配的内存(即,在静态链接的dll中释放由共享VCRT分配的内存或反之亦然)。
请注意,添加自己的引用计数包装器以围绕资源可以帮助减轻与需要以特定方式处理的资源相关的问题(即,通过回调到原始dll处处置一种类型的资源的包装器,另一种来源于另一个dll的资源的不同包装器等)。

5
使用tcmalloc而不是MSVCRT时,可以获得大幅度的性能提升,这就是前提条件。 - Weidenrinde
1
如果是这样的话,那么一个不使用Qt的C程序或者C++程序可以从这个改变中受益。然而,在分析表明MSVCRT不足之前,我不同意它们“应该配备一个malloc替代品”。 - sean e
2
  1. 明显的大幅性能提升与MSVCRT的内存管理密切相关。影响取决于程序的分配方式,因此我并不认为MSVCRT是不好的。
  2. 我只是提到Qt是因为几个子应用程序使用QT库,因此静态链接不是首选选项。
- Weidenrinde
1
请仔细阅读:我说的是“一个程序”,而不是“C++程序”。MSVCRT对堆有一种特殊的小对象处理方式,但据我所知,它具有固定的小对象最大大小。对于特定的程序,这种行为表现不佳。 - Weidenrinde
2
更改系统内存分配策略通常是可取的,因为该系统内存分配器与其他替代方案(例如我广泛使用的dlmalloc)相比具有不同的性能特征。显然,不需要内存分配并仅使用静态池可以避免这个问题,但是如果继承了大量进行内存分配并调用malloc()和其它函数的代码,则通常可以通过选择另一种替代方案来提高性能。默认设置可能对许多应用程序已足够,但它绝非最佳性能领袖。 - dash-tom-bang
显示剩余4条评论

1

nedmalloc?还要注意的是,smplayer使用了一个特殊的补丁来覆盖malloc,这可能是你要走的方向。


你知道nedmalloc如何处理这个问题吗? - Weidenrinde
不确定,但我知道它“不会自动替换系统malloc()”。http://www.nedprod.com/programs/portable/nedmalloc/可能有更多信息。 - rogerdpack
在nedmalloc源代码中,有一个winpatcher。看起来它正在执行(4)的魔法,这可能是你想要的。不过还没有检查过,是否支持x64 Windows。 - zerm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接