Як правило, як часто і коли я повинен оптимізувати свій код?


13

У «звичайному» бізнес-програмуванні крок оптимізації часто залишається, поки дійсно не потрібен. Значить, не слід оптимізувати, поки це дійсно не потрібно.

Пам’ятайте, що сказав Дональд Кнут : «Ми повинні забути про малу ефективність, скажімо, про 97% часу: передчасна оптимізація - корінь усього зла»

Коли саме час оптимізувати, щоб переконатися, що я не витрачаю зусиль. Чи потрібно це робити на рівні методу? Рівень класу? Рівень модуля?

Крім того, що повинен міряти оптимізацію? Кліщі? Частота кадрів? Загальний час?

Відповіді:


18

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

  • "Профілер людини", він просто грає в гру ; відчуваєш себе повільно чи «зачепиш» час від часу? Помічаєте ривкові анімації? (Зауважте, як розробник, ви будете більш чутливі до деяких видів продуктивності та не звертаєте уваги на інші. Плануйте додаткові тестування відповідно.)
  • Увімкніть FPS-дисплей , який є середньою секундою FPS з розсувним вікном. Дуже невеликі накладні витрати для обчислення та відображення.
  • Увімкніть смуги профілів , що представляють собою лише серію квадроциклів (кольори ROYGBIV), які представляють різні частини кадру (наприклад, vblank, попередній кадр, оновлення, зіткнення, візуалізація, пост кадр), використовуючи простий таймер "секундомір" навколо кожного розділу коду . Для того, щоб підкреслити те, що ми хочемо, ми встановили, що ширина екрана, що стоїть на барі, повинна бути репрезентативною для цільового кадру 60 Гц, тож насправді легко зрозуміти, чи є ви, наприклад, на 50% за бюджетом (лише півбар) або на 50% більше ( брусок загортається і стає півтора бруска). Також досить легко сказати, що зазвичай їсть більшу частину кадру: червоний = візуалізація, жовтий = оновлення тощо ...
  • Створіть спеціальну інструментальну збірку, яка вставляє «секундомір» як код навколо кожної функції. (Зверніть увагу, що при цьому ви можете отримати величезну продуктивність, дкаш та ікаче, тому це, безумовно, нав'язливо. Але якщо вам не вистачає належного профіля вибірки або гідної підтримки процесора, це прийнятний варіант. Ви також можете бути розумними. про записи мінімум даних про функції входу / виходу та відновлення calltraces пізніше.) Коли ми побудували наш, ми імітували багато дргоЕ «s формат виведення.
  • Найкраще запустити пробірник вибірки ; VTune та CodeAnalyst доступні для x86 та x64, у вас є різні імітаційні та емуляційні середовища, які можуть давати вам тут дані.

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

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

Однак, щоб відповісти на ваше запитання безпосередньо: з мого досвіду, хоча це спокусливо (і часто корисне - я зазвичай щось вчу) переписати окремі функції / модулі, щоб оптимізувати кількість інструкцій або продуктивність icache чи dcache, і ми насправді це потрібно робити Іноді, коли ми стикаємося з особливо неприємною продуктивністю, переважна більшість питань щодо продуктивності, якими ми регулярно займаємось, приходить до розробки . Наприклад:

  • Чи слід кешувати оперативну пам’ять або перезавантажувати з диска кадри анімації стану «атаки» для програвача? Як щодо кожного ворога? У нас немає оперативної пам’яті, щоб зробити їх усі, але завантаження диска дорого! Ви можете бачити, що хитне, якщо одразу вискочить 5 чи 6 різних ворогів! (Гаразд, як щодо приголомшливого нересту?)
  • Ми робимо один тип операцій над усіма частинками або всі операції над однією частинкою? (Це компроміс ікаче / дкаш, і відповідь не завжди зрозуміла.) Як щодо того, щоб роз'єднати всі частинки і зберегти позиції разом (знаменита "структура масивів") проти збереження всіх даних про частинки в одному місці (" масив структур »).

Ви чуєте це, поки не стане неприємно на будь-яких курсах інформатики університетського рівня, але: це дійсно все про структури даних та алгоритми. Витративши деякий час на розробку алгоритму та потоку даних, ви отримаєте більше грошей на долар в цілому. (Переконайтесь, що ви прочитали чудові підводні камені об'єктно-орієнтованого програмування слайдів від колеги Sony Developer Services для ознайомлення тут.) Це не "відчуває" оптимізацію; в основному це витрачено час на дошку чи інструмент UML або на створення багатьох прототипів, а не на те, щоб поточний код запускався швидше. Але це взагалі набагато доцільніше.

І ще одна корисна евристика: якщо ви близькі до «ядра» вашого двигуна, можливо, варто докласти додаткових зусиль та експериментів для оптимізації (наприклад, векторизуйте ці матричні множення!). Чим далі від основної, тим менше ви повинні турбуватися про це, якщо один із ваших інструментів профілювання не скаже вам про інше.


6
  1. Використовуйте правильні структури даних та алгоритми вперед.
  2. Не проводите мікрооптимізацію, доки ви не профіліруєте та точно не дізнаєтесь, де стоять ваші гарячі місця.
  3. Не хвилюйтесь, що ви розумні. Компілятор вже виконує всі маленькі хитрощі, про які ви думаєте ("О! Мені потрібно помножити на чотири! Я зміщу ліворуч на два!")
  4. Зверніть увагу на пропуски кешу.

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

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

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

5

Також пам’ятайте, проте "передчасну песимізацію". Незважаючи на те, що не потрібно робити жорстку інформацію в кожному рядку коду, є виправдання для розуміння того, що ви насправді працюєте над грою, яка має наслідки для продуктивності в реальному часі.
Хоча всі говорять про те, щоб виміряти та оптимізувати гарячі точки, ця техніка не покаже вам продуктивність, яка втрачається у прихованих місцях. Наприклад, якщо кожна операція "+" у вашому коді займе вдвічі більше часу, ніж повинна, вона не відображатиметься як "гаряча точка", і, таким чином, ви ніколи не оптимізуєте її і навіть не зрозумієте, однак, оскільки вона використовується Це може коштувати вам великої продуктивності. Ви здивуєтеся, скільки з цих циклів вискакує, не виявляючи жодного разу. Тож знайте, чим займаєтесь.
Крім цього, я прагну регулярно проводити профілі, щоб мати уявлення про те, що там, і скільки часу залишається на кадр. Для мене час на кадр є найбільш логічним, оскільки він прямо мені підказує, де я перебуваю з рамковими цілями. Також спробуйте з’ясувати, де знаходяться вершини і що їх викликає - я віддаю перевагу стійкій частоті кадрів над високою частотою кадрів із шипами.


Мені це здається таким неправильним. Звичайно, мій знак "+" може тривати вдвічі більше кожного разу, коли це викликається, але це дійсно має значення лише в тісному циклі. Усередині щільного циклу зміна одного "+" може зробити порядки більше, ніж зміни "+" поза циклом. Навіщо думати про десяту частину мікросекунди, коли мілісекунд можна зберегти?
Вілдук

1
Тоді ви не розумієте ідеї, що стоїть за втратою струменя. '+' (лише як приклад) називається сотні тисяч разів на кадр, а не лише в тісних петлях. Якщо це втрачає кілька циклів щоразу, коли ви втрачаєте багато циклів по всій дошці, але це ніколи не відображатиметься як гаряча точка, оскільки виклики рівномірно розподіляються по вашій кодовій базі / шляху виконання. Отже, ви не говорите про десяту частину мікросекунди, а насправді тисячі разів перевищує ті десяті мікросекунди, додаючи до декількох мілісекунд. Після того, як пішов за низько висячими плодами (тісними петлями), я набрав мілісекунд таким чином не один раз.
Кай

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

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

3

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

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

Більшість оптимізацій ігрового коду зводиться до скорочення циклів процесора, необхідних для кожного кадру. Один із способів зробити це - просто оптимізувати кожен розпорядок, коли ви його пишете, і переконатися, що він проходить якнайшвидше. Однак є поширена приказка, що 90% циклів процесора витрачається на 10% коду. Це означає, що перенаправлення всієї вашої роботи з оптимізації до цих вузьких процедур матиме 10-кратний ефект оптимізації всього. Тож як ви визначаєте ці процедури? Профілювання робить це легко.

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


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

@Kaj, хороші профілі підсумовують всі сотні окремих виконання поганого алгоритму і показують вам загальну суму. Далі ви скажете "Але що робити, якщо у вас було 10 поганих методів і всі викликали на 1/10 частоті?" Якщо ви витратите весь свій час на ці 10 методів, вам не вистачить усіх низько висячих фруктів, де ви отримаєте набагато більший удар за свій долар.
Джон Макдональд

2

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

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

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

Тож будуйте деякі статистичні показники роботи з першого дня.

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

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


Щоб додати до гравця ingame (з яким я цілком згоден), розширення вашого гравця ingame на показ декількох барів (для декількох кадрів) допоможе вам співвіднести поведінку гри з шипами і може допомогти вам знайти вузькі місця, які не відображатимуться при середньому захопленні з профілером.
Кай

2

Якщо подивитися на цитату Кнута в її контексті, він продовжує пояснювати, що ми повинні оптимізувати, але з інструментами, як профілер.

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

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

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


1

Для мого проекту я зазвичай застосовую ДУЖЕ необхідні оптимізації у своєму базовому двигуні. Наприклад, я завжди люблю реалізовувати хорошу тверду реалізацію SIMD за допомогою SSE2 та 3DNow! Це гарантує, що моя математика з плаваючою комою стоїть на полі, де я хочу, щоб це було. Ще однією хорошою практикою є створення звички з оптимізації, як ви кодуєте, а не повертаєтесь назад. Більшість часу ці маленькі практики займають стільки ж часу, скільки і те, що ви кодували. Перш ніж кодувати функцію, переконайтесь, що ви дослідили найбільш ефективний спосіб це зробити.

Підсумок, на мою думку, його РОЗПОВІСТЬ зробити ваш код більш ефективним після того, як він уже смокче.


0

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


0

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

Ви хочете оптимізувати, коли ваш кадр скоротиться до моменту, коли гра почне страждати. Найімовірнішим винуватцем буде занадто багато (100%) використання вашого процесора.


Я б сказав, що GPU так само вірогідний, як і процесор. Дійсно, залежно від того, наскільки щільно поєднані речі, цілком можливо бути сильно пов'язаним процесором у половині кадру та сильно пов'язаним GPU з другою половиною. Тупе профілювання може навіть виявити шлях менше, ніж 100% використання на будь-якому. Переконайтеся, що ваше профілювання є досить дрібним, щоб показати, що (але не настільки дрібнозернисте, щоб бути настирливим!)
JasonD

0

Ви повинні оптимізувати свій код ... так часто, як вам потрібно.

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

Іноді це не код. Багато проблем, з якими я стикався в минулому, були орієнтовані на gpu (надано, це було на iPhone). Заповнення проблем, занадто багато розіграшів, недостатня геометрія, неефективні шейдери ...

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


0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.