Чи варто використовувати пули частинок на керованих мовах?


10

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

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

Питання: чи варто використовувати об’єктний пул для частинок (зокрема, тих, що гинуть і швидко відтворюються) керованою мовою?

Відповіді:


14

Так.

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

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

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


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

2
Я не торкався того, що стосується структури, оскільки згаданий ОП використовує Java, і я не так знайомий, як типи / структури значень діють на цій мові.

У Java немає структур, лише класи (завжди в купі).
Брендан Лонг

1

Для Java це не так корисно об'єднати об’єкти *, оскільки перший цикл GC для об'єктів, що ще існують навколо, переставить їх у пам'ять, перемістивши їх з простору "Едем" і, можливо, втратить просторову локальність у процесі.

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

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

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

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

class Particle
{
    public float x;
    public float y;
    public boolean alive;
}

Зробіть щось на кшталт:

class Particles
{
    // X positions of all particles. Resize on demand using
    // 'java.util.Arrays.copyOf'. We do not use an ArrayList
    // since we want to work directly with contiguously arranged
    // primitive types for optimal memory access patterns instead 
    // of objects managed by GC.
    public float x[];

    // Y positions of all particles.
    public float y[];

    // Alive/dead status of all particles.
    public bool alive[];
}

Тепер для повторного використання пам’яті для існуючих частинок можна зробити це:

class Particles
{
    // X positions of all particles.
    public float x[];

    // Y positions of all particles.
    public float y[];

    // Alive/dead status of all particles.
    public bool alive[];

    // Next free position of all particles.
    public int next_free[];

    // Index to first free particle available to reclaim
    // for insertion. A value of -1 means the list is empty.
    public int first_free;
}

Тепер, коли nthчастинка відмирає, щоб дозволити її повторному використанню, натисніть її на безкоштовний список так:

alive[n] = false;
next_free[n] = first_free;
first_free = n;

Додаючи нову частинку, подивіться, чи можна виводити індекс із вільного списку:

if (first_free != -1)
{
     int index = first_free;

     // Pop the particle from the free list.
     first_free = next_free[first_free];

     // Overwrite the particle data:
     x[index] = px;
     y[index] = py;
     alive[index] = true;
     next_free[index] = -1;
}
else
{
     // If there are no particles in the free list
     // to overwrite, add new particle data to the arrays,
     // resizing them if needed.
}

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

Щоб полегшити роботу з кодом, можливо, варто написати власні базові змінні контейнери, які зберігають масиви поплавців, масиви цілих чисел та масиви булевих. Знову ж, ви не можете використовувати generics і ArrayListтут (принаймні, з останнього разу, коли я перевірив), оскільки для цього потрібні об'єкти, керовані GC, а не суміжні примітивні дані. Ми хочемо використовувати суміжний масив int, наприклад, не керовані GC масивами, Integerякі не обов'язково будуть суміжними після виходу з простору Eden.

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


1
Це приємне написання цього питання, і після 5 років кодування Java я можу це чітко бачити; Java GC, безумовно, не тупа, і вона не була створена для програмування ігор (оскільки вона не дуже піклується про локальність даних та інше), тому нам краще грати, як заманеться: P
Gustavo Maciel
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.