Хабрахабр

[Перевод] Надо знать, где поставить ноль

В других же случаях серьёзный прирост производительности даёт минимальное изменение: иногда нужно лишь поставить ноль. Для некоторых оптимизаций требуются сложные структуры данных и тысячи строк кода. Это похоже на старую байку о котельщике, который знает правильное место для удара молотком, а потом выставляет клиенту счёт: $0,50 за удар по клапану и $999,50 за знание, куда бить.

Я лично встречал несколько ошибок производительности, которые исправлялись вводом одного нуля, и в этой статье хочу поделиться двумя историями.

Важность измерения

Во времена оригинальной Xbox я помог оптимизировать множество игр. В одной из них профилировщик указал на функцию матричного преобразования, которая потребляла 7% времени CPU — самый большой скачок на графике. Поэтому я прилежно приступил к работе по оптимизации этой функции.

Функцию уже переписали на ассемблере. Видно, что я не первый пытался это сделать. Это важный шаг, иначе легко заняться «оптимизацией», которая ничего не изменит или даже ухудшит ситуацию. Я нашёл несколько потенциальных улучшений в языке ассемблера и попытался измерить их эффект.

Я запускал игру, немного играл с параллельным профилированием, а затем изучал профиль: стал ли код быстрее. Однако измерение оказалось затруднено. Казалось, что есть какое-то небольшое улучшение, но нельзя было сказать наверняка.

Написал коллекцию тестов для управления старой и новой версиями кода, чтобы точно измерить различия в производительности. Так что я применил научный метод. Это не заняло много времени: как и ожидалось, новый код оказался примерно на 10% быстрее старого.

Но выясмнилось, что 10% ускорения — это ерунда.

Вот это было захватывающее открытие. Гораздо интереснее, что внутри теста код выполнялся примерно в 10 раз быстрее, чем в игре.

После проверки результатов я некоторое время смотрел в пустоту, но потом меня осенило.

Роль кэширования

Чтобы дать разработчикам игр полный контроль и максимальную производительность, игровые приставки позволяют выделять память с различными атрибутами. В частности, оригинальный Xbox позволяет выделять некэшируемую память. Этот тип памяти (фактически, тип тега в таблицах страниц) полезен при записи данных для GPU. Поскольку память не кэшируется, запись почти сразу пойдёт в RAM без задержек и загрязнения кэша при «нормальном» мэппинге.

В частности, крайне важно, чтобы игры никогда не пытались читать из некэшируемой памяти, иначе их производительность серьёзно снизится. Таким образом, некэшируемая память — важная оптимизация, но её следует использовать осторожно. Даже относительно медленному CPU на 733 МГц в оригинальном Xbox нужны свои кэши, чтобы обеспечить достаточную производительность при чтении данных.

Судя по всему для этой функции данные выделяются в некэшируемой памяти, отсюда низкая производительность. Теперь становится понятно, что происходит. Я нашёл строку, где выделяется память, дважды щёлкнул по значению флага, и указал ноль. Небольшая проверка подтвердила эту гипотезу, так что пришло время исправить проблему.

Вместо примерно 7% процессорного времени функция стала потреблять около 0,7% и больше не представляла проблемы.

По итогам недели мой отчёт выглядел примерно так: «39,999 часа исследований, 0,001 часа программирования — огромный успех!»

Но если вам интересно, насколько некэшируемая память способна замедлить работу программы, попробуйте флаги PAGE_NOCACHE или PAGE_WRITECOMBINE в VirtualAlloc. Разработчикам обычно не нужно беспокоиться о случайном выделении некэшируемой памяти: в большинстве операционных систем эта опция не доступна в пользовательском пространстве стандартными методами.

0 ГиБ лучше, чем 4 ГиБ

Хочу рассказать вам ещё одну историю. Она о баге, который нашёл я, а исправил кто-то другой. Пару лет назад я заметил, что дисковый кэш на моём ноутбуке слишком часто прочищается. Я отследил, что это происходит при достижении рубежа 4 ГиБ, и в итоге оказалось, что драйвер моего нового HDD для бэкапов устанавливает SectorSize в 0xFFFFFFFF (или −1) при указании на неизвестный размер сектора. Ядро Windows интерпретирует это значение как 4 ГиБ и выделяет соответствующий блок памяти, что и стало причиной проблемы.

Один введённый символ — и решена серьёзная проблема производительности. У меня нет контактов в Western Digital, но можно с уверенностью предположить, что они исправили эту ошибку, заменив константу 0xFFFFFFFF (или −1) на ноль.

(Подробнее об этом исследовании читайте в статье «Замедление Windows: изучение и идентификация»)

Наблюдения

  • В обоих случаях проблема связана с кэшированием
  • Решающим стало использование профилировщика для точного определения проблемы
  • Если патч не проверен измерениями, то он не обязательно поможет
  • Я мог бы написать о многих других таких случаях, но они либо слишком секретны, либо слишком скучны
  • Правильное решение не обязательно должно быть сложным. Иногда огромное улучшение даёт небольшое изменение. Нужно только знать, в каком месте

Мне случалось оптимизировать код, расскоментив #define и путём других тривиальных изменений. Расскажите в комментариях, если у вас есть такие истории.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть