Godot 4. SceneTree root access speed test
Какой самый быстрый способ доступа к общему корню дерева сцен? Давайте потестируем...
Не самое популярное место для тем по Godot, но видос снимать лень, а поделиться инфой хочется. Тем более это такой олдскул, встречающийся, например, на офф. сайте php или на разных форумах, где в сообщениях десятилетней+ давности люди тестили скорость тех или иных команд и именно так определялось, чем лучше пользоваться.
В общем, задался я вопросом доступа к глобальному корню, начал тестить 4 известных мне способа это сделать и получил неожиданный результат. Выполним сначала прогон с вызовами всех четырёх вариантов, закончим одиночными. Для теста использовалась пустая 2D сцена с кнопкой и скриптом на корне сцены, выглядит вот так.
Структура сцены, показан сигнал на кнопке
Конечно, такой цикл следует оборачивать в функцию и вызывать её, а не тупо копипастить весь цикл, но... Я так и сделал и это замедлило общую скорость в 2 раза! Хотя соотношение результатов не изменилось, поэтому вот финальный вариант нашего тестового скрипта.
Как бы вы написали такой код?
Строка №12 написана с нарушение стайлгайда, там не рекомендуется использовать однострочную запись, но оказалось, что так цикл работает незначительно быстрее...
После первых прогонов выяснилось, что фукция, вызываемая первой, почти всегда работает медленнее, хотя изредка бывает так, что замедления не происходит. Поэтому пришлось прогнать в разных вариантах порядка. Тут как раз помогла обёртка в лямбду.
Не знаю, как бы я выкручивался без функций первого класса, добавленных в Godot 4.
Количество шагов цикла выбрано в 100 000, т.к. это достаточно, чтобы результаты были заметными, с учётом замедления от лямбда функций. При большем шаге разлёт в скорости каждого из вариантов сглаживается, эффект замедления первого test становится незаметен, а это нам не нужно. В самом конце (на скриншоте не видно) я повторил порядок 1,2,3,4, чтобы сравнить его с самой первой группой. Прогнав несколько раз, стало понятно, что во всех вызовах, кроме самого первого, порядок вызовов не влияет на результат. Отсюда, достаточно вызвать 3 одинаковых группы и отбросить результаты в первой четвёрке.
Финальный вариант группового теста
Результаты:
1 место - f1 = 0.032 - 0.037
2 место = 3 место - f3 и f4 = 0.033 - 0.050
4 место - f2 = 0.038 - 0.063
Стабильно хуже всех работает get_tree().root. Это прямой доступ к полю root объекта SceneTree, возвращаемого методом get_tree(). Видимо из-за итерации по объекту, которой, видимо, не происходит в таком объёме при других вариантах. Между остальными тремя различие ситуативно, поэтому я бы просто везде использовал $/root.
Одиночный тест я решил не проводить из-за всё того же замедления первого вызова. Провёл сдвоенный как на скриншоте и так для каждого из четырёх.
f1 = 0.034
f2 = 0.040
f3 = 0.035
f4 = 0.035
В среднем, f1 и здесь оказался быстрее. Спорный результат у f3 и f4. Понятно, что одно является сокращённой записью (не люблю слово синтаксический сахар) другого. Повторение в строенном варианте тоже ничего не дело. Вроде как действительно различия нет. Но, есть ещё один вариант тестирования, приближенный к реальным кейсам, т.к. связан он с записью результата любого из четырёх методов в переменную и доступа к этой переменной.
Изменён код на строке 13, и в функции, принимающей сигнал кнопки.
Результат ожидаемо одинаковый в пределах погрешности измерений и случайного разброса: от 0.004 до 0.011, что кратно быстрее доступа к корню через любой из четырёх методов. Отсюда можно сделать вывод, что в скриптах Godot 4, где нужен доступ к глобальному корню, стоит создать переменную с помощью синтаксиса вроде "@onready var root = get_tree().get_root()", т.к. такой код будет наиболее быстрым, и использовать уже её. Применение $/root по месту удобно и хорошо читаемо, но работать будет незначительно медленнее. Насколько это будет заметно в проекте? Зависит от частоты вызовов, но скорее всего незаметно. Однако, задача этой статьи - найти наиболее быстрый способ доступа и она выполнена, это вариант f1 записанный в переменную.