Skip to content

Standards

Mikhail Agapov edited this page Aug 8, 2023 · 19 revisions

Memory Allocations

Minimize Garbage Collection (GC) Pressure

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:

Implement object pooling to reuse frequently created and destroyed objects.

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:

Pools

Thread Safe Pools

Avoid permutations of collections

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.

Be Mindful of Serialization and Deserialization

  • 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.

Be Mindful of Boxing and Unboxing

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.

Avoid String Concatenation

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.

Optimize Data Structures:

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.

Use Structs for Small Data

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.

Dispose of Resources Properly:

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.

Profile and Optimize

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.

Use the Unity Memory Profiler:

Utilize the Unity Memory Profiler to analyze and optimize memory allocations.

Do not use LINQ, it allocates too much memory.

See: https://www.jacksondunstan.com/articles/4840

Performance

Tests

Newly written code should always utilise a reasonably high test coverage, we use the NUnitand NSubstitute testing framework.

Error Handling & Reporting