-
Notifications
You must be signed in to change notification settings - Fork 11
Standards
Reduce the frequency and size of memory allocations to minimize the impact on garbage collection. Reuse objects and data structures whenever possible instead of creating new ones. Avoid creating temporary objects within frequently called methods or update loops. Heap Allocation Viewer is a perfect plugin for Rider that shows all types of allocations. Use Object Pooling:
Pooling allows you to recycle objects instead of allocating and deallocating them, reducing GC overhead. Try to assume the upper estimation of the initial pool size to avoid runtime allocations as much as possible. We have some custom pooling classes available under Utility:
Preferring loose contracts (IReadOnlyCollection
, IReadOnlyList
, etc) over final types (array
, List<T>
) makes your classes and functions much more flexible.
Don't try to match the contract by calling ToList()
and ToArray()
, avoid them by all means.
Use Span<T>
, Memory<T>
, and ArraySegment<T>
if you require a slice of an original array.
Use stackalloc
if you require a small temporary fixed-sized array.
Use Unity Native Collections if compatibility with Jobs or Unity's low-level API is required. If the size of the collection is known beforehand, consider using Fixed
collections which are fully allocated on stack and, thus, produce no GC
pressure.
- JSON. Using JSON is not a great idea overall. It creates a significant GC Pressure. If it is still needed consider reusing the existing objects and filling them with data instead of creating new ones. Consider using Unity's
JsonUtility
as it's more performant. - Protobuf. Instead of creating a new instance, parse into the existing one.
Avoid the use of boxing and unboxing operations, which can create unnecessary memory allocations. Use generic collections and data structures to avoid boxing of value types.
Avoid the use of object
.
Avoid passing a structure
as an interface.
String concatenation using the "+" operator can create multiple intermediate string objects.
Instead, use StringBuilder
or string.Format
for efficient string concatenation and formatting. Avoid any string manipulation in hot paths.
If it is expected that StringBuilder
will be used frequently cache it, clear and then reuse it.
Be Mindful of Lambdas/Delegates overhead
Avoid unintentional variable captures: every time you invoke a function with such a delegate a new instance of a class is created by the compiler leading to the temporary allocation.
Use static
keyword for a lambda
or a static
local function to explicitly indicate that there is no intention for capture: it will also help the compiler with caching a delegate so it will be instantiated only once.
Choose data structures that are efficient in terms of memory usage and access patterns. For example, use lists or arrays instead of dictionaries or hash sets when a key-value mapping is not required.
For small, simple data structures, consider using structs instead of classes. Structs are value types and are allocated on the stack, reducing memory overhead and GC pressure.
If your code uses objects that implement the IDisposable interface (e.g., FileStream
, Texture2D
, UnityWebRequest
), ensure proper disposal.
Call Dispose()
or use the "using" statement to release unmanaged resources promptly.
Always profile your code to identify memory allocation hotspots and optimize them.
Unity provides profiling tools like the Unity Profiler to help you identify performance bottlenecks and memory issues.
Utilize the Unity Memory Profiler to analyze and optimize memory allocations.
See: https://www.jacksondunstan.com/articles/4840
Newly written code should always utilise a reasonably high test coverage, we use the NUnitand NSubstitute testing framework.