Удосконалення функції O (N ^ 2) (усі сутності, що повторюються над усіма іншими об'єктами)


21

Трохи тла, я кодую еволюційну гру з другом на C ++, використовуючи ENTT для системи сутності. Істоти ходять по двовимірній карті, їдять зелень або інші істоти, відтворюють і їхні риси мутують.

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

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

Приклад скріншоту гри

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

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

for (creature : all_creatures)
{
  for (food : all_entities_with_food_value)
  {
    // if the food is within the creatures view and it's
    // the best food found yet, it becomes the best food
  }
  // set the best food as the target for creature
  // make the creature chase it (change its state)
}

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

Я відкритий до ідей, як зробити цей процес більш ефективним. Я хотів би зменшити складність від , але я не знаю, чи це можливо.O(N2)

Один із способів, який я вже вдосконалюю, - це сортування all_entities_with_food_valueгрупи так, що коли істота перебирає надто велику їжу, щоб її з'їсти, вона зупиняється на цьому. Будь-які інші вдосконалення більш ніж вітаються!

EDIT: Дякую всім за відповіді! Я реалізував різні речі з різних відповідей:

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

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

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

Зараз візуалізація - це те, що сповільнює мою роботу, але це проблема для іншого дня. Дякую вам всім!


2
Ви експериментували з декількома потоками на декількох ядрах процесора, що працюють одночасно?
Ед Марті

6
Скільки істот у вас в середньому? Мабуть, це не так вже й високо, судячи з знімків. Якщо це завжди так, розділення простору не дуже допоможе. Чи вважали ви не виконувати цю функцію при кожному галочці? Ви можете запустити його, скажімо, по 10 кліщів. Результати моделювання не повинні якісно змінюватися.
Турмс

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

Наївна пропозиція: ви можете використовувати квадри або споріднену структуру даних замість способу O (N ^ 2), який ви зараз робите.
Сейрія

3
Як запропонував @Harabeck, я б поглибився, щоб побачити, де в циклі витрачається весь цей час. Якщо, наприклад, це квадратні кореневі обчислення відстані, ви, можливо, зможете спочатку порівняти XY-команди, щоб попередньо усунути багато кандидатів, перш ніж робити дорогий sqrt на решті. Додавання if (food.x>creature.x+149.64 or food.x<creature.x-149.64) continue;має бути простішим, ніж реалізація "складної" структури сховища, якщо це достатньо ефективно. (Пов'язано: Це може нам допомогти, якщо ви опублікували трохи більше коду у внутрішньому циклі)
AC

Відповіді:


34

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

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


Істота, що знаходить їжу

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

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

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

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

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


Додано нову їжу

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

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

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

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


Пропуск моделювання

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

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

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


1
"і шукайте лише там." а найближчі сусіди - це 9 клітин. Чому 9? Бо що робити, якщо істота знаходиться прямо в кутку клітини.
UKMonkey

1
@UKMonkey "Зробіть у клітинках хоча б радіус кіл, які ви хочете зіткнути", якщо сторона комірки - це радіус, а істота знаходиться в кутку ... ну, я вважаю, вам потрібно шукати лише чотири в цьому випадку. Однак, безумовно, ми можемо зробити клітини меншими, що може бути корисно, якщо є занадто багато їжі і занадто мало істот. Редагувати: Я уточню.
Тераот

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

@UKMonkey, тому я спочатку не згадував про це.
Theraot

16

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

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

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

Ви також можете скористатися бібліотекою AABB.cc.


1
Це дійсно зменшило б складність до N log N, але розділити це також буде дорого. Бачачи, як мені потрібно зробити розділення кожної галочки (оскільки істоти переміщують кожну галочку), чи все-таки це варто? Чи є рішення, які допомагають розділити рідше?
Олександр Родрігес

3
@AlexandreRodrigues вам не доведеться відновлювати ціле дерево кожного галочки, оновлювати лише ті частини, які рухаються, і лише тоді, коли щось виходить за межі конкретного контейнера AABB. Для подальшого підвищення продуктивності вам може знадобитися жирувати вузли (залишаючи деякий простір між дітьми), так що вам не доведеться перебудовувати цілу гілку під час оновлення листя.
Оцелот

6
Я думаю, що BVH тут може бути занадто складним - рівномірна сітка, реалізована як хеш-таблиця, є досить хорошою.
Стівен

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

2

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

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

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

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

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


@Theraot ми не знаємо, як структуровані речі малювання. Але так, недоліки стануть вузькими місцями, як тільки ви дістанетесь досить швидко
joojaa

1

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

Так званий алгоритм Fortune робить це для вас у Nlog (N), а сторінка wiki на цій сторінці містить псевдокод для його реалізації. Я впевнений, що там є і бібліотечні реалізації. https://en.wikipedia.org/wiki/Fortune%27s_algorithm


Ласкаво просимо на GDSE & спасибі за відповіді. Як саме Ви б застосували це до ситуації з ОП? В описі проблеми говориться, що суб'єкт господарювання повинен розглянути всю їжу на відстані свого перегляду та вибрати найкращу. Традиційний Вороной виключав блюд, які були ближче до іншого об'єкту. Я не кажу, що Вороной не спрацював би, але з вашого опису не очевидно, як ОП повинна використовувати один для проблеми, як описано.
Пікалек

Мені подобається ця ідея, я хотів би бачити її розширеною. Як ви представляєте діаграму voronoi (як у структурі даних пам'яті)? Як ви це запитуєте?
Theraot

@Theraot вам не потрібна діаграма voronoi просто така ж ідея розгортки.
joojaa

-2

Найпростішим рішенням було б інтегрування двигуна фізики та використання лише алгоритму виявлення зіткнень. Просто побудуйте коло / сферу навколо кожної сутності, і нехай фізичний двигун обчислює зіткнення. Для 2D я б запропонував Box2D або Бурундук , а Куля для 3D.

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

  • Широкофазне виявлення: метою цього етапу є отримання списку кандидатських пар об'єктів, які можуть стикатися якомога швидше. Два поширені варіанти:
    • Підмітайте та обріжте : сортуйте обмежувальні поля по осі X та позначте ті пари об’єктів, які перетинаються. Повторіть для кожної іншої осі. Якщо кандидатська пара здає всі тести, вона переходить до наступного етапу. Цей алгоритм дуже добре використовує тимчасову узгодженість: ви можете зберігати списки відсортованих утворень та оновлювати їх кожен кадр, але оскільки вони майже відсортовані, це буде дуже швидко. Він також використовує просторову когерентність: оскільки сутності відсортовані за зростанням просторового порядку, коли ви перевіряєте на зіткнення, ви можете зупинитись, як тільки сутність не зіткнеться, тому що всі наступні будуть далі.
    • Структури даних просторового розподілу, як-от квадри, октрей та сітки. Сітки легко здійснити, але можуть бути дуже марними, якщо щільність сутності низька, і дуже важко реалізувати для необмеженого простору. Статичні просторові дерева теж легко реалізувати, але їх важко збалансувати або оновити на місці, тому вам доведеться перебудовувати його кожен кадр.
  • Вузька фаза: кандидатські пари, знайдені на широкій фазі, додатково перевіряються за допомогою більш точних алгоритмів.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.