Причина замедления ~ 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 или Марк Руссинович мог ответить:)