Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Загальні поради щодо оптимізації

Вступ

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

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

Для досягнення найкращих результатів у нас є два підходи:

  • Робити швидше.

  • Робити розумніше.

І бажано використовувати їх поєднання.

Дим і дзеркала

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

Природа повільності

Для стороннього спостерігача проблеми продуктивності часто змішуються разом. Але насправді існує кілька різних типів проблем з продуктивністю:

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

  • Переривчастий процес, який викликає "сплески" повільності, що призводить до зупинок.

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

Кожен з них дратує користувача, але по-різному.

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

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

Існує кілька методів вимірювання продуктивності, зокрема:

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

Обмеження

Профайлери ЦП часто є основним методом вимірювання продуктивності. Однак вони не завжди розповідають всю історію.

  • Вузькі місця часто виникають на графічному процесорі, "в результаті" інструкцій ЦП.

  • Скачки можуть виникати в процесах операційної системи (за межами Godot) "в результаті" інструкцій, що використовуються в Godot (наприклад, динамічне виділення пам'яті).

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

  • Можливо, вам доведеться вирішити проблеми з продуктивністю, які виникають на обладнанні, до якого ви не маєте доступу.

Внаслідок цих обмежень вам часто буде доводитися гратися в детектива, щоб з’ясувати, де слабкі місця.

Робота детектива

Детективна робота — це важливий навик для розробників (як з точки зору продуктивності, так і з точки зору виправлення помилок). Вона може включати перевірку гіпотез і двійковий пошук.

Перевірка гіпотези

Скажімо, наприклад, що ви вважаєте, що вашу гру сповільнюють спрайти. Ви можете перевірити цю гіпотезу:

  • Заміряючи продуктивність, при збільшенні кількості спрайтів, або зменшенні.

Це може привести до наступної гіпотези: чи впливає розмір спрайту на падіння продуктивності?

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

Профайлери

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

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

For more info about using Godot's built-in profiler, see The Profiler.

Принципи

Дональд Кнут сказав:

Програмісти витрачають величезну кількість часу на роздуми, або виправлення, швидкодії некритичних частин своїх програм, і ці спроби підвищення ефективності насправді мають сильний негативний вплив, якщо розглядати налагодження та обслуговування. Треба забути про невелику ефективність, скажімо, приблизно в 97% випадків: передчасна оптимізація — корінь усього зла. Але ми не повинні втрачати свої можливості в цих критичних 3%.

Ці повідомлення дуже важливі:

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

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

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

Одним з аспектів цитати, що вводить в оману, є те, що люди, як правило, зосереджуються на підцитаті "передчасна оптимізація — корінь усього зла". Хоча передчасна оптимізація (за визначенням) і небажана, але продуктивне програмне забезпечення є результатом продуктивного проектування.

Ефективний проект

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

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

Проект з кількох спроб

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

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

Процес оптимізації

Припустимо, що ми маємо розумний проект і беремо уроки в Кнута, нашим першим кроком в оптимізації має бути визначення найбільших вузьких місць – найповільніших функцій.

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

Процес такий:

  1. Профайл/Визначте вузьке місце.

  2. Оптимізуйте вузьке місце.

  3. Поверніться до кроку 1.

Оптимізація вузьких місць

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

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

Коли алгоритми та дані будуть налагоджені, ви зможете вносити невеликі зміни, які покращують продуктивність. Наприклад, ви можете перемістити деякі обчислення за межі циклів, або перетворити вкладені цикли for у невкладені. (Це має бути можливим, якщо ви заздалегідь знаєте ширину, або висоту, 2D-масиву.)

Завжди перевіряйте час/вузькі місця після кожної зміни. Деякі зміни призведуть до збільшення швидкості, інші можуть мати негативний вплив. Іноді невеликий позитивний ефект буде переважено недоліками більш складного коду, і ви можете відмовитися від такої оптимізації.

Додаток

Математика вузьких місць

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

A: 9 ms
Everything else: 1 ms
Total frame time: 10 ms
A: 1 ms
Everything else: 1ms
Total frame time: 2 ms

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

Однак, якщо щось інше працює повільно і також заважає вашому проекту, те саме покращення не сильно зможе позначитися на продуктивності:

A: 9 ms
Everything else: 50 ms
Total frame time: 59 ms
A: 1 ms
Everything else: 50 ms
Total frame time: 51 ms

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

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

CPU: 9 ms
GPU: 50 ms
Total frame time: 50 ms
CPU: 1 ms
GPU: 50 ms
Total frame time: 50 ms

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