winapi - 在 Windows Vista 和 Windows 7 上使用 HEAP_NO_SERIALIZE 的堆内存函数速度减慢约 100 倍的原因

标签 winapi memory-management windows-7 heap-memory windows-vista

我正在尝试追踪 Windows Vista 和 Windows 7 中堆内存功能的巨大减慢(我没有在任何服务器版本上进行测试)。这种情况在 Windows XP 上根本不会发生,只会在 Microsoft 较新的操作系统上发生。

我最初在 Windows 上编译 PHP 时遇到了这个问题。脚本本身似乎以预期的速度运行,但在脚本执行后,我在内部 PHP 关闭函数中遇到了 1-2 秒的延迟。启动调试后,我发现这与 PHP 内存管理器使用 HeapAlloc/HeapFree/HeapReAlloc 有关。

我追踪到了堆函数上标志 HEAP_NO_SERIALIZE 的使用:

#ifdef ZEND_WIN32
#define ZEND_DO_MALLOC(size) (AG(memory_heap) ? HeapAlloc(AG(memory_heap), HEAP_NO_SERIALIZE, size) : malloc(size))
#define ZEND_DO_FREE(ptr) (AG(memory_heap) ? HeapFree(AG(memory_heap), HEAP_NO_SERIALIZE, ptr) : free(ptr))
#define ZEND_DO_REALLOC(ptr, size) (AG(memory_heap) ? HeapReAlloc(AG(memory_heap), HEAP_NO_SERIALIZE, ptr, size) : realloc(ptr, size))
#else
#define ZEND_DO_MALLOC(size) malloc(size)
#define ZEND_DO_FREE(ptr) free(ptr)
#define ZEND_DO_REALLOC(ptr, size) realloc(ptr, size)
#endif

以及start_memory_manager函数中的(实际上设置了HeapAlloc/HeapFree/HeapReAlloc的默认值):

#ifdef ZEND_WIN32
    AG(memory_heap) = HeapCreate(HEAP_NO_SERIALIZE, 256*1024, 0);
#endif

我删除了 HEAP_NO_SERIALIZE 参数(替换为 0),它解决了问题。现在,CLI 和 SAPI Apache 2 版本中的脚本都可以快速清理。这是针对 PHP 4.4.9 的,但 PHP 5 和 6 源代码(正在开发中)在调用中包含相同的标志。

我不确定我所做的是否危险。它都是 PHP 内存管理器的一部分,因此我将不得不进行一些挖掘和研究,但这提出了一个问题:

为什么使用 HEAP_NO_SERIALIZE 的 Windows Vista 和 Windows 7 上的堆内存函数如此缓慢?

在研究这个问题时,我得到了一个很好的结果。请阅读博文http://www.brainfarter.net/?p=69发布者解释了该问题并提供了一个测试用例(源代码和二进制文件均可用)来突出显示该问题。

我在 Windows 7 x64 四核 8 GB 计算机上进行的测试给出了43,836。哎哟!没有 HEAP_NO_SERIALIZE 标志的相同结果是 655,在我的例子中快了约 70 倍。

最后,似乎任何使用 Visual C++ 6 创建的程序都使用 malloc/freenew/delete 似乎在这些较新的平台上受到影响。 Visual C++ 2008 编译器默认情况下不会为这些函数/运算符设置此标志,因此它们不会受到影响 - 但这仍然会让许多程序受到影响!

我鼓励您下载概念验证并尝试一下。这个问题解释了为什么我在 Windows 上正常安装的 PHP 正在爬行,并且可能解释为什么 Windows Vista 和 Windows 7 有时看起来较慢。

更新 2010-01-26: 我收到了 Microsoft 的回复,指出 low-fragmentation heap (LFH) 是保存大量分配的堆的事实上的默认策略。在 Windows Vista 中,他们重新组织了大量代码,删除了额外的数据结构和代码路径,这些数据结构和代码路径不再是处理堆 API 调用的常见情况的一部分。使用 HEAP_NO_SERIALIZE 标志并在某些调试情况下,它们不允许使用 LFH,并且我们陷入了通过堆管理器的缓慢且优化程度较低的路径上。因此...强烈建议不要使用 HEAP_NO_SERIALIZE,因为您将错过 LFH 的所有工作以及 Windows 堆 API 中的任何 future 工作。

最佳答案

我注意到的第一个区别是 Windows Vista 始终使用低碎片堆 (LFH)。 Windows XP 好像没有。因此,Windows Vista 中的 RtlFreeHeap 的长度要短得多 - 所有工作都委托(delegate)给 RtlpLowFragHeapFreeMore information regarding LFH及其在各种操作系统中的存在。请注意顶部的红色警告。

More information (备注部分):

Windows XP, Windows Server 2003, and Windows 2000 with hotfix KB 816542:

A look-aside list is a fast memory allocation mechanism that contains only fixed-sized blocks. Look-aside lists are enabled by default for heaps that support them. Starting with Windows Vista, look-aside lists are not used and the LFH is enabled by default.

另一个重要信息:LFH 和 NO_SERIALIZE 是互斥的(两者不能同时激活)。结合

Starting with Windows Vista, look-aside lists are not used

这意味着在 Windows Vista 中设置 NO_SERIALIZE 会禁用 LFH,但它不会(也不能)回退到标准后备列表(作为快速替换) ,根据上面的引用。我不清楚指定 NO_SERIALIZE 时 Windows Vista 使用什么堆分配策略。从它的性能来看,它似乎使用了一些非常幼稚的东西。

更多信息:

查看 allocspeed.exe 的一些堆栈快照,它似乎始终处于就绪状态(不是运行或等待),并且位于 HeapFree 的 TryEnterCriticalSection 中,并将 CPU 固定在接近 100 % 负载 40 秒。 (在 Windows Vista 上。)

示例快照:

ntdll.dll!RtlInterlockedPushEntrySList+0xe8
ntdll.dll!RtlTryEnterCriticalSection+0x33b
kernel32.dll!HeapFree+0x14
allocspeed.EXE+0x11ad
allocspeed.EXE+0x1e15
kernel32.dll!BaseThreadInitThunk+0x12
ntdll.dll!LdrInitializeThunk+0x4d

这很奇怪,因为 NO_SERIALIZE 准确地告诉它跳过锁定获取。有些东西没有加起来。

这只是一个问题Raymond ChenMark Russinovich可以回答:)

关于winapi - 在 Windows Vista 和 Windows 7 上使用 HEAP_NO_SERIALIZE 的堆内存函数速度减慢约 100 倍的原因,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1983563/

相关文章:

python - 最小化 Tk 窗口

c++ - 如何检测从 "Startup"文件夹快捷方式启动?

c++ - 将Win32对话框中的图标设置为默认图标

c++ - 如何将 CListCtrl 中的项目设置为选中?

c++ - 删除数组时出现访问冲突异常

c - 在c中使用参数作为输出

objective-c - 在 UIViewController 中加载和卸载数据

windows - 如何在 Windows 上安装 G++ 4.9

c - 简单 float 组初始化中的堆栈溢出

c++ - 使用文件 I/O 正确创建和运行 win32 服务