The Profiler

Запускаешь игру в Godot и играешь. Игра увлекательна, она постепенно набирает функциональность, и кажется, что релиз уже близок.

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

Можно попробовать всё оптимизировать и запускать игру снова и снова, но можно подойти к этому умнее и сузить круг возможностей. Воспользуйтесь профайлером Godot.

Обзор профилировщика

Вы можете открыть профилировщик, открыв панель Debugger и щелкнув вкладку Profiler.

../../../_images/profiler.png

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

Чтобы начать профилирование, запустите игру, а затем вернитесь в редактор. Нажмите кнопку Start в левом верхнем углу вкладки Profiler. Вы также можете установить флажок Autostart, чтобы профилировщик автоматически запускался при следующем запуске проекта. Обратите внимание, что состояние флажка Autostart не сохраняется между сеансами редактора.

Примечание

Профилировщик в настоящее время не поддерживает скрипты C#. Скрипты C# можно профилировать с помощью JetBrains Rider и JetBrains dotTrace с плагином поддержки Godot.

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

Измеренные данные

Интерфейс профилировщика разделён на две части: слева — список функций, справа — график производительности.

Основными измерениями являются время кадра, время выполнения физических задач, время простоя и время выполнения физических задач.

  • frame time — это время, которое требуется Godot для выполнения всей логики для всего изображения, от физики до рендеринга.

  • Physics frame — это время, которое Godot выделяет между обновлениями физики. В идеальном сценарии время кадра равно выбранному вами: 16,66 миллисекунды по умолчанию, что соответствует 60 FPS. Это система отсчёта, которую можно использовать для всего остального.

  • Idle time — это время, которое потребовалось Godot для обновления логики, отличной от физики, например, кода, находящегося в _process, или таймеров и камер, настроенных на обновление при Idle.

  • Physics time — это время, которое потребовалось Godot для обновления физических задач, таких как _physics_process и встроенных узлов, настроенных на обновление Physics.

Примечание

Frame Time включает время рендеринга. Допустим, вы обнаружили в игре загадочный всплеск задержки, но при этом физика и скрипты работают быстро. Задержка может быть связана с появлением частиц или визуальных эффектов!

По умолчанию Godot устанавливает галочки для параметров Frame Time и Physics Time. Это позволяет оценить длительность каждого кадра относительно выделенного желаемого FPS для физики. Вы можете включать и выключать функции, устанавливая флажки слева. Другие функции появляются по мере продвижения по списку, например, Physics 2D, Physics, и Audio, прежде чем перейти к функциям скрипта, где отображается ваш код.

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

Область измерения и окна измерений

Вы можете изменить тип измерения, используя раскрывающееся меню Measure. По умолчанию оно начинается со значения Frame Time и отображает время, необходимое для прохождения кадра в миллисекундах. Среднее время — это среднее время, которое потребовалось любой функции при многократном вызове. Например, функция, выполнение которой заняло 0,05 миллисекунды пять раз, должна дать среднее время 0,01 миллисекунды.

Если точное количество миллисекунд не важно и вы хотите видеть соотношение времени к остальной части кадра, используйте процентные значения. Frame % рассчитывается относительно Frame Time, а Physics % — относительно Physics Time.

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

../../../_images/split_curve.png

get_neighbors, find_nearest_neighbor и move_subject заняли много времени. Можно подумать, что это связано с медлительностью всех трёх функций.

Но если изменить на Self, Godot измеряет время, проведенное в теле функции, не принимая во внимание вызовы функций, которые он сам сделал.

../../../_images/self_curve.png

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

Отладка медленного кода с помощью профайлера

Поиск медленного кода с помощью профилировщика сводится к запуску игры и наблюдению за графиком производительности во время отрисовки. Если во время кадра происходит недопустимый скачок производительности, можно нажать на график, чтобы приостановить игру и сузить область поиска _Frame #_ до начала скачка. Возможно, вам придётся переключаться между кадрами и функциями, чтобы найти первопричину.

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

Измерение вручную в микросекундах

Если ваша функция сложная, может быть сложно определить, какая её часть нуждается в оптимизации. Дело в ваших расчётах или в том, как вы обращаетесь к другим данным для выполнения этих расчётов? В цикле for? В операторах if?

Вы можете сузить диапазон измерений, вручную подсчитывая тики во время выполнения кода с использованием временных функций. Эти две функции являются частью объекта класса Time. Это get_ticks_msec и get_ticks_usec. Первая измеряет в миллисекундах (1,000 в секунду), а вторая — в микросекундах (1,000,000 в секунду).

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

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

# Measuring the time it takes for worker_function() to run
var start = Time.get_ticks_usec()
worker_function()
var end = Time.get_ticks_usec()
var worker_time = (end-start)/1000000.0

# Measuring the time spent running a calculation over each element of an array
start = Time.get_ticks_usec()
for calc in calculations:
    result = pow(2, calc.power) * calc.product
end = Time.get_ticks_usec()
var loop_time = (end-start)/1000000.0

print("Worker time: %s\nLoop time: %s" % [worker_time, loop_time])

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

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