Як оптимізувати функцію відстані?


23

Розробляючи досить просту гру, схожу на RTS, я помітив, що мої розрахунки на відстані викликали вплив на продуктивність.

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

Моє запитання саме в цьому. Хочеться знати, які альтернативи розробникам ігор мають для перевірки відстаней, крім звичайного підходу sqrt (x * x + y * y), що досить забирає багато часу, якщо ми виконуємо його тисячі разів за кадр.

Я хотів би зазначити, що я знаю про порівняння відстаней на Манхеттені та відстані в квадраті (пропускаючи вузьке вузьке місце). Ще щось?



Якщо у вас є об'єкти, які ви не очікуєте переміщати, як, наприклад, будівлі, можливо, варто буде взяти 2D-тейловий ряд функції відстані, обрізати його на квадратному терміні, а потім зберегти отриману функцію як функція відстані від конкретної будівлі. Це могло б перенести частину бурхливої ​​роботи на ініціалізацію та могло б трохи прискорити роботу.
Олександр Грубер

Відповіді:


26

TL; DR; Ваша проблема не у виконанні функції відстані. Ваша проблема полягає у виконанні функції відстані стільки разів. Іншими словами, вам потрібна алгоритмічна оптимізація, а не математична.

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

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

Відстань ^ 2 = x * x + y * y;

це насправді загальна хитрість. Але вам потрібно відповідно підкоригувати свої розрахунки. Він також може бути використаний як початкова перевірка перед розрахунком фактичної відстані. Так, наприклад, замість обчислення фактичної відстані між двома точками / сферами для випробування на перетині ми можемо обчислити відстань у квадраті і порівняти з квадратом радіуса замість радіуса.

Редагуйте, після того як @ Byte56 зазначив, що я не читав питання, і що ви знаєте про оптимізацію квадратних відстаней.

Ну у вашому випадку, на жаль, ми в комп'ютерній графіці майже виключно маємо справу з евклідовим простором , і відстань точно визначена, як Sqrt of Vector dot itselfу евклідовому просторі.

Відстань у квадраті - найкраще наближення, яке ви збираєтесь отримати (з точки зору продуктивності), я не бачу нічого, що б'є 2 множення, одне додавання та завдання.

Так ви кажете, що я не можу оптимізувати функцію дистанції, що мені робити?

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

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

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

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

Складніший підхід - використовувати BSP або Octrees.


2
Я вважаю, що в останньому реченні питання йдеться про те, що ОП шукає інші альтернативи (вони знають про використання відстані у квадраті).
MichaelHouse

@ Byte56 так, ти прав, я цього не читав.
concept3d

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

@Grimshaw Я відредагував відповідь, щоб вирішити початкову проблему.
concept3d

@ Byte56 спасибі за вказівку. Я відредагував відповідь.
concept3d

29

Якщо вам потрібно щось, що залишається лінійним на будь-якій відстані (на відміну від distance^2) і все-таки виглядає розпливчасто круговим (на відміну від шкваркового Чебишева та діамантоподібних Манхеттенських відстаней), ви можете провести середнє значення для двох останніх прийомів, щоб отримати восьмикутну форму наближення відстані:

dx = abs(x1 - x0)
dy = abs(y1 - y0)

dist = 0.5 * (dx + dy + max(dx, dy))

Ось візуалізація (контурний графік) функції завдяки Wolfram Alpha :

Контурний сюжет

І ось графік його функції помилок порівняно з евклідовою відстані (радіани, лише перший квадрант):

Помилка

Як бачимо, похибка коливається від 0% на осях до приблизно + 12% у часточках. Змінивши коефіцієнти трохи, ми можемо зменшити його до +/- 4%:

dist = 0.4 * (dx + dy) + 0.56 * max(dx, dy)

введіть тут опис зображення

Оновлення

Використовуючи наведені вище коефіцієнти, максимальна похибка буде в межах +/- 4%, але середня помилка все одно буде + 1,3%. Оптимізований для нульової середньої помилки, ви можете використовувати:

dist = 0.394 * (dx + dy) + 0.554 * max(dx, dy)

що дає помилки від -5% до + 3% та середня помилка + 0,043%


Під час пошуку в Інтернеті назви цього алгоритму я знайшов подібне восьмикутне наближення :

dist = 1007/1024 * max(dx, dy) + 441/1024 * min(dx, dy)

Зауважте, що це по суті еквівалентно (хоча показники різні - вони дають помилку від -1,5% до 7,5%, але їх можна масажувати до +/- 4%), оскільки max(dx, dy) + min(dx, dy) == dx + dy. Використовуючи цю форму, дзвінки minта maxдзвінки можна визначити на користь:

if (dy > dx)
    swap(dx, dy)

dist = 1007/1024 * dx + 441/1024 * dy

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


3
Цікаво, я цього раніше не бачив! У нього є назва, чи просто "середній Чебішев та Манхеттен"?
congusbongus

@congusbongus Мабуть, це ім’я, але я не знаю, що це таке. Якщо ні, то, можливо, одного дня це буде називатися дистанцією Crist (га ... мабуть, ні)
bcrist

1
Зауважте, що множення з плаваючою комою не дуже ефективно. Ось чому в іншому наближенні використовується 1007/1024 (яке буде реалізоване як ціле множення з подальшим зсувом бітів).
MSalters

@MSalters Так, операції з плаваючою комою часто повільніше, ніж цілі операції, але це не має значення - 0,4 і 0,56 можна так само легко перетворити для використання цілих операцій. Крім того, на сучасному апаратному забезпеченні x86 більшість операцій з плаваючою комою (крім FDIV, FSQRTта інших трансцендентальних функцій) коштують фактично так само, як і їх цілі версії: 1 або 2 цикли за інструкцію.
bcrist

1
Це дуже схоже на Alpha max + Beta Min: en.wikipedia.org/wiki/Alpha_max_plus_beta_min_algorithm
drake7707

21

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

У великому ігровому світі з багатьма акторами неможливо продовжувати перевіряти відстань між одним актором та всіма іншими. Чим більше гравців, НВЦ і снаряди входять в світ, число порівнянь , які повинні бути зроблені зростатиме квадратично з O(N^2).

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

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

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

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

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

Розгляд того, як зростає витрата алгоритму в міру отримання більшої кількості даних, має вирішальне значення для масштабованої розробки програмного забезпечення. Іноді достатньо просто вибрати правильну структуру даних . Витрати, як правило , описується з допомогою Big O позначення .

(Я розумію, що це не пряма відповідь на питання, але це може бути корисно для деяких читачів. Мої вибачення, якщо я витратив ваш час!)


7
Це найкраща відповідь. Немає що оптимізувати у функції відстані; просто потрібно використовувати його рідше.
sam hocevar

3
Прийнята відповідь також охоплює просторовий розподіл, інакше ваша відповідь дійсно оптимальна. Дякую.
Грімшо

Мій час дуже добре провів, читаючи вашу відповідь. Дякую, Джоуї.
Патрік М

1
Це найкраща відповідь і єдина, яка зосереджується на реальній проблемі, а не на червоній оселедці виконання функцій дистанції. Прийнята відповідь також може охоплювати просторовий розподіл, але це як бік; вона зосереджена на обчисленні відстані. Розрахунок відстані тут не є основною проблемою; оптимізація обчислення відстані - це жорстоке рішення, яке не масштабується.
Максим Мінімус

Чи можете ви пояснити, чому кількість порівнянь буде експоненціальним? Я хоч би був квадратичним, порівнюючи кожного актора між собою протягом кожного часового періоду.
Петро Пудлак

4

Як щодо відстані Чебишева? Для точок p, q його визначають так:

відстань

Так для точок (2, 4) та (8, 5) відстань Чебишева дорівнює 6, як | 2-8 | > | 4-5 |.

Крім того, нехай E - евклідова відстань, а C - відстань Чебишева. Потім:

відстань2

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

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


1
Ви також можете використовувати відстань на Манхеттені як верхню межу.
congusbongus

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

2

Дуже проста локальна оптимізація - це просто спочатку перевірити один вимір.

Це є :

distance ( x1, y1 , x1, y2) > fabs (x2 - x1)

Тому просто перевірка fabs (x2 - x1)як перший фільтр може принести помітний приріст. Скільки буде залежати від розміру світу порівняно з відповідними діапазонами.

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

Якщо всі відповідні об'єкти впорядковані у списку у порядку координати x, знаходяться поблизу об’єкти у списку. Навіть якщо список вийшов з ладу через те, що вони не підтримуються повністю, коли об’єкти рухаються, то, задавши відомі межі швидкості, ви все одно можете зменшити розділ списку, який слід шукати поблизу.


2

У минулому докладалися зусилля для оптимізації sqrt. Хоча це більше не стосується сучасних машин, ось приклад із вихідного коду Quake, який використовує магічне число 0x5f3759df :

float Q_rsqrt( float number )
{
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y  = number;
  i  = * ( long * ) &y;  // evil floating point bit level hacking
  i  = 0x5f3759df - ( i >> 1 ); // what the hell?
  y  = * ( float * ) &i;
  y  = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
  // y  = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration (optional)
  // ...
  return y;
}

Докладне пояснення того , що відбувається тут , можна знайти у Вікіпедії.

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

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


2
Це вже не вартий оптимізації. Практично у всіх архітектурах ПК для споживачів, які ви можете придбати сьогодні, є інструкції, оптимізовані апаратним забезпеченням sqrt, які виконують квадратний корінь у тактовому циклі чи менше. Якщо вам справді потрібен найшвидший можливий sqrt, ви використовуєте інструкцію sqrt з плаваючою комою x86 simd: en.wikipedia.org/wiki/… Для таких речей, як шейдери на GPU, виклик sqrt автоматично призведе до такої інструкції. У процесорі я припускаю, що багато компіляторів реалізують sqrt через SIMD sqrt, якщо вони доступні.
TravisG

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