На щастя, як ви вказали, складений COMPACT Mono використовує генераційний GC (на відміну від таких, як Microsoft, як WinMo / WinPhone / XBox, які просто підтримують плоский список).
Якщо ваша гра проста, GC повинен справлятися з нею просто чудово, але ось кілька покажчиків, на які ви можете звернути увагу.
Передчасна оптимізація
Спочатку переконайтеся, що це насправді проблема для вас, перш ніж намагатися її виправити.
Об'єднання об'єднань Дорогі типи посилань
Вам слід об'єднати довідкові типи, які ви часто створюєте, або які мають глибокі структури. Прикладом кожного може бути:
- Створюється часто:
Bullet
Об'єкт у грі з кулею .
- Глибока структура: Дерево рішень для реалізації ШІ.
Ви повинні використовувати a Stack
як свій пул (на відміну від більшості реалізацій, які використовують a Queue
). Причина цього полягає в тому, що з, Stack
якщо ви повертаєте об'єкт у басейн, а щось інше негайно хапає його; у нього буде набагато більший шанс опинитися на активній сторінці - або навіть у кеші CPU, якщо вам пощастить. Це просто крихітний трішки швидше. Крім того, завжди обмежуйте розмір своїх басейнів (просто ігноруйте "чеки", якщо ваш ліміт перевищено).
Уникайте створення нових списків, щоб очистити їх
Не створюйте нового, List
коли ви насправді цього мали намір Clear()
. Ви можете повторно використовувати резервний масив і зберегти набір розподілу та копій масиву. Подібно до цього спробуйте створити списки із значущою початковою потужністю (пам’ятайте, це не межа - лише стартовий потенціал) - це не потрібно бути точним, а лише оцінка. Це стосується в основному будь-якого типу колекції - за винятком a LinkedList
.
Використовуйте масиви структури (або списки), де це можливо
Ви отримуєте невелику вигоду від структур використання (або типів цінності взагалі), якщо передаєте їх між об'єктами. Наприклад, у більшості «хороших» частинок системи окремі частинки зберігаються у масивному масиві: масив та індекс передаються навколо замість самої частинки. Причина, яка працює так добре, полягає в тому, що коли GC потрібно зібрати масив, він може повністю пропустити вміст (це примітивний масив - тут нічого робити). Тож замість того, щоб дивитись на 10 000 об’єктів, GC просто повинен подивитися на 1 масив: величезний виграш! Знову ж таки, це буде працювати лише з типами значень .
Після RoyT. за умови життєздатних та конструктивних відгуків, я вважаю, що мені потрібно розширити цю проблему. Ви повинні використовувати цю техніку лише тоді, коли маєте справу з величезною кількістю сутностей (від тисяч до десятків тисяч). Крім того, вона повинна бути структурою, яка не повинна мати жодних посилальних типів полів і повинна містити явно набраний масив. На відміну від його відгуків, ми розміщуємо його в масиві, який, швидше за все, є полем у класі - це означає, що він збирається висадити купу (ми не намагаємося уникати розподілу купи - просто уникаємо роботи GC). Нас дійсно хвилює той факт, що це суцільний шматок пам’яті з великою кількістю значень, на який GC може просто дивитися в O(1)
операції замість O(n)
операції.
Ви також повинні виділити ці масиви як можна ближче до запуску програми, щоб зменшити шанси на фрагментацію або надмірну роботу, коли GC намагається перемістити ці шматки навколо (і розглянути можливість використання гібридного пов'язаного списку замість вбудованого List
типу ).
GC.Collect ()
Це, безумовно, НАЙКРАЩИЙ спосіб застрелити себе в ногу (див. "Розгляд продуктивності") з поколінням GC. Ви повинні викликати це лише тоді, коли ви створили ЕКСТРЕМУ кількість сміття - і один екземпляр, де це могло бути проблемою, - це лише після того, як ви завантажили вміст на рівні - і навіть тоді ви, мабуть, повинні збирати лише перше покоління ( GC.Collect(0);
) сподіваємось запобігти просуванню об’єктів третьому поколінню.
Ідентифікаційний і полевий обнулення
Варто обнулити поля, коли вам більше не потрібен об'єкт (тим більше на обмежених об'єктах). Причина полягає в подробицях того, як працює GC: він видаляє лише об'єкти, які не вкорінюються (тобто посилаються), навіть якщо цей об’єкт був би вкорінений через інші об’єкти, видалені в поточній колекції ( примітка: це залежить від GC аромат у використанні - деякі фактично чистять ланцюги). Крім того, якщо об’єкт вижив у колекції, його негайно просувають до наступного покоління - це означає, що будь-які об’єкти, що залишаються навколо полів, будуть рекламовані під час колекції. Кожне наступне покоління експоненціально дорожче збирати (і трапляється так само рідко).
Візьмемо такий приклад:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// G1 Collection
MyNestObject (G2) -> MyFurtherNestedObject (G2)
// G2 Collection
MyFurtherNestedObject (G3)
Якщо MyFurtherNestedObject
міститься багатомігабайтний об'єкт, вам можна гарантувати, що GC не буде дивитися на нього досить довго - тому що ви ненароком пересунули його на G3. Порівняйте це з цим прикладом:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// Dispose
MyObject (G1)
MyNestedObject (G1)
MyFurtherNestedObject (G1)
// G1 Collection
Шаблон Disposer допомагає вам встановити передбачуваний спосіб запитувати об'єкти, щоб очистити їхні приватні поля. Наприклад:
public class MyClass : IDisposable
{
private MyNestedType _nested;
// A finalizer is only needed IF YOU CONTROL UNMANAGED RESOURCES
// ~MyClass() { }
public void Dispose()
{
_nested = null;
}
}