Garbage collector unity что это

Обновлено: 05.07.2024

We have just added an experimental new feature to Unity 19.1a10: Incremental Garbage Collection. Read this post to learn what this feature is all about, how it can help your project, what all our future plans for it and how can you get involved in the process.

Unity uses the Boehm–Demers–Weiser garbage collector which is a stop-the-world garbage collector, meaning that whenever it needs to perform garbage collection, it will stop the running program, and only resume normal execution once it has finished all its work. This can cause delays in the execution of the program at somewhat arbitrary moments, which can take anywhere between less than 1 and several 100 milliseconds, depending on how much memory the garbage collector needs to process and on the platform the program is running on. Now, obviously, for real-time applications like games, this can become quite a big issue, as it isn’t possible to sustain a consistent frame rate as required for smooth animation if the program’s execution can be arbitrarily suspended by the garbage collector. These interruptions are also known as GC spikes, as they will show as spikes in an otherwise smooth profiler frame time graph. Usually, developers try to work around this issue by writing their code to avoid creating “garbage” memory while running the game, so the garbage collector has less work to do - but this isn’t always possible or easy.

Enter Incremental Garbage Collection. With Incremental GC, we still use the same Boehm–Demers–Weiser GC, but we run it in an incremental mode, which allows it to split its work into multiple slices. So instead of having a single long interruption of your program’s execution to allow the GC to do its work, you can have multiple, much shorter interruptions. While this will not make the GC faster overall, it can significantly reduce the problem of GC spikes breaking the smoothness of the animation by distributing the workload over multiple frames.

Tracking allocations

Unity has the following tools to keep track of memory allocations:

  • Unity Profiler’s CPU Usage module: Provides details of the GC Alloc per frame
  • Unity Profiler’s Memory module: Provides high-level memory usage frame by frame
  • The Memory Profiler package: A separate Unity package which provides detailed information about memory usage during specific frames in your application

In the CPU Usage module, the Hierarchy view contains a GC Alloc column. This column displays the number of bytes that Unity allocated on the managed heap in a specific frame. It also displays the amount of memory that the garbage collector managed, and it includes memory that Unity might have allocated and reused in subsequent frames. This means that the sum of the GC Alloc over all frames doesn’t total how much the managed memory grew in that time.

To get the most accurate information, you should always profile your application on a development build A development build includes debug symbols and enables the Profiler. More info
See in Glossary on the target platform or device you want to build to. The Unity Editor works in a different way to a build, and this affects the profiling data; for example, the GetComponent method always allocates memory when it’s executed in the Editor, but not in a built project.

Value and Reference Types

При вызове функции, значения её параметров копируются в зону памяти, зарезервированную специально для этого вызова. Типы данных, которые занимают всего лишь несколько байт могут быть скопированы легко и быстро. Однако, обычно объекты, строки и массивы гораздо больше, и было бы очень неэффективно копировать эти данные как обычно. К счастью, это не обязательно; реальное место хранения для больших элементов выделяется из кучи и для запоминания его местоположения используется небольшое “указательное” значение. После этого, во время передачи параметра нужно будет скопировать только указатель. Пока система среды выполнения может найти определяемый указателем элемент, можно использовать одиночную копию данных так часто, как это требуется.

Типы, которые хранятся напрямую и копируются при передаче параметра, называются значимыми типами (value types). В них включены integer, float, boolean и структурные типы Unity (например, Color и Vector3). Типы, которые выделяются из кучи, после чего доступ к ним получается при помощи указателя, называются ссылочными типами, т.к. значения хранящиеся в переменной только “ссылаются” на реальные данные. Примеры ссылочных типов включают объекты, строки и переменные.

Enabling incremental garbage collection

Incremental garbage collection is currently supported on the following platforms:

  • Mac standalone player
  • Windows standalone player
  • Linux standalone player
  • iOS
  • Android
  • Windows UWP player
  • PS4
  • Xbox One
  • Nintendo Switch
  • Unity Editor

On supported configurations, Unity provides Incremental garbage collection as an option in the “Other settings” area of the Player settings window. Just enable the Use incremental GC checkbox.

Player Settings to enable incremental garbage collection

Player Settings to enable incremental garbage collection

In addition, if you set the VSync Count to anything other than Don’t Sync in your project Quality settings or with the Application.VSync property or you set the Application.targetFrameRate property, Unity automatically uses any idle time left at the end of a given frame for incremental garbage collection.

You can exercise more precise control over incremental garbage collection behavior using the Scripting.GarbageCollector class. For example, if you do not want to use VSync or a target frame rate, you could calculate the amount of time available before the end of a frame yourself and provide that time to the garbage collector to use.

Значимые и ссылочные типы

При вызове функции, значения её параметров копируются в зону памяти, зарезервированную специально для этого вызова. Типы данных, которые занимают всего лишь несколько байт могут быть скопированы легко и быстро. Однако, обычно объекты, строки и массивы гораздо больше, и было бы очень неэффективно копировать эти данные как обычно. К счастью, это не обязательно; реальное место хранения для больших элементов выделяется из кучи и для запоминания его местоположения используется небольшое “указательное” значение. После этого, во время передачи параметра нужно будет скопировать только указатель. Пока система среды выполнения может найти определяемый указателем элемент, можно использовать одиночную копию данных так часто, как это требуется.

Типы, которые хранятся напрямую и копируются при передаче параметра, называются значимыми типами (value types). В них включены integer, float, boolean и структурные типы Unity (например, Color и Vector3 ). Типы, которые выделяются из кучи, после чего доступ к ним получается при помощи указателя, называются ссылочными типами, т.к. значения хранящиеся в переменной только “ссылаются” на реальные данные. Примеры ссылочных типов включают объекты, строки и переменные.

Оптимизация

Сборка мусора - это автоматический и невидимый программисту процесс, но процесс сборки на деле требует некоторого времени “закулисной” работы процессора. При правильном использовании, автоматическое управление памятью обычно не уступает по производительности ручному выделению. Тем не менее, для программиста важно избегать ошибок, которые будут вызывать сборку чаще чем надо и выражаться в задержках работы.

Есть несколько алгоритмов с сомнительной репутацией, которые могут быть ночным кошмаром для GC, хотя на первый взгляд они выглядят невинно. Постоянное объединение строк - классический пример:-

Ключевой деталью является то, что новые части не добавляются к строке один за одним. На самом деле, в каждой итерации цикла предыдущее содержание переменной “умирает” - выделяется целая новая строка для размещения в ней оригинальной части и новой части в конце. Т.к. строка становится длиннее, то с увеличивающимся значением i, значение потребляемого пространства кучи также повышается и с лёгкостью достигает сотни байтов свободного пространства кучи при каждом вызове этой функции. Если вам нужно объединить много строк вместе, то более подходящим вариантом будет класс Mono библиотеки System.Text.StringBuilder.

Однако, даже повторяющееся соединение не вызовет много неприятностей, если не вызывать его часто, и в Unity под этим обычно подразумевается каждый кадр. Что-то вроде:-

…будет выделять новые строки при каждом вызове Update и генерировать постоянный поток нового мусора. Большую часть этого можно сохранить, обновляя текст только тогда, когда счёт меняется:-

Другая потенциальная проблема появляется, когда функция возвращает массив:-

Это очень элегантный и удобный тип функции, если создаётся новый массив заполненный значениями. Однако, если её постоянно вызывать, то каждый раз будет выделяться свежая память. Т.к. массивы могут быть очень большими, свободное пространство кучи может быть постоянно использовано, что приведёт к частой сборке мусора. Единственный способ избежать этой проблемы, это извлечь пользу из того факта, что массив - ссылочный тип. Массив, использованный в функции как параметр, может быть использован внутри этой функции и результаты останутся после возврата функции. Функция, вроде указанной выше, часто может быть замещена чем-нибудь вроде:-

Она просто замещает существующие данные массива новыми значениями. Хотя она и требует стартового выделения массива, чтобы быть выполненной в коде вызова (который выглядит не очень элегантно), функция не будет создавать какого-либо нового мусора, когда она будет вызвана.

Garbage collector overview

Unity uses a garbage collector to reclaim memory from objects that your application and Unity are no longer using. When a script tries to make an allocation on the managed heap but there isn’t enough free heap memory to accommodate the allocation, Unity runs the garbage collector. When the garbage collector runs, it examines all objects in the heap, and marks for deletion any objects that your application no longer references. Unity then deletes the unreferenced objects, which frees up memory.

To determine which heap blocks are no longer in use, the garbage collector searches through all active reference variables and marks the blocks of memory that they refer to as “live.” At the end of the search, the garbage collector considers any space between the “live” blocks empty and marks them for use for subsequent allocations. The process of locating and freeing up unused memory is called garbage collection (GC).

Note: The garbage collector works differently in WebGL A JavaScript API that renders 2D and 3D graphics in a web browser. The Unity WebGL build option allows Unity to publish content as JavaScript programs which use HTML5 technologies and the WebGL rendering API to run Unity content in a web browser. More info
See in Glossary . For more information, see Garbage collection considerations.

In Unity, the garbage collector has the following modes:

  • Incremental garbage collection: Enabled by default (Project Settings > Player > Configuration), this mode spreads out the process of garbage collection over multiple frames.
  • Incremental garbage collection disabled: If you disable the Incremental GC Player Setting, the garbage collector stops running your application to inspect and process objects on the heap.
  • Disable automatic garbage collection: Use the GarbageCollector.GCMode API to take full control of when Unity should run the garbage collector.

Possible problems with incremental collection

In most cases, incremental garbage collection can mitigate the problem of garbage collection spikes. However, in some cases, incremental garbage collection may not prove beneficial in practice.

When incremental garbage collection breaks up its work, it breaks up the marking phase in which it scans all managed objects to determine which objects are still in use and which objects can be cleaned up. Dividing up the marking phase works well when most of the references between objects don’t change between slices of work. When an object reference does change, those objects must be scanned again in the next iteration. Thus, too many changes can overwhelm the incremental garbage collector and cause a situation where the marking pass never finishes because it always has more work to do – in this case, the garbage collection falls back to doing a full, non-incremental collection.

Also, when using incremental garbage collection, Unity needs to generate additional code (known as write barriers) to inform the garbage collection whenever a reference has changed (so the garbage collection will know if it needs to rescan an object). This adds some overhead when changing references which can have a measurable performance impact in some managed code.

Still, most typical Unity projects (if there is such a thing as a “typical” Unity project) can benefit from incremental garbage collection, especially if they suffer from garbage collection spikes.

Always use the Profiler to verify that your game or program performs as you expect.

Understanding Automatic Memory Management

При создании объекта, строки или массива, память для его хранения выделяется из центрального пула, который называется куча (heap). Когда использование элемента прекращается, память, которую он занимал, можно будет освободить и использовать для чего-нибудь ещё. В прошлом, выделение и освобождение этих блоков памяти с помощью вызовов соответствующих методов в основном лежало на плечах программистов. Теперь за вас памятью автоматически управляют среды выполнения, например, движок Mono у Unity. Автоматическое управление памятью требует меньше усилий при написании кода, чем прямое выделение / освобождение и значительно уменьшает потенциал для утечки памяти (ситуации, когда память была выделена, но впоследствии так и не была освобождена).

Optimization

Garbage collection is automatic and invisible to the programmer but the collection process actually requires significant CPU time behind the scenes . When used correctly, automatic memory management will generally equal or beat manual allocation for overall performance. However, it is important for the programmer to avoid mistakes that will trigger the collector more often than necessary and introduce pauses in execution.

Есть несколько алгоритмов с сомнительной репутацией, которые могут быть ночным кошмаром для GC, хотя на первый взгляд они выглядят невинно. Постоянное объединение строк - классический пример:-

Ключевой деталью является то, что новые части не добавляются к строке один за одним. На самом деле, в каждой итерации цикла предыдущее содержание переменной “умирает” - выделяется целая новая строка для размещения в ней оригинальной части и новой части в конце. Т.к. строка становится длиннее, то с увеличивающимся значением i, значение потребляемого пространства кучи также повышается и с лёгкостью достигает сотни байтов свободного пространства кучи при каждом вызове этой функции. Если вам нужно объединить много строк вместе, то более подходящим вариантом будет класс Mono библиотеки System.Text.StringBuilder.

Однако, даже повторяющееся соединение не вызовет много неприятностей, если не вызывать его часто, и в Unity под этим обычно подразумевается каждый кадр. Что-то вроде:-

…будет выделять новые строки при каждом вызове Update и генерировать постоянный поток нового мусора. Большую часть этого можно сохранить, обновляя текст только тогда, когда счёт меняется:-

Другая потенциальная проблема появляется, когда функция возвращает массив:-

Это очень элегантный и удобный тип функции, если создаётся новый массив заполненный значениями. Однако, если её постоянно вызывать, то каждый раз будет выделяться свежая память. Т.к. массивы могут быть очень большими, свободное пространство кучи может быть постоянно использовано, что приведёт к частой сборке мусора. Единственный способ избежать этой проблемы, это извлечь пользу из того факта, что массив - ссылочный тип. Массив, использованный в функции как параметр, может быть использован внутри этой функции и результаты останутся после возврата функции. Функция, вроде указанной выше, часто может быть замещена чем-нибудь вроде:-

Она просто замещает существующие данные массива новыми значениями. Хотя она и требует стартового выделения массива, чтобы быть выполненной в коде вызова (который выглядит не очень элегантно), функция не будет создавать какого-либо нового мусора, когда она будет вызвана.

Disabling garbage collection

If you are using the Mono or IL2CPP scripting backend, you can avoid CPU spikes during garbage collection by disabling garbage collection at run time. When you disable garbage collection, memory usage never decreases because the garbage collector does not collect objects that no longer have any references. In fact, memory usage can only ever increase when you disable garbage collection. To avoid increased memory usage over time, take care when managing memory. Ideally, allocate all memory before you disable the garbage collector and avoid additional allocations while it is disabled.

For more details on how to enable and disable garbage collection at run time, see the GarbageCollector Scripting API page.

Requesting a Collection

As mentioned above, it is best to avoid allocations as far as possible. However, given that they can’t be completely eliminated, there are two main strategies you can use to minimise their intrusion into gameplay.

Small heap with fast and frequent garbage collection

Эта стратегия лучше всего подходит играм с долгими сессиями игрового процесса, когда стабильный FPS стоит на первом месте. Подобная игра обычно будет выделять небольшие блоки почаще, но эти блоки будут в использовании совсем не долго. Типичный размер кучи при использовании подобной стратегии на iOS составляет около 200 КБ, и сборка мусора занимает где-то 5 миллисекунд на iPhone 3G. Если размер кучи увеличить до 1 МБ, то сборка займёт где-то 7 миллисекунд. Следовательно, иногда будет разумно запрашивать сборку мусора раз в определённый интервал. В результате сборка будет проходить чаще чем строго необходимо, но сборка пройдёт быстрее с минимальным влиянием на игровой процесс:-

Однако вам следует использовать эту технику аккуратно и проверять статистку профайлера, чтобы убедиться, что это действительно уменьшает время сборки мусора для вашей игры.

Large heap with slow but infrequent garbage collection

Эта стратегия лучше всего подходит для игр, где выделения (и последующие сборки) памяти относительно редки и их можно провести во время пауз в игровом процессе. Таким образом кучу можно сделать максимально большой (но не на столько, чтобы перегрузить память системы, что может вызвать закрытие вашего приложения). Однако, среда запуска Mono по возможности избегает автоматического расширения кучи. Вы можете расширить кучу вручную путём предварительного выделения некоторого пространства во время запуска (т.е. вы вызываете “бесполезный” объект, который выделен только для влияния на менеджер памяти):-

Достаточно большую кучу не следует заполнять под завязку между этими паузами в игровом процессе, включающими в себя сборку. Когда такая пауза происходит, вы можете напрямую запросить сборку:-

Опять же, вам следует аккуратно использовать эту стратегию и обращать внимание на статистику профайлера, нежели просто предполагать, что желаемый эффект достигнут.

Reusable Object Pools

There are many cases where you can avoid generating garbage simply by reducing the number of objects that get created and destroyed. There are certain types of objects in games, such as projectiles, which may be encountered over and over again even though only a small number will ever be in play at once. In cases like this, it is often possible to reuse objects rather than destroy old ones and replace them with new ones.

Выделение и сборка мусора

Менеджер памяти отслеживает зоны в куче, которые определены как неиспользуемые. При запросе нового блока памяти (допустим, при создании экземпляра объекта), менеджер выбирает неиспользуемую зону, из которой следует выделить блок, и затем удаляет выделенную память из зоны известного неиспользуемого пространства. Последующие запросы обрабатываются тем же способом, пока в неиспользуемой зоне будет достаточно места для выделения блока необходимого размера. Доступ к ссылочному элементу в куче может быть получен только пока есть ссылочные переменные, которые могут его найти. Если все ссылки к блоку памяти пропадут (т.е. ссылочные переменные были переназначены или они являются локальными переменными, которые теперь вне контекста), то занимаемая им память может быть ещё раз безопасно выделена.

Чтобы определить, какие блоки кучи больше не используются, менеджер памяти просматривает все активные ссылочные переменные и отмечает блоки, к которым они ссылаются как “live” (используемые). В конце поиска, любое пространство между используемыми блоками менеджером считается пустым и в будущем может быть использовано для выделения. По очевидным причинам, процесс обнаружения и освобождения неиспользуемой памяти известен как сборка мусора(Garbage Collection, или GC для сокращения).

Incremental Garbage Collection

Incremental Garbage Collection spreads out the work performed to perform garbage collection over multiple frames.

With Incremental garbage collection, Unity still uses the Boehm–Demers–Weiser garbage collector, but runs it in an incremental mode. Instead of doing a full garbage collection each time it runs, Unity splits up the garbage collection workload over multiple frames. So instead of having a single, long interruption of your program’s execution to allow the garbage collector to do its work, you have multiple, much shorter interruptions. While this does not make garbage collection faster overall, it can significantly reduce the problem of garbage collection “spikes” breaking the smoothness of your game by distributing the workload over multiple frames.

The following screenshots from the Unity Profiler, without and with incremental garbage collection enabled, illustrate how incremental collection reduces frame rate hiccups. In these profile traces, the light blue parts of the frame show how much time is used by script operations, the yellow parts show the time remaining in the frame until Vsync (waiting for the next frame to begin), and the dark green parts show the time spent for garbage collection.

Nonincremental garbage collection profile

Nonincremental garbage collection profile

Without incremental GC (above), you can see a spike interrupting the otherwise smooth 60fps frame rate. This spike pushes the frame in which garbage collection occurs well over the 16 millisecond limit required to maintain 60FPS. (In fact, this example drops more than one frame because of garbage collection.)

Incremental garbage collection profile

Incremental garbage collection profile

With incremental garbage collection enabled (above), the same project keeps its consistent 60fps frame rate, as the garbage collection operation is broken up over several frames, using only a small time slice of each frame (the darker green fringe just above the yellow Vsync trace).

Incremental garbage collection using left over time in frame

Incremental garbage collection using left over time in frame

This screenshot shows the same project, also running with incremental garbage collection enabled, but this time with fewer scripting operations per frame. Again, the garbage collection operation is broken up over several frames. The difference is that this time, the garbage collection uses more time each frame, and requires fewer total frames to finish. This is because we adjust the time allotted to the garbage collection based on the remaining available frame time if Vsync or Application.targetFrameRate is being used. This way, we can run the garbage collection in time which would otherwise be spent waiting, and thus get garbage collection “for free”.

Allocation and Garbage Collection

Менеджер памяти отслеживает зоны в куче, которые определены как неиспользуемые. При запросе нового блока памяти (допустим, при создании экземпляра объекта), менеджер выбирает неиспользуемую зону, из которой следует выделить блок, и затем удаляет выделенную память из зоны известного неиспользуемого пространства. Последующие запросы обрабатываются тем же способом, пока в неиспользуемой зоне будет достаточно места для выделения блока необходимого размера. Доступ к ссылочному элементу в куче может быть получен только пока есть ссылочные переменные, которые могут его найти. Если все ссылки к блоку памяти пропадут (т.е. ссылочные переменные были переназначены или они являются локальными переменными, которые теперь вне контекста), то занимаемая им память может быть ещё раз безопасно выделена.

Чтобы определить, какие блоки кучи больше не используются, менеджер памяти просматривает все активные ссылочные переменные и отмечает блоки, к которым они ссылаются как “live” (используемые). В конце поиска, любое пространство между используемыми блоками менеджером считается пустым и в будущем может быть использовано для выделения. По очевидным причинам, процесс обнаружения и освобождения неиспользуемой памяти известен как сборка мусора(Garbage Collection, или GC для сокращения).

Unity uses the Boehm–Demers–Weiser garbage collector, a stop-the-world garbage collector. Whenever Unity needs to perform garbage collection, it stops running your program code and only resumes normal execution when the garbage collector has finished all its work. This interruption can cause delays in the execution of your game that last anywhere from less than one millisecond to hundreds of milliseconds, depending on how much memory the garbage collector needs to process and on the platform the game is running on. For real-time applications like games, this can become quite a big issue, because you can’t sustain the consistent frame rate that smooth animation require when the garbage collector suspends a game’s execution. These interruptions are also known as GC spikes, because they show as spikes in the Profiler frame time graph. In the next sections you can learn more about how to write your code to avoid unnecessary garbage-collected memory allocations while running the game, so the garbage collector has less work to do.

Понимание автоматического управления памятью

При создании объекта, строки или массива, память для его хранения выделяется из центрального пула, который называется куча (heap). Когда использование элемента прекращается, память, которую он занимал, можно будет освободить и использовать для чего-нибудь ещё. В прошлом, выделение и освобождение этих блоков памяти с помощью вызовов соответствующих методов в основном лежало на плечах программистов. Теперь за вас памятью автоматически управляют среды выполнения, например, движок Mono у Unity. Автоматическое управление памятью требует меньше усилий при написании кода, чем прямое выделение / освобождение и значительно уменьшает потенциал для утечки памяти (ситуации, когда память была выделена, но впоследствии так и не была освобождена).

Запрос на сборку мусора

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

Маленькая куча с быстрой и частой сборкой мусора

Эта стратегия лучше всего подходит играм с долгими сессиями игрового процесса, когда стабильный FPS стоит на первом месте. Подобная игра обычно будет выделять небольшие блоки почаще, но эти блоки будут в использовании совсем не долго. Типичный размер кучи при использовании подобной стратегии на iOS составляет около 200 КБ, и сборка мусора занимает где-то 5 миллисекунд на iPhone 3G. Если размер кучи увеличить до 1 МБ, то сборка займёт где-то 7 миллисекунд. Следовательно, иногда будет разумно запрашивать сборку мусора раз в определённый интервал. В результате сборка будет проходить чаще чем строго необходимо, но сборка пройдёт быстрее с минимальным влиянием на игровой процесс:-

Однако вам следует использовать эту технику аккуратно и проверять статистку профайлера, чтобы убедиться, что это действительно уменьшает время сборки мусора для вашей игры.

Большая куча с медленной, но не частой сборкой мусора

Эта стратегия лучше всего подходит для игр, где выделения (и последующие сборки) памяти относительно редки и их можно провести во время пауз в игровом процессе. Таким образом кучу можно сделать максимально большой (но не на столько, чтобы перегрузить память системы, что может вызвать закрытие вашего приложения). Однако, среда запуска Mono по возможности избегает автоматического расширения кучи. Вы можете расширить кучу вручную путём предварительного выделения некоторого пространства во время запуска (т.е. вы вызываете “бесполезный” объект, который выделен только для влияния на менеджер памяти):-

Достаточно большую кучу не следует заполнять под завязку между этими паузами в игровом процессе, включающими в себя сборку. Когда такая пауза происходит, вы можете напрямую запросить сборку:-

Опять же, вам следует аккуратно использовать эту стратегию и обращать внимание на статистику профайлера, нежели просто предполагать, что желаемый эффект достигнут.

Повторно используемые пулы объектов

Если много случаев, в которых вы можете избежать генерации мусора просто уменьшив число создающихся и уничтожающихся объектов. Существуют различные типы объектов в играх, такие как снаряды, которые встречаются в игре снова и снова, даже если одновременно в игре их будет находиться небольшое количество. В таких случаях зачастую можно использовать объект ещё раз, нежели уничтожать старый и замещать его новым.

Читайте также: