Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Оптимізація Центрального Процесора

Вимірювання продуктивності

Ми повинні знати, де є «вузькі місця», щоб знати, як прискорити нашу програму. Вузькі місця — це найповільніші частини програми, які обмежують швидкість прогресу всього. Зосередження на вузьких місцях дозволяє нам зосередити наші зусилля на оптимізації областей, які забезпечать нам найбільше покращення швидкості, замість того, щоб витрачати багато часу на оптимізацію функцій, які призведуть до невеликих покращень продуктивності.

Для ЦП найпростіший спосіб виявити вузькі місця – це використовувати профайлер.

Профайлери ЦП

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

Godot IDE має вбудований профайлер. Він не запускається щоразу, коли ви запускаєте проект: його потрібно запускати та зупиняти вручну. Це тому, що, як і більшість профайлерів, запис цих вимірювань часу може значно уповільнити ваш проект.

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

Результати профайлера Godot

Результати профайлера для одного з демонстраційних проектів.

Примітка

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

Час, витрачений на очікування різних вбудованих серверів, може не враховуватися в профайлерах. Це відома помилка.

Коли проект працює повільно, ви часто зможете побачите очевидну функцію, або процес, що займає набагато більше часу, ніж інші. Це ваше основне вузьке місце, і ви зазвичай зможете збільшити швидкість, оптимізуючи цю область.

Щоб отримати додаткові відомості про використання вбудованого профайлера Godot, див. Панель налагоджувача.

Зовнішні профайлери

Хоча профайлер Godot IDE дуже зручний і корисний, іноді вам потрібна більша потужність і можливість профілювання самого вихідного коду рушія Godot.

Для цього ви можете use a number of third-party C++ profilers.

Скріншот Callgrind

Приклад результатів від Callgrind, який є частиною Valgrind.

Зліва Callgrind перераховує відсоток часу у функції та її нащадків (включно), відсоток часу, проведеного в самій функції, за винятком дочірніх функцій (Self), кількість викликів функції, назву функції, а також файл, або модуль.

У цьому прикладі ми бачимо, що майже весь час витрачається на функцію Main::iteration(). Це основна функція у вихідному коді Godot, яка викликається неодноразово. Це спричиняє малювання кадрів, симуляцію фізичних тактів, а також оновлення вузлів і сценаріїв. Значна частина часу витрачається на функції візуалізації полотна (66%), оскільки в цьому прикладі використовується 2D тест. Нижче ми бачимо, що майже 50% часу витрачається поза кодом Godot в libglapi та i965_dri (драйвер графіки). Це говорить нам про те, що значна частка процесорного часу витрачається на графічний драйвер.

Насправді це чудовий приклад, тому що в ідеальному світі лише дуже мала частина часу витрачалася б на графічний драйвер. Це вказує на те, що існує проблема із занадто великою кількістю зв’язку та роботи, що виконується в графічному API. Це специфічне профілювання призвело до розробки 2D пакетної обробки, яка значно прискорює 2D-рендерінг, зменшуючи вузькі місця в цій області.

Функції визначення часу вручну

Ще одна зручна техніка, особливо коли ви визначили вузьке місце за допомогою профайлера, полягає в тому, щоб вручну визначити час роботи функції або області коду, що тестується. Специфіка залежить від мови, але в GDScript ви повинні зробити наступне:

var time_start = Time.get_ticks_usec()

# Your function you want to time
update_enemies()

var time_end = Time.get_ticks_usec()
print("update_enemies() took %d microseconds" % (time_end - time_start))

Під час ручного визначення часу зазвичай доцільно запускати функцію багато разів (1000 і більше), а не лише один раз (якщо це не дуже повільна функція). Причина цього полягає в тому, що таймери часто мають обмежену точність. Крім того, процесори плануватимуть процеси випадковим чином. Тому середнє значення за серією прогонів є більш точним, ніж одне вимірювання.

Намагаючись оптимізувати функції, обов'язково проводьте повторне профілювання або замір часу виконання. Так ви зрозумієте, чи працює оптимізація (чи ні).

Кеші

Кеш-пам'ять процесора - це ще одна річ, про яку слід пам'ятати, особливо коли порівнюються результати часу виконання двох різних версій функції. Результати можуть сильно залежати від того, чи є дані в кеші процесора чи ні. Процесори не завантажують дані безпосередньо з системної оперативної пам'яті, навіть якщо вона величезна порівняно з кешем процесора (кілька гігабайт замість кількох мегабайт). Це пов'язано з тим, що системна оперативна пам'ять дуже повільна для доступу. Замість цього процесори завантажують дані з меншого, швидшого банку пам'яті, який називається кеш. Завантаження даних з кешу відбувається дуже швидко, але кожного разу, коли ви намагаєтеся завантажити адресу пам'яті, яка не зберігається в кеші, кеш повинен звертатися до основної пам'яті і повільно завантажувати дані. Ця затримка може призвести до тривалого простою процесора і називається "промахом кешу".

Це означає, що при першому запуску функції вона може працювати повільно, оскільки дані не знаходяться в кеші процесора. Вдруге і пізніше вона може працювати набагато швидше, оскільки дані знаходяться в кеші. Через це завжди використовуйте середні значення при визначенні часу і пам'ятайте про вплив кешу.

Розуміння кешування також має вирішальне значення для оптимізації процесора. Якщо у вас є алгоритм (підпрограма), який завантажує невеликі біти даних з випадково розподілених ділянок оперативної пам'яті, це може призвести до великої кількості промахів кешу, і процесор більшу частину часу буде чекати на дані замість того, щоб виконувати будь-яку роботу. Натомість, якщо ви можете зробити доступ до даних локалізованим, або, ще краще, звертатися до пам'яті лінійно (як до безперервного списку), то кеш працюватиме оптимально, а процесор зможе працювати якомога швидше.

Godot зазвичай піклується про такі незначні деталі за вас. Наприклад, серверні API гарантують, що дані оптимізовані для кешування вже для таких речей, як візуалізація та фізика. Тим не менш, ви повинні особливо пам’ятати про кешування під час написання GDExtensions.

Мови

Godot підтримує кілька різних мов, і варто пам'ятати, що тут є певні компроміси. Деякі мови призначені для простоти використання за рахунок швидкості, а інші - швидші, але з ними складніше працювати.

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

Скрипт

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

C#

C# популярний і має першокласну підтримку в Godot. Він пропонує гарний компроміс між швидкістю та простотою використання. Однак остерігайтеся можливих пауз та витоків під час збирання сміття, які можуть виникати під час гри. Поширеним підходом до вирішення проблем зі збиранням сміття є використання пулу об'єктів, що виходить за рамки цього посібника.

Інші мови

Треті сторони надають підтримку для кількох інших мов, зокрема Rust.

C++

Godot написаний мовою C++. Використання C++ зазвичай призводить до найшвидшого коду. Однак на практичному рівні його найважче розгорнути на машинах кінцевих користувачів на різних платформах. Варіанти використання C++ включають GDExtensions і custom modules.

Потоки

Коли ви робите багато обчислень, подумайте про використання потоків, які можуть виконуватися паралельно один з одним. Сучасні процесори мають кілька ядер, кожне з яких здатне виконувати обмежений обсяг роботи. Розподіливши роботу на декілька потоків, ви зможете досягти максимальної ефективності процесора.

Недоліком ниток є те, що ви повинні бути неймовірно обережними. Оскільки кожне ядро ЦП працює незалежно, вони можуть спробувати отримати доступ до однієї пам’яті одночасно. Один потік може зчитувати змінну, а інший записує: це називається змаганням. Перш ніж використовувати потоки, переконайтеся, що ви розумієте небезпеку та те, як спробувати запобігти таким умовам гонки. Потоки можуть значно ускладнити налагодження.

Для отримання додаткової інформації про потоки див. Використання кількох потоків.

Дерево Сцен

Незважаючи на те, що вузли є неймовірно потужною та універсальною концепцією, майте на увазі, що кожен вузол має свою вартість. Вбудовані функції, такі як _process() і _physics_process() поширюються по дереву. Таке обслуговування може знизити продуктивність, якщо у вас дуже велика кількість вузлів (скільки саме залежить від цільової платформи та може варіюватися від тисяч до десятків тисяч, щоб переконатися, що ви профілюєте продуктивність на всіх цільових платформах під час розробки).

Кожен вузол обробляється окремо у візуалізаторі Godot. Тому менша кількість вузлів з більшою кількістю даних у кожному з них може призвести до кращої продуктивності.

Однією з особливостей SceneTree є те, що іноді ви можете отримати набагато кращу продуктивність, видаливши вузли з Дерева Сцен, а не призупинивши, чи приховавши, їх. Вам не обов'язково видаляти відокремлений вузол. Ви можете, наприклад, зберегти посилання на вузол, вилучити його з дерева сцен за допомогою Node.remove_child(node), а потім знову приєднати його за допомогою Node.add_child(node). Це може бути дуже корисно, наприклад, для додавання та видалення областей у грі.

Ви можете взагалі відмовитися від Дерева Сцен, скориставшись серверними API. Докладнішу інформацію наведено у Оптимізація за допомогою серверів.

Фізика

У деяких ситуаціях вузьким місцем може стати фізика. Особливо це стосується складних світів і великої кількості фізичних об'єктів.

Ось кілька прийомів для прискорення фізики:

  • Спробуйте використовувати спрощені версії геометрії для форм зіткнення. Часто це буде непомітно для кінцевих користувачів, але може значно підвищити продуктивність.

  • Спробуйте видаляти об'єкти з фізикою, коли вони знаходяться поза зоною видимості/за межами поточної області, або використовувати фізичні об'єкти повторно (наприклад, ви дозволите 8 монстрів на область, і використаєте їх повторно).

Іншим важливим аспектом фізики є частота оновлення фізики. У деяких іграх ви можете значно зменшити частоту оновлення фізики, і замість того, щоб, наприклад, оновлювати фізику 60 разів на секунду, ви можете оновлювати її лише 30, або навіть 20, разів на секунду. Це може значно зменшити навантаження на процесор.

Недоліком зміни частоти оновлення фізики є те, що ви можете отримати ривкові рухи, або тремтіння, коли частота оновлення фізики не відповідає частоті візуалізації кадрів на секунду. Крім того, зменшення частоти оновлення фізики може призвести до збільшення затримки введення. У більшості ігор, де гравець рухається у реальному часі, рекомендується дотримуватися частоти оновлення фізики за замовчуванням (60 Гц).

Рішенням проблеми тремтіння є використання інтерполяції з фіксованим часовим кроком, яка передбачає згладжування позицій та обертань відрендера протягом кількох кадрів для відповідності фізиці. Godot має вбудовану фізичну інтерполяцію, про яку ви можете прочитати here. З точки зору продуктивності, інтерполяція є дуже дешевою операцією порівняно з виконанням фізичного тику. Вона на порядок швидша, тому це може бути значним виграшем у продуктивності, а також зменшуючи тремтіння.