Причина замедления ~ 100x с функциями памяти кучи с использованием HEAP_NO_SERIALIZE в Windows Vista и Windows 7

Я пытаюсь проследить огромное замедление функций памяти кучи в Windows Vista и Windows 7 (я не тестировал на каких-либо серверных версиях). Это вообще не происходит в Windows XP, только в новых операционных системах Microsoft.

Я изначально столкнулся с этой проблемой, когда PHP выполнялся в Windows. Похоже, что сами скрипты выполнялись с ожидаемой скоростью, но после выполнения script я столкнулся с 1-2 секундами задержки во внутренних функциях выключения PHP. После запуска отладки я увидел, что это связано с использованием диспетчера памяти 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

и (который фактически устанавливает значение по умолчанию для HeapAlloc/HeapFree/HeapReAlloc) в функции start_memory_manager:

#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, поэтому мне придется делать некоторые копания и исследования, но это вызывает вопрос:

Почему функция памяти кучи настолько медленная в Windows Vista и Windows 7 с HEAP_NO_SERIALIZE?

Во время исследования этой проблемы я придумал ровно один хороший удар. Пожалуйста, прочитайте сообщение в блоге http://www.brainfarter.net/?p=69, где плакат объясняет эту проблему и предлагает тестовый пример (как исходный, так и двоичный файл), чтобы выделить проблему.

Мои тесты на четырехъядерном 8-ядерном 8-ядерном 8-ядерном компьютере Windows 7 дают 43 836. Ой! Те же результаты без флага HEAP_NO_SERIALIZE 655, ~ 70x быстрее в моем случае.

Наконец, кажется, что любая программа, созданная с использованием Visual С++ 6 с использованием malloc/free или new/delete, кажется, затронута на этих новых платформах. Компилятор Visual С++ 2008 не устанавливает этот флаг по умолчанию для этих функций/операторов, поэтому они не затрагиваются - но это все еще оставляет много затронутых программ!

Я рекомендую вам загрузить доказательство концепции и попробовать. Эта проблема объясняет, почему мой обычный PHP на установке Windows обход и может объяснить, почему Windows Vista и Windows 7 кажутся медленнее время от времени.

UPDATE 2010-01-26: Я получил ответ от Microsoft, в котором говорится, что кучка с низкой фрагментацией (LFH) является фактической политикой дефолта для куч, которые содержат сколько-нибудь заметное количество распределений. В Windows Vista они реорганизовали много кода для удаления дополнительных структур данных и путей кода, которые больше не являются частью общего случая для обработки вызовов API кучи. С флагом HEAP_NO_SERIALIZE и в некоторых ситуациях отладки они не позволяют использовать LFH, и мы застряли на медленном и менее оптимизированном пути через диспетчер кучи. Поэтому... настоятельно рекомендуется не использовать HEAP_NO_SERIALIZE, так как вы пропустите всю работу с LFH и любую будущую работу в API кучи Windows.

Ответ 1

Первое различие, которое я заметил, заключается в том, что Windows Vista всегда использует кукурузу с низкой фрагментацией (LFH). Windows XP не кажется. RtlFreeHeap в Windows Vista в результате намного короче - вся работа делегируется RtlpLowFragHeapFree. Дополнительная информация о LFH и его присутствии в различных ОС. Обратите внимание на красное предупреждение в верхней части.

Дополнительная информация (раздел замечаний):

Windows XP, Windows Server 2003 и Windows 2000 с исправлением KB 816542:

Список поиска - это быстрая память механизм распределения, содержащий только блоки фиксированного размера. Посмотрите, в сторону списки включены по умолчанию для кучи которые поддерживают их. Начиная с Windows Vista, списки поиска не используется, а LFH включен по умолчанию.

Другая важная часть информации: LFH и NO_SERIALIZE являются взаимоисключающими (оба одновременно не могут быть активны). В сочетании с

Начиная с Windows Vista, списки поиска не используется

Это означает, что установка NO_SERIALIZE в Windows Vista отключает LFH, но она не возвращается (и не может) обратно в стандартные списки поиска (в качестве быстрой замены) в соответствии с приведенной выше цитатой. Я не понимаю, какая стратегия распределения кучи используется Windows Vista при указании NO_SERIALIZE. Похоже, что он использует что-то ужасно наивное, исходя из его производительности.

Еще больше информации:

Посмотрев несколько снимков в стеке allocspeed.exe, он всегда находится в состоянии готовности (не работает или ждет) и в TryEnterCriticalSection от HeapFree и привязывает процессор почти на 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 Chen или Марк Руссинович мог ответить:)