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

Original author: Bruce Dawson
  • Translation


Для некоторых оптимизаций требуются сложные структуры данных и тысячи строк кода. В других же случаях серьёзный прирост производительности даёт минимальное изменение: иногда нужно лишь поставить ноль. Это похоже на старую байку о котельщике, который знает правильное место для удара молотком, а потом выставляет клиенту счёт: $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 и путём других тривиальных изменений. Расскажите в комментариях, если у вас есть такие истории.
Support the author
Share post

Similar posts

Comments 39

    +9
    Буквально вчера был эпизод увеличения производительности в ~72 раза одной кнопкой. Это было delete по вызову логирования, который по некоторым историческим причинам попал внутрь одной функции, вызываемой при численном интегрировании сложной матмодели. Профилировщик, правда, для этого не понадобился, просто нужно помнить что логи — штука медленная, и если пишешь их для отладки, то после себя — удалять, а не комментировать на случай «а вдруг оно опять понадобится». Не понадобится. Зато кто-нибудь другой может залезть в код и сказать «о! логи закоменчены!» и вернуть всё взад.
      0
      А можно добавить еще один коммент: " логи — штука медленная!"? :)
      +1
      Почему Microsoft просто не поставили более мощный процессор в приставку?
      Ведь разработка нового процессора стоит меньше, чем зарплата программиста.
        +24
        А ведь именно из-за того мышления мы сейчас получаем лагающие игры даже на новых i9 в связке с 2080.
        Код должен быть оптимизирован всегда. А херовый код даже лучший процессор не вытянет.
          +17
          Да нет, просто подтянулись разработчики на Electron =)
            +22
            Я искренне подозреваю что сказанное про процессор было сарказмом, просто его не поняли. Ведь разработка процессора намного дороже чем найти баг в программе.
              +1
              разработка
              Нет, это было сказано полностью серьезно, конечно-же.
                0

                Может имелась ввиду не разработка, а покупка процессора?

                  +7
                  walti
                  Ведь разработка нового процессора стоит меньше, чем зарплата программиста.

                  walti
                  разработка
                  Нет, это было сказано полностью серьезно, конечно-же.

                  dimonoid
                  Может имелась ввиду не разработка, а покупка процессора?


                  Знаете, я уже не уверен, что я имел ввиду. (с) walti
              –4
              Код должен

              Кому должен?

              +5
              У приставок своя атмосфера. В отличие от настольных ПК они не подлежат апгрейду и имеют при этом гораздо более долгий срок жизни. Поэтому в любом случае даже супер-пупер железо на момент выхода приставки (а точнее на момент начала ее разработки) уже через несколько лет успеет устареть и разработчикам придется выкручиваться.

              Я помню очень удивлялся, как на PS3 с ее жалкими 512 (кажется) Мб памяти летали игры, которые лагали на ПК с двумя гигами. Сколько ухищрений, оптимизаций и упрощений запихивали разработчики туда.
                +1
                «PS3 имеет 256 Мб XDR DRAM оперативной памяти производства Rambus.»

                Wikipedia
                  0
                  Там ещё столько же видеопамяти, а когда очень надо — что-то можно и в видеопамять сложить… прям как tor на машинках 30-летней давности, да…
              0
              Кеширование — вообще непростая штука и я чисто видел случаи, где пытаясь сделать лучше только убивали производительность именно тем, что «а давайте мы поставим кеширование».
              Ошибки такого рода больше свойственны начинающим, кто плохо понимает механику работы и кто не умеет пользоваться инструментами замера производительности.
                0
                There are only two hard things in Computer Science: cache invalidation and naming things.

                — Phil Karlton
                  0
                  Забыли про ошибку на единицу.
                0
                Сильно увеличил размер индекса в чей-то реализации хэш-таблицы. На малом кол-ве элементов хэш работал нормально, а на миллионах вставал колом.
                  +1

                  Исправил регулярное выражение, тем самым увеличив скорость комплиляции шаблонов Laravel в 40 раз :)

                    0
                    Тоже была подобная проблема с регулярками, только для проверки на то, что строка является ссылкой(целиком). Правда решил не исправлением большой регулярки, а проверкой каждой части ссылки своей регуляркой.
                      +1

                      Лет 5 назад столкнулся с интересной проблемой catastrophic backtracing в regular expressions (статья не моя, просто пример). Это когда визуально вроде всё нормально, и оно даже работает. Но с увеличением длины строки производительность гасится весьма нелинейно. Патч в 1-2 символа. С тех пор стал писать их куда внимательнее :)

                      0
                      Ускорил файловый кэш (на секундочку, сам кэш призван ускорять) в бородатом симфони 1.2 в 3-10 раз
                      0

                      Нужно просто разрабатывать на процессоре (пусть даже i9), но на пониженной частоте строго 2-3ghz, а запускать потребителю — на нормальных 4-5ghz. Вот и весь секрет скорости.

                        +1
                        Не разрабатывать, а отлаживать (тестировать). И желательно частоту вообще где-то в 1ГГц загнать, но заставить ПО использовать все доступные ядра, чтобы ещё и многопоточные ошибки проявлялись почаще.
                        0

                        Кто-то закомментировал кеширование при формировании списка товаров для отладки и забыл вернуть. Исправил это спустя год-полтора :-)

                          +3

                          Добавил в таблицу индекс, отчёт стал готовиться за 40 секунд вместо 40 минут.

                            –1

                            Только что заменил селект из сабселекта из сабселекта, сбор айдишников в цикле по выбранным в первом селекте записям, селект с условием where id in (17000 айдишников собранных в цикле) на селект с одним простым джойном, запрос стал выполняться 15 миллисекунд вместо 35 секунд. Оптимизация в 2000 раз, однако. Апгрейженный из-за перегрузки сервер теперь загружен процентов на 5%, хоть майнер запускай.

                            0
                            У нас один добрый человек выводил hp-bar'ы над юнитами, используя stencil bufffer, очищая его каждый раз перед отрисовкой нового bar'a. Простая замена на scirrors дала более чем двукратный прирост fps:)
                              +1
                              >> Добавил в таблицу индекс…

                              Удалил сложный индекс в таблице-снизил число блокировок и увеличил производительность БД
                                0
                                Одни из самых противных ошибок это те которые не приводят напрямую к вылету, но зато грузят процессор.
                                  0
                                  «Байка о котельщике» — понятно, что перевод, но все же её в России про Капицу рассказывают, а не про котельщика.
                                    +2
                                    Ой да ладно, эта байка пересказывается с абсолютно рандомными именами, фамилиями и местами событий.
                                      0
                                      Но вот про котельщика я ее не слышал, причем на историю про котельщика автор оригинала ссылается как на устойчивую единицу рассказа.
                                        +1
                                        А я про Капицу не слышал, зато слышал про инженера Уатта и автомеханика (это два разных варианта).
                                          0
                                          Ну, кто что читал. Но идея про Капицу мне для легенды больше нравится.
                                      0
                                      А я читал версию, что на атомной станции были проблемы. Мелкие, но неприятные, и это атомная станция. И как старенький профессор пометил карандашом проблемную трубу, оказался тысячу раз прав, и в конце рассказа объяснял, что взял деньги не за время, потраченное на решение проблемы, а за знание, что и где смотреть
                                      P.S. Читал давно, помню только общий сюжет и найти сходу не смог :(
                                        +2
                                        Ага, фрилансеры чинят атомную станцию.
                                          0
                                          А про забытые в первом контуре ключи и даже фуфайки, к сожалению, не шутки (с мертвого форума):
                                          — А откуда вообще мусор то? Ну поменяли задвижку ну и что?
                                          (случайно забытые гаечные ключи, болты-гайки, ставшие «лишними» я в расчет не беру).
                                          — Так дело в том, что чаще всего именно ключи, гайки и т.д. оттуда и выносятся. Это что, бывало вылавливали фуфайки на фильтрах РГК — почти вход в активную зону.
                                          — Не, это, конечно, мусор, но я думал что такой мусор (болты, ключи) исключен в принципе.Я понимаю что моя наивность не знает границ
                                      0
                                      Частый пробег по одному полю из массива структур тормозил. Вынесение значения этого поля из структур в отдельный массив решило вопрос. (оказалось за годы структуры разрослись и начались промахи кэша...)
                                        0
                                        Практически Flyweight pattern

                                      Only users with full accounts can post comments. Log in, please.