Version: 2021.2
Managed memory
Garbage collector overview

Memory allocator customization

When you are optimizing your application’s performance, one important factor to consider is the allocation of memory. This documentation provides information on Unity’s native memory allocator types, and describes scenarios where you can customize the allocators to improve performance. It is intended for users with a general understanding of allocators.

For a full reference of allocator types and their default values, see Customizing allocators.

An application uses memory allocators to balance performance and available memory space. If an application has a lot of spare memory, it can favour faster, memory-heavy allocators when it loads scenes and frames. However, if the application has limited memory, it needs to use that memory efficiently, even if that means using slower allocators. To help you get the best performance for different projects, you can customize Unity’s allocators to fit the size and requirements of each application.

Unity has five allocator types. Each type has a different algorithm for fitting allocations into blocks of memory, and is therefore useful for different allocations. The important difference between allocations is usually persistence, or allocation lifespan, which determines where an allocation should go. For example, a long-live (persistent) allocation goes to the heap and bucket allocators, while short-lived allocations go to the threadsafe linear and TLS allocators.

This table lists the algorithm and uses of each allocator type:

Allocator type Algorithm Used for
Dynamic heap Two Level Segregated Fit (TLSF) • Main allocator
• Gfx allocator
• Typetree allocator
• File cache allocator
• Profiler allocator
• Editor Profiler allocator (on Editor only)
Bucket Fixed size lock-free allocator As a shared allocator for small allocations for:

• Main allocator
• Gfx allocator
• Typetree allocator
• File cache allocator
Dual thread Redirects allocations based on size and thread ID • Main allocator
• Gfx allocator
• Typetree allocator
• File cache allocator
Thread Local Storage (TLS) stack LIFO stack Temporary allocations
Threadsafe linear Round robin FIFO Buffers for passing data to jobs

Note: The examples in this documentation use the memory usage reports that are written to the log when you close the player or Editor. To find your log files, follow the instructions on the log files page.

Dynamic heap, bucket, and dual thread allocators

This section reviews the functionality and customization scenarios for the dynamic heap, bucket and dual thread allocators.

概述

The dual thread allocator is a wrapper that combines dynamic and bucket allocators. More specifically, it combines:

  • Two dynamic heap allocators: A lock-free allocator for the main thread, and an allocator that is shared by all other threads, which locks on allocation and deallocation. Unity uses these allocators for allocations that are too large for the bucket allocator. The dynamic heap allocator uses memory blocks. Allocations that are equal to or greater than half a block go to the virtual memory system instead of the dynamic heap allocator.

  • A bucket allocator for small allocations. If the bucket allocator is full, allocation spills over into the dynamic heap allocator.

Dynamic heap allocator

The main heap allocator is the dynamic heap allocator. It applies the algorithm Two Level Segregated Fit (TLSF) to blocks of memory.

Each platform has a default block size, which you can customize. An allocation must be smaller than half a block. If an allocation is equal to or greater than half a block, it is too large for the dynamic heap allocator; instead, Unity uses the virtual memory API to make the allocation.

An example usage report for the dynamic heap allocator:

[ALLOC_DEFAULT_MAIN]
Peak usage frame count: [16.0 MB-32.0 MB]: 497 frames, [32.0 MB-64.0 MB]: 1 frames
Requested Block Size 16.0 MB
Peak Block count 2
Peak Allocated memory 54.2 MB
Peak Large allocation bytes 40.2 MB

In this example, the TLSF block size is set to 16 MB, and Unity has allocated two blocks. The peak usage of the allocator was 54.2MB. Of those 52.4MB, 40.2MB were not allocated in the TLSF block, and instead fell back to virtual memory. Most frames had 16–32MB of allocated memory, while one frame - likely the loading frame - peaked at 32–64MB of memory.

If you increased the block size the large allocation would stay in the dynamic heap rather than fall back into virtual memory. However, that block size could lead to memory waste, because the blocks might not be fully used.

To avoid using the typetree and cache allocators, set their size to 0. Allocations that would have used typetree and cache will instead fall back to the main allocator. This can cause more fragmentation, but saves the memory of those allocators’ memory blocks.

Bucket allocator

The bucket allocator is a fast lock-free allocator that performs small allocations. Usually, the bucket allocator is used as a first step to speed up small allocations, before they go to the heap allocator.

The allocator reserves blocks of memory for allocations. Each block is divided into subsections of 16KB. This is not configurable, and does not appear in the user interface. Each subsection is divided into allocations. The allocation size is a multiple of a configured fixed size, called granularity.

The following example setup demonstrates the process of reserving blocks for allocations:

Shared Bucket Allocator for the Windows, Mac and Linux player
Shared Bucket Allocator for the Windows, Mac and Linux player

In this setup, the total block size (Bucket Allocator Block Size) is 4MB, and the granularity of allocations (Bucket Allocator Granularity) is 16B. The first allocation is 16B, the second is 32B (2*16), then 48B, 64B, 80B, 96B, 112B, and 128B, for a total of eight buckets (Bucket Allocator BucketCount).

Each subsection contains a different number of buckets. To calculate the number of buckets in a subsection, divide the subsection size (16KB) by the granularity size. For example:

  • When the allocation granularity is 64B, 256 buckets fit in a subsection.
  • When the allocation granularity is 16B, 1,024 buckets fit in a subsection.

Bucket allocators produce different usage reports for a development build and a release build, because a development build, each allocation has a header of an additional 40B. The following diagram demonstrates the difference between development and release builds for 16B and 64B allocations:

Development and Release builds comparison
Development and Release builds comparison

The header is also the reason the allocator reports it is full after allocating only 2MB of its 4MB:

[ALLOC_BUCKET]
      Large Block size 4.0 MB
      Used Block count 1
      Peak Allocated bytes 2.0 MB
      Failed Allocations. Bucket layout:
        16B: 64 Subsections = 18724 buckets. Failed count: 3889
        32B: 17 Subsections = 3868 buckets. Failed count: 169583
        48B: 31 Subsections = 5771 buckets. Failed count: 39674
        64B: 28 Subsections = 4411 buckets. Failed count: 9981
        80B: 17 Subsections = 2321 buckets. Failed count: 14299
        96B: 6 Subsections = 722 buckets. Failed count: 9384
        112B: 44 Subsections = 4742 buckets. Failed count: 5909
        128B: 49 Subsections = 4778 buckets. Failed count: 8715

In a release build for the same project, the allocator block size is enough:

[ALLOC_BUCKET]
      Large Block size 4.0 MB
      Used Block count 1
      Peak Allocated bytes 3.3 MB

If the bucket allocator is full, the allocation falls back to another allocator. The usage report displays usage statistics, including how many allocations failed. If the report displays a fail count that increases linearly, it is likely that the failed allocations happen when calculating the frames, not the load. Fallback allocations are not a problem for a scene load, but they can impact performance if they happen when calculating frames.

To prevent these fallback allocations, increase the block size, and limit the new block size to match the frames’ peak usage, rather than the scene load peak usage. This prevents the block from becoming so large that it reserves a lot of memory that is then not available at runtime.

Tip: The Profiler allocators share an instance of a bucket allocator. You can customize this shared instance in the Shared Profiler Bucket Allocator.

Dual thread allocator

The dual thread allocator wraps a shared bucket allocator for small allocations, and two instances of the dynamic heap allocator: a lock-free allocator for the main thread, and an allocator that is shared by all other threads, but locks on allocation and deallocation.

You can customize the block sizes of the two dynamic heap allocators:

Main Allocator, with a custom value for Shared Thread Block Size
Main Allocator, with a custom value for Shared Thread Block Size

The usage report contains information for all three parts of the allocator. For example:

[ALLOC_DEFAULT] Dual Thread Allocator
  Peak main deferred allocation count 135
    [ALLOC_BUCKET]
      Large Block size 4.0 MB
      Used Block count 1
      Peak Allocated bytes 3.3 MB
    [ALLOC_DEFAULT_MAIN]
      Peak usage frame count: [16.0 MB-32.0 MB]: 8283 frames, [32.0 MB-64.0 MB]: 1 frames
      Requested Block Size 16.0 MB
      Peak Block count 2
      Peak Allocated memory 53.3 MB
      Peak Large allocation bytes 40.2 MB
    [ALLOC_DEFAULT_THREAD]
      Peak usage frame count: [64.0 MB-128.0 MB]: 8284 frames
      Requested Block Size 16.0 MB
      Peak Block count 2
      Peak Allocated memory 78.3 MB
      Peak Large allocation bytes 47.3 MB

Note: The Peak main deferred allocation count is the number of items in a deletion queue. The main thread must delete any allocation it made. If another thread deletes an allocation, that allocation is added to a queue. The allocation waits in the queue for the main thread to delete it. It is then counted as a deferred allocation.

TLS and threadsafe linear allocators

This section describes the functionality and customization scenarios for the Thread Local Storage (TLS) and threadsafe linear allocators.

概述

Unity has two allocators that work outside the dual thread allocator:

  • Thread Local Storage (TLS): A stack-based allocator for fast temporary allocations. This is the fastest allocator because it has almost no overhead. It also prevents fragmentation. It is Last In, First Out (LIFO)-based.

  • Threadsafe linear: A First In, First Out (FIFO) round-robin allocator that the temporary job allocation uses to pass short-lived memory between worker threads).

Thread Local Storage (TLS) stack allocator

Each thread uses its own fast stack allocator for temporary allocations. These allocations are very fast, with a lifespan of less than a frame.

The default block size for the temporary allocator is 4MB for the platforms and 16MB for the Unity Editor. You can customize these values.

Note: If the allocator use exceeds the configured block size, Unity increases the block size. The limit for this increase is twice the original size.

Main Thread Block Size custom value in the Fast Per Thread Temporary Allocators
Main Thread Block Size custom value in the Fast Per Thread Temporary Allocators

If a thread’s stack allocator is full, allocations fall back to the threadsafe linear job allocator. A few overflow allocations are fine: 1 to 10 in a frame, or a few hundred during load. However, if the numbers grow on every frame, you can increase the block sizes.

The information in the usage report can help you select a block size that is appropriate for your application. For example, in the following main thread usage report, the load peaks at 2.7MB, but the remaining frames are below 64KB. You can reduce the block size from 4MB to 64KB and allow the loading frame to spill over the allocations:

[ALLOC_TEMP_TLS] TLS Allocator
  StackAllocators :
    [ALLOC_TEMP_MAIN]
      Peak usage frame count: [16.0 KB-32.0 KB]: 802 frames, [32.0 KB-64.0 KB]: 424 frames, [2.0 MB-4.0 MB]: 1 frames
      Initial Block Size 4.0 MB
      Current Block Size 4.0 MB
      Peak Allocated Bytes 2.7 MB
      Overflow Count 0
    [ALLOC_TEMP_Job.Worker 18]

In this second example, the worker thread is not used for large temporary allocations. To save memory, you can reduce the worker’s block size to 32KB. This is especially useful on a multi-core machine, where each worker thread has its own stack:

[ALLOC_TEMP_Job.Worker 14]
      Initial Block Size 256.0 KB
      Current Block Size 256.0 KB
      Peak Allocated Bytes 18.6 KB
      Overflow Count 0

Threadsafe linear allocator

The worker threads in Unity use a round robin first-in-first-out (FIFO) algorithm for fast, lock-free allocations of work buffers for jobs. The jobs dispose of the buffers when done.

This allocator allocates blocks of memory, then linearly allocates memory within those blocks. Available blocks are held in a pool. When one block is full, the allocator fetches a new block from the pool. When the allocator no longer needs the memory in a block, it clears the block, and the block returns to the pool of available blocks. It is important to clear allocations quickly to make blocks available again, so a job should not stay allocated for more than a few frames.

You can customize the block size. The allocator allocates up to 64 blocks, as needed.

Default value for Fast Thread Shared Temporary Allocators for the Editor
Default value for Fast Thread Shared Temporary Allocators for the Editor

If all blocks are in use, or an allocation is too big for a block, the allocation falls back to the main heap allocator, which is much slower than the job allocator. A few overflow allocations are fine: 1 to 10 in a frame, or a few hundred, especially during load. If the overflow count grows with every frame, you can increase the block size to avoid fallback allocations. However, if you increase the block size too much (for example, to match peak use in events such as scene loading), you might leave a lot of memory unavailable during play.

例如:

[ALLOC_TEMP_JOB_4_FRAMES (JobTemp)]
  Initial Block Size 0.5 MB
  Used Block Count 64
  Overflow Count (too large) 0
  Overflow Count (full) 50408

In this example usage report, the 0.5MB block size was too small to accommodate the job memory that the application needed, and the full allocator caused a large number of allocations to overflow.

To check whether your build’s frame overflow is sufficient, run it for a short time and then for a longer time. If the overflow count remains steady, the overflow is a high watermark that occurs during load. If the overflow count increases with a longer run, the build is processing a per-frame overflow. In both cases, you can increase the blocksize to reduce the overflow, but the overflow is less critical during load than per frame.

Customizing allocators

To customize allocator settings, do one of the following:

  • Use the Editor:
    1. Select Project Settings > Memory Settings.
    2. Select the lock icon next to the value you want to edit.
Project Settings > Memory Settings, showing a selection of Player memory settings
Project Settings > Memory Settings, showing a selection of Player memory settings
  • Use command line arguments. To find the name of the allocator parameters you want to change, check the list of allocator settings the Editor and players print when they start up. For example, to change the block size of the main heap allocators, use -memorysetup-main-allocator-block-size=<new_value>

The allocator parameter names and their default values:

Allocator 描述 Parameter name Default value
Main Allocators The allocators Unity uses for most allocations.
Main Allocator The primary allocator Unity uses for most allocations.
Main Thread Block Size Block size of dedicated main thread allocator. memorysetup-main-allocator-block-size 16777216
Shared Thread Block Size Block size of shared thread allocator. memorysetup-thread-allocator-block-size 16777216
Gfx Allocator The allocator Unity uses for CPU allocations related to the Gfx system.
Main Thread Block Size Block size of the dedicated main thread Gfx allocator. memorysetup-gfx-main-allocator-block-size 16777216
Shared Thread Block Size Block size of the shared thread Gfx allocator. memorysetup-gfx-thread-allocator-block-size 16777216
Other Allocators
File Cache Block Size The file cache has its own allocator to avoid fragmentation. This is its block size. memorysetup-cache-allocator-block-size 4194304
Type Tree Block Size The type treess have their own allocator to avoid fragmentation due to many small allocations. This is its block size. memorysetup-typetree-allocator-block-size 2097152
Shared Bucket Allocator The bucket allocator that is shared between the main allocators.
Bucket Allocator Granularity Step size for buckets in the shared allocator. memorysetup-bucket-allocator-granularity 16
Bucket Allocator BucketCount Number of bucket sizes. memorysetup-bucket-allocator-bucket-count 8
Bucket Allocator Block Size Size of memory blocks used for buckets. memorysetup-bucket-allocator-block-size Editor: 8388608
Player: 4194304
Bucket Allocator Block Count Maximum number of blocks to be allocated. memorysetup-bucket-allocator-block-count Editor: 8
Player: 1
Fast Per Thread Temporary Allocators The Thread Local Storage (TLS) allocator that handles very short-lived allocations.
Main Thread Block Size The initial size for the main thread stack. memorysetup-temp-allocator-size-main Editor: 16777216
Player: 4194304
Job Worker Block Size Size of each job worker in the Unity job system. memorysetup-temp-allocator-size-job-worker E262144
Background Job Worker Block Size Size for each background worker. memorysetup-temp-allocator-size-background-worker 32768
Preload Block Size The preload manager stack size. memorysetup-temp-allocator-size-preload-manager Editor: 33554432
Player: 262144
Audio Worker Block Size Each audio worker thread’s stack size. memorysetup-temp-allocator-size-audio-worker 65536
Cloud Worker Block Size Cloud worker threads stack size. memorysetup-temp-allocator-size-cloud-worker 32768
Gfx Thread Blocksize The main render threads stack size. memorysetup-temp-allocator-size-gfx 262144
GI Baking Blocksize Each GI worker thread’s stack size. memorysetup-temp-allocator-size-gi-baking-worker 262144
NavMesh Worker Block Size Nav mesh worker threads stack size. memorysetup-temp-allocator-size-nav-mesh-worker 65536
Fast Thread Shared Temporary Allocators Fast linear allocator for short lived allocations shared between threads.
Job Allocator Block Size The round robin linear thread allocator Unity mainly uses for the job worker threads. memorysetup-job-temp-allocator-block-size 2097152
Background Job Allocator Block Size The linear allocator for the background workers that allows longer lived allocations. memorysetup-job-temp-allocator-block-size-background 21048576
Job Allocator Block Size on low memory platforms Platforms with less than 2GB memory use this size for both the job workers and the background jobs. memorysetup-job-temp-allocator-reduction-small-platforms 262144
Profiler Allocators Allocators that Unity uses exclusively for the Profiler so that they don’t interfere with the application’s allocation patterns.
Profiler Block Size The block size for the main part of the Profiler. memorysetup-profiler-allocator-block-size 16777216
Editor Profiler Block Size Block size for the Editor part of the Profiler. This is not present on players. memorysetup-profiler-editor-allocator-block-size 1048576
Shared Profiler Bucket Allocator Shared bucket allocator for the Profiler and Editor Profiler allocators.

Not present on low memory platforms.
Bucket Allocator Granularity Step size for buckets in the shared allocator. memorysetup-profiler-bucket-allocator-granularity 16
Bucket Allocator BucketCount Number of bucket sizes. For example, if the value is 4, the sizes are 16, 32, 48 and 64. memorysetup-profiler-bucket-allocator-bucket-count 8
Bucket Allocator Block Size Size of memory blocks used for buckets. memorysetup-profiler-bucket-allocator-block-size Editor: 33554432
Player: 4194304
Bucket Allocator Block Count Maximum number of blocks to be allocated. memorysetup-profiler-bucket-allocator-block-count Editor: 8
Player: 1

Tip: To ensure your settings improve performance, profile the application before and after making changes. See the Profiler overview page for more information. You can also check the memory usage reports. They are available in the log when you close the player or Editor. To find your log files, follow the instructions on the log files page.

Storing and reading the settings

Unity stores allocator settings in MemorySettings.asset, which populates the boot.config file with the modified settings at build time. This means new settings take effect at every build.

In the Editor, the boot.config is in the ProjectSettings folder. It gets updated every time Unity imports or changes MemorySettings.asset. New values for the Editor only take effect on the next Editor startup.

Managed memory
Garbage collector overview