Прагматичный Подход К Продуктивности

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

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

1. Время программиста — ограниченный ресурс Если вы пишете большую программу, то некоторые части кода не будут работать с максимально возможной теоретической скоростью.

Извините, я перефразирую.

Если вы пишете большую программу, ни одна часть кода не будет работать с максимально возможной теоретической скоростью.

Да, я думаю, стоит осознать, что любую строчку вашего кода можно изменить, чтобы она работала немного быстрее.

Написание быстрой программы не означает всегда достижения максимальной производительности.

Вам нужна приемлемая производительность там, где это важно.

Если вы потратите три недели на оптимизацию фрагмента кода, который вызывается раз в год, то вы могли бы потратить эти три рабочих недели на что-то более значимое.

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

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

2. Не стоит недооценивать силу простоты Простые решения легче реализовать, чем сложные.

Но это лишь верхушка айсберга.

Настоящие преимущества простых решений приходят со временем.

Простое решение легче понять, легче отлаживать, легче реализовать, легче портировать, легче профилировать, легче оптимизировать, легче распараллеливать и легче заменять.

Со временем все эти преимущества накапливаются.

Использование простого решения может сэкономить время, даже если оно медленнее, чем сложное решение, потому что.

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

Части, которые действительно имеют значение.

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

Например, когда сложное решение значительно быстрее простого (или в 2 раза, или что-то еще) и когда оно находится в системе, где это действительно важно (что потребляет значительный процент процессорного времени).

Конечно, простота видна только конкретному человеку.

Я думаю, что массивы просты.

Я считаю, что типы данных POD просты.

Я думаю, что капли — это просто.

Я не думаю, что классы с 12 уровнями наследования — это непросто.

Я не думаю, что занятия, основанные на 8 различных правилах, — это непросто.

Я не думаю, что геометрическая алгебра — это непросто.

3. Воспользуйтесь возможностью спроектировать систему.

Некоторые люди считают, что отказ от «преждевременной оптимизации» означает проектирование системы, вообще не обращая внимания на производительность.

Вам нужно сначала что-то собрать, а потом уже исправлять, когда будете делать «оптимизацию».

Я совершенно не согласен с таким подходом.

И не потому, что люблю продуктивность ради продуктивности, а из чисто прагматических интересов.

Когда вы проектируете систему, у вас в голове есть полная картина того, как различные части будут сочетаться друг с другом, какие требования к ним будут предъявляться и как часто будут вызываться те или иные функции.

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

И наоборот, если вы строите систему без учета производительности, а «исправлениями» занимаетесь позже, все может оказаться гораздо сложнее.

Если вам необходимо реорганизовать базовые структуры данных или ввести поддержку многопоточности, вам может потребоваться переписать всю систему с нуля.

Только система уже будет в продакшене, а вы будете ограничены опубликованным API и подключениями к другим системам.

Кроме того, вы не можете позволить себе сломать проекты, в которых используется система.

А поскольку к тому времени пройдет несколько месяцев с тех пор, как вы (или кто-то другой) написал код, вам придется начать вспоминать и понимать все мысли, которые в него вошли.

А все мелкие исправления и дополнения, которые были сделаны в процессе написания, скорее всего, будут потеряны.

И вы начнете отладку с обновленным пакетом ошибок.

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

Просто потому, что это требует меньше усилий, чем последующее исправление.

Конечно, нужно быть благоразумным.

Улучшения производительности легче реализовать на начальном этапе, но мы не можем точно знать, какое влияние они окажут на систему в целом.

Позже, после профилирования, потребуются больше усилий, но мы будем гораздо лучше знать, на чем сосредоточить свое внимание.

В общем, как и во всем в жизни, важно сохранять баланс.

Когда я проектирую систему, я делаю приблизительный набросок того, как часто в единицу времени вызываются участки кода, и формулирую требования к проектированию:

  • 1-10. Производительность не имеет значения.

    Делать что-нибудь

  • 100. Убедитесь, что это На) , ориентирован на данные и может быть кэширован
  • 1000. Используйте многопоточность
  • 10000. Подумайте хорошенько, что вы здесь делаете.

Также есть несколько рекомендаций, которым я стараюсь следовать при написании новых систем:
  • Храните статические данные в постоянных целых блоках памяти.

  • Складывать динамические данные в соседние ячейки памяти.

  • ЭКоном памяти
  • Массивы лучше сложных структур данных
  • Доступ к памяти линейный (упрощает кэширование)
  • Убедитесь, что функции выполняются за время O(n)
  • Избегайте обновлений «ничего не делать» — лучше следите за активными объектами.

  • Если система работает со многими объектами, обеспечьте параллельный доступ к данным
Сегодня я написал множество систем в этом стиле, и мне не нужно прилагать никаких усилий, чтобы следовать этим рекомендациям.

И я обнаружил, что, следуя им, я получаю приличную базовую производительность.

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

Конечно, не всегда возможно выполнить все рекомендации.

Например, некоторые алгоритмы на самом деле требуют больше времени, чем O(n).

Но я точно знаю, что если я отступлю от этих рекомендаций, мне нужно притормозить и задуматься, не наношу ли я вред своей продуктивности.

4. Используйте нисходящее профилирование, чтобы найти узкие места Независимо от того, насколько хороша ваша первоначальная архитектура, ваш код будет тормозить в самых неожиданных местах.

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

Это означает, что в вашем коде есть ошибка.

Некоторые ошибки не приводят к сбою системы, а просто негативно влияют на производительность.

И это будут ошибки, о которых вы в принципе не могли знать.

Чтобы понять, на что ваша программа на самом деле тратит время, профилировщик сверху вниз является бесценным инструментом.

Мы явно определяем части нашего кода, которые профилируем, и передаем текущие данные по сети внешнему инструменту, который может визуализировать их различными способами:

Прагматичный подход к продуктивности

Скриншот (старого) профилировщика BitSquid Профилирование сверху вниз подскажет вам, на чем сосредоточить усилия по оптимизации.

Вы тратите 60% своего времени на анимацию и 0,5% времени на интерфейс? Сделайте анимацию, будет работать, но интерфейс ни копейки не стоит. Профилируя сверху вниз, вы можете сужать и сужать сегменты профилируемого кода, пока не дойдете до проблемы с производительностью, на которую время действительно тратится впустую.

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

5. Используйте восходящее профилирование, чтобы найти цели оптимизации низкого уровня.

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

Но у восходящего профилирования есть варианты использования.

Он хорош для поиска горячих точек — функций, которые вызываются из разных частей программы и которые можно пропустить при профилировании сверху вниз.

Эти «горячие точки» могут стать хорошей целью для низкоуровневой оптимизации каждой инструкции.

Либо их наличие свидетельствует о том, что что-то было сделано неправильно.

Например, если функция strcmp() отображается как горячая точка, значит ваша программа ведет себя очень плохо и ее нужно срочно поставить в угол и лишить ее сладостей.

Распространенной горячей точкой в нашем коде является lua_Vexecute().

Что неудивительно.

Это основная функция виртуальной машины Lua, большого коммутатора, на котором выполняется большая часть кода Lua. Но это говорит нам о том, что низкоуровневая оптимизация этой функции для конкретной платформы может обеспечить значительный выигрыш в производительности.

6. Избегайте синтетических тестов Я не провожу много синтетических тестов, таких как запуск кода в цикле 10 000 раз и измерение времени выполнения.

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

В противном случае я не могу быть уверен, что не оптимизирую данные, которые не встречаются в реальной жизни.

Тестирование 500 экземпляров одного и того же объекта, воспроизводящих одну и ту же анимацию, будет отличаться от тестирования одной и той же локации, но с 50 разными объектами, имеющими разную анимацию.

Модели доступа к данным будут совершенно другими.

Оптимизация, затрагивающая только один случай, не будет иметь значения в другом.

7. Оптимизация — это садоводство Программисты оптимизируют движок.

Сценаристы впихивают в него всякое другое.

Так было, есть и будет. И это хорошо.

Оптимизация — это не какой-то изолированный процесс, происходящий в строго ограниченный период времени.

Это часть целого цикла: разработка, внедрение и развитие.

Оптимизация — это постоянный диалог программистов и сценаристов о том, что должно быть в движке.

Повышать урожайность — это то же самое, что ухаживать за садом: видеть, что все в порядке, пропалывать сорняки, придумывать, как сделать жизнь растений лучше.

Задача сценаристов — уронить двигатель.

И задача программистов — поднять его обратно, гораздо более мощное.

И в процессе этого противостояния наступает момент, когда игра проявляется наиболее ярко.




Переведено несвязанный .

Оригинал Прагматичный подход к производительности Всех с наступающим годом! Пишите много хороших и быстрых программ! Теги: #оптимизация кода #разработка игр #профилирование #дизайн и рефакторинг

Вместе с данным постом часто просматривают: