В общем, как часто и когда мне следует оптимизировать мой код?

13

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

Помните, что сказал Дональд Кнут : «Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла»

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

И что должно быть моим измерением оптимизации? Клещи? Частота кадров? Общее время?

Дэвид Басараб
источник

Ответы:

18

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

  • «Профилировщик человека», он же просто играет в игру ; это чувствует себя медленным или "заминкой" иногда? Заметили отрывистую анимацию? (Как разработчик, обратите внимание, что вы будете более чувствительны к некоторым видам проблем с производительностью и не будете обращать внимания на другие. Планируйте дополнительное тестирование соответствующим образом.)
  • Включите дисплей FPS , который является скользящим окном 5 секунд среднего FPS. Очень мало накладных расходов для расчета и отображения.
  • Включите панели профиля , которые представляют собой просто серию четырехугольников (цвета ROYGBIV), которые представляют различные части кадра (например, vblank, preframe, update, collision, render, postframe), используя простой таймер «секундомера» вокруг каждой части кода , Чтобы подчеркнуть то, что мы хотим, мы устанавливаем значение ширины полосы в один экран в качестве репрезентативного для целевого кадра 60 Гц, поэтому очень легко увидеть, например, что у вас 50% бюджета (только половина полосы) или 50% ( бар оборачивается и становится полутора барами). Также довольно легко определить, что обычно ест большую часть кадра: красный = рендер, желтый = обновление и т. Д.
  • Создайте специальную инструментальную сборку, которая вставляет код «секундомер» вокруг каждой функции. (Обратите внимание, что при этом вы можете сильно пострадать от производительности, dcache и icache, так что это определенно навязчиво. Но если вам не хватает соответствующего профилировщика выборки или приличной поддержки ЦП, это приемлемый вариант. Вы также можете быть умным о записи минимума данных о входе / выходе функции и перестроении calltraces позже.) Когда мы создавали наши, мы имитировали большую часть выходного формата gprof .
  • Лучше всего запустить профилировщик выборки ; VTune и CodeAnalyst доступны для x86 и x64, у вас есть различные среды моделирования или эмуляции, которые могут предоставить вам данные здесь.

(Есть забавная история из прошлогоднего GDC о графическом программисте, который сделал четыре своих снимка - счастливый, равнодушный, раздраженный и злой - и показал соответствующую картинку в углу внутренних сборок на основе частоты кадров. Создатели контента быстро научились не включать сложные шейдеры для всех своих объектов и сред: они бы разозлили программиста. Вот сила обратной связи.)

Обратите внимание, что вы также можете делать забавные вещи, такие как непрерывное построение графиков «полосок профиля», чтобы вы могли видеть шаблоны пиков («мы теряем кадр каждые 7 кадров») или тому подобное.

Чтобы ответить на ваш вопрос напрямую, хотя: по моему опыту, хотя это заманчиво (и часто полезно - я обычно чему-то учусь), переписать отдельные функции / модули для оптимизации количества инструкций или производительности icache или dcache, и нам действительно нужно сделать иногда, когда у нас возникает особенно неприятная проблема с производительностью, подавляющее большинство проблем с производительностью, с которыми мы регулярно сталкиваемся, сводятся к проектированию . Например:

  • Должны ли мы кэшировать в ОЗУ или перезагрузить с диска кадры анимации состояния «атаки» для игрока? Как насчет каждого врага? У нас нет оперативной памяти, чтобы сделать все это, но загрузка дисков стоит дорого! Вы можете видеть сцепление, если 5 или 6 различных врагов появляются одновременно! (Хорошо, а как насчет нереста?)
  • Выполняем ли мы один тип операции для всех частиц или все операции для одной частицы? (Это компромисс между icache и dcache, и ответ не всегда ясен.) Как насчет разделения всех частиц и сохранения позиций вместе (известная «структура массивов») против хранения всех данных частиц в одном месте (» массив структур ").

Вы слышите это до тех пор, пока оно не станет неприятным на любых курсах информатики университетского уровня, но: это действительно все о структурах данных и алгоритмах. Потратив некоторое время на алгоритм и проектирование потока данных, вы получите больше отдачи в целом. (Убедитесь, что вы прочитали отличные слайды « Подводные камни объектно-ориентированного программирования» от сотрудника Sony Developer Services для некоторого понимания здесь.) Это не «похоже» на оптимизацию; это в основном время, проведенное с доской или инструментом UML или созданием многих прототипов, вместо того, чтобы заставить текущий код работать быстрее. Но это, как правило, гораздо более стоящее.

И еще одна полезная эвристика: если вы близки к «ядру» вашего движка, возможно, стоит потратить некоторые дополнительные усилия и эксперименты на оптимизацию (например, векторизация этих умножений матриц!). Чем дальше от ядра, тем меньше вы должны беспокоиться об этом, если один из ваших инструментов профилирования не скажет вам иначе.

Leander
источник
6
  1. Используйте правильные структуры данных и алгоритмы заранее.
  2. Не оптимизируйте микро, пока не профилируете и точно не узнаете, где находятся ваши горячие точки.
  3. Не беспокойся о том, чтобы быть умным. Компилятор уже выполняет все маленькие хитрости, о которых вы думаете («О! Мне нужно умножить на четыре! Я сдвинусь влево на два!»)
  4. Обратите внимание на промахи кеша.
необычайно щедрый
источник
1
Полагаться на компилятор разумно только до определенного момента. Да, он выполнит некоторые оптимизации глазка, о которых вы даже не подумали (и не смог бы обойтись без сборки), но он не знает, что должен делать ваш алгоритм, поэтому он не может выполнять интеллектуальные оптимизации. Кроме того, вы будете удивлены тем, сколько циклов вы можете выиграть, внедрив критический код в ассемблер или встроенный код .... если вы знаете, что делаете. Компиляторы не так умны, как они задуманы, они не будут знать, что вы делаете, если вы не скажете им явно везде (например, неукоснительно используете «restrict»).
Кадж
1
И еще раз я должен прокомментировать, что если вы будете искать только горячие точки, вы пропустите много циклов, потому что вы не найдете никаких циклов с задержкой по всем направлениям (например, smartpointers .... где-либо разыменования, никогда не появляются как горячая точка, потому что фактически вся ваша программа является горячей точкой).
Кадж
1
Я согласен с обоими вашими соображениями, но большую часть этого я бы назвал «использовать правильные структуры данных и алгоритмы». Если вы везде используете умные указатели с пересчетом и проходите циклы подсчета, вы определенно выбрали неверную структуру данных.
Великолепно
5

Также помните, однако, «преждевременная пессимизация». Несмотря на то, что нет необходимости использовать хардкор для каждой строки кода, есть основания полагать, что вы действительно работаете над игрой, которая влияет на производительность в реальном времени.
Хотя все говорят вам, что нужно измерять и оптимизировать «горячие точки», эта техника не покажет вам производительность, которая теряется в скрытых местах. Например, если каждая операция «+» в вашем коде займет в два раза больше времени, чем нужно, она не будет отображаться как горячая точка, и, таким образом, вы никогда не будете ее оптимизировать или даже осознавать, однако, поскольку она используется везде место это может стоить вам много производительности. Вы были бы удивлены, сколько из этих циклов не было обнаружено. Так что знайте, что вы делаете.
Кроме того, я имею тенденцию регулярно регистрировать, чтобы понять, что там, и сколько времени осталось на кадр. Для меня время в кадре является наиболее логичным, поскольку оно говорит мне непосредственно, где я нахожусь с целями частоты кадров. Также попробуйте выяснить, где находятся пики и что их вызывает - я предпочитаю стабильную частоту кадров, а не высокую частоту кадров с пиками.

Кая
источник
Это кажется мне неправильным. Конечно, мой «+» может занимать вдвое больше времени при каждом вызове, но это действительно важно только в тесном цикле. Внутри замкнутого цикла изменение одного «+» может сделать на несколько порядков больше, чем изменение «+» вне цикла. Зачем думать о десятой доле микросекунды, когда можно сохранить миллисекунду?
Wilduck
1
Тогда вы не понимаете, что лежит в основе потери струйки. '+' (просто в качестве примера) вызывается сотни тысяч раз за кадр, а не только в виде замкнутых циклов. Если это теряет несколько циклов каждый раз, когда вы теряете много циклов по всем направлениям, но оно никогда не будет отображаться как горячая точка, поскольку вызовы равномерно распределяются по вашей базе кода / пути выполнения. То есть речь идет не о десятых долях микросекунды, а о тысячных долях этих десятых долей микросекунды, что составляет несколько миллисекунд. После перехода на низко висящие фрукты (плотные петли) я набирал миллисекунды таким образом не раз.
Кадж
Это как кран, который капает. Зачем беспокоиться о сохранении этой маленькой капли? - «Если ваш кран стекает со скоростью одна капля в секунду, вы можете ожидать, что будете тратить 2700 галлонов в год».
Кадж
О, я думаю, было неясно, что я имел в виду, когда оператор + был перегружен, так что это повлияло бы на все «+» в коде - вы действительно не захотите оптимизировать каждый «+» в коде. Плохой пример, я думаю… Я имел в виду пример «основной функциональности, которая вызывается повсюду, где реализация может быть медленнее, чем предполагалось, особенно когда она скрыта перегрузкой оператора или другими запутывающими конструкциями C ++».
Кадж
3

Когда игра готова к выпуску (финальная или бета-версия) или она заметно медленная, это, вероятно, лучшее время для профилирования вашего приложения. Конечно, вы всегда можете запустить профилировщик в любой момент; но да, преждевременная оптимизация - корень всего зла. Необоснованная оптимизация тоже; вам нужны реальные данные, чтобы показать, что какой-то код работает медленно, прежде чем пытаться «оптимизировать» его. Профилировщик делает это для вас.

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

Большая часть оптимизации игрового кода сводится к сокращению циклов процессора, необходимых для каждого кадра. Один из способов сделать это - просто оптимизировать каждую подпрограмму по мере ее написания и убедиться, что она выполняется максимально быстро. Тем не менее, есть распространенное высказывание, что 90% циклов ЦП расходуются на 10% кода. Это означает, что перенаправление всей вашей работы по оптимизации на эти узкие места будет иметь 10-кратный эффект для оптимизации всего равномерно. Итак, как вы определяете эти процедуры? Профилирование делает это легко.

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

Ricket
источник
В то время как низко висящий фрукт действительно имеет тенденцию составлять 10% кода, и его легко поймать при профилировании в конце, чистая работа с профилированием для этого заставит вас упустить подпрограммы, которые вызываются много, но имеют немного немного плохого кода каждый - они не будут отображаться в вашем профиле, но у них много циклов за вызов. Это действительно складывает.
Кадж
@Kaj, Хорошие профилировщики суммируют все сотни отдельных исполнений плохого алгоритма и показывают вам общее количество. Далее вы скажете: «А что, если у вас было 10 плохих методов и все они были вызваны на 1/10 частоты?» Если вы потратите все свое время на эти 10 методов, вам не хватит всех низко висящих фруктов, где вы получите гораздо большую отдачу за свой доллар.
Джон Макдональд
2

Я считаю полезным встроить профилирование. Даже если вы не активно оптимизируете, полезно иметь представление о том, что ограничивает вашу производительность в любой момент времени. Многие игры имеют своего рода наложенный HUD, который отображает простую графическую диаграмму (обычно просто цветную полосу), показывающую, как долго различные части игрового цикла занимают каждый кадр.

Было бы плохой идеей оставлять анализ производительности и оптимизацию на более поздней стадии. Если вы уже создали игру и у вас на 200% больше ресурсов процессора, но вы не можете найти это с помощью оптимизации, вы облажались.

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

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

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

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

JasonD
источник
Чтобы добавить в игровой профилировщик (с чем я полностью согласен), расширение игрового профилировщика для отображения нескольких столбцов (для нескольких кадров) помогает соотнести поведение игры с пиками и может помочь вам найти узкие места, которые не будут отображаться при среднем захвате. с профайлером.
Кадж
2

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

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

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

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

Джонатан Фишофф
источник
1

Для моего проекта я обычно применяю ОЧЕНЬ необходимые оптимизации в своем базовом движке. Например, мне всегда нравится реализовывать хорошую и надежную реализацию SIMD с использованием SSE2 и 3DNow! Это гарантирует, что моя математика с плавающей запятой совпадает с тем, где я хочу. Еще одна хорошая практика - использовать привычку оптимизации во время написания кода, а не возвращаться назад. В большинстве случаев эти маленькие практики занимают столько же времени, сколько и то, что вы программировали. Прежде чем кодировать функцию, убедитесь, что вы исследуете наиболее эффективный способ сделать это.

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

Krankzinnig
источник
0

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

Коммунистическая утка
источник
0

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

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

Брайан Денни
источник
Я бы сказал, что GPU так же вероятен, как и CPU. Действительно, в зависимости от того, насколько тесно связаны вещи, вполне возможно быть сильно ограниченным ЦП в половине кадра, а в сильной степени ГПУ - в другой половине. Глупое профилирование может даже показать использование менее чем на 100%. Убедитесь, что ваше профилирование достаточно мелкозернистое, чтобы показать это (но не настолько мелко, чтобы быть навязчивым!)
JasonD
0

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

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

Иногда это не код. Многие проблемы, с которыми я сталкивался в прошлом, были ориентированы на gpu (да, это было на iPhone). Проблемы с заполнением, слишком много вызовов отрисовки, недостаточно пакетная геометрия, неэффективные шейдеры ...

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

тетрада
источник
0

Для меня лучше всего следовать хорошо подготовленной модели данных. И оптимизация - перед главным шагом вперед. Я имею в виду, прежде чем начать реализацию чего-то большого нового. Другая причина для оптимизации заключается в том, что когда я теряю контроль над ресурсами, приложению требуется большая загрузка ЦП / ГП или памяти, и я не знаю почему :) или это слишком много.

samboush
источник