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

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

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

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

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

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

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

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

../../_images/godot_profiler.png
Результати профайлера Godot

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

Примітка

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

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

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

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

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

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

Для цього можна використовувати кілька сторонніх профайлерів, включаючи Valgrind, VerySleepy, HotSpot, Visual Studio і Intel VTune.

Примітка

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

Скріншот Callgrind

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

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

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

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

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

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

var time_start = OS.get_ticks_usec()

# Your function you want to time
update_enemies()

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

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

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

Кеші

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

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

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

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

Мови

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

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

Скрипт

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

C#

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

Інші мови

Сторонні розробники надають підтримку декількох інших мов, зокрема Rust та Javascript.

C++

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

Потоки

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

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

Потоки також можуть значно ускладнювати налагодження. Налагоджувач GDScript поки що не підтримує встановлення точок зупинки у потоках.

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

Дерево Сцен

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

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

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

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

Фізика

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

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

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

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

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

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

Вирішенням проблеми тремтіння є використання інтерполяції з фіксованим кроком, яка передбачає згладжування позицій та обертань зображуваного кадру протягом декількох кадрів, щоб відповідати фізиці. Ви можете реалізувати її самостійно або використати сторонній аддон<https://github.com/lawnjelly/smoothing-addon> __. З точки зору продуктивності, інтерполяція є дуже дешевою операцією порівняно зі збільшенням частоти оновлення фізики. Вона на порядки швидша, тож це може дати значний виграш у продуктивності, а також зменшити тремтіння.