Introduction
In Unity, frequent memory allocations in performance-critical methods, such as Update
, can lead to unnecessary garbage collection (GC) and frame drops. One common mistake is allocating new collections each frame, which can cause performance issues over time.
Problem: Allocating Lists Every Frame
Consider this example:
using System.Collections.Generic;
using UnityEngine;
public class ListTest : MonoBehaviour
{
private List<Enemy> _enemies = new List<Enemy>();
private void Start()
{
for (int i = 0; i < 1000; i++)
{
EnemyType enemyType = (EnemyType)Random.Range(0, 1);
_enemies.Add(new Enemy(enemyType));
}
}
private void Update()
{
FindEnemiesByType(EnemyType.Water);
}
private List<Enemy> FindEnemiesByType(EnemyType enemyType)
{
// New allocation every frame
List<Enemy> matchingEnemies = new List<Enemy>();
for (int i = 0; i < _enemies.Count; i++)
{
if (_enemies[i].EnemyType == enemyType)
matchingEnemies.Add(_enemies[i]);
}
return matchingEnemies;
}
public class Enemy
{
private EnemyType _enemyType;
public EnemyType EnemyType => _enemyType;
public Enemy(EnemyType type)
{
_enemyType = type;
}
}
public enum EnemyType
{
Fire,
Water,
}
}
This code allocates a new list every time it runs, which, when called every frame, leads to frequent memory allocations and GC overhead. Allocating memory each frame can trigger garbage collection more often, resulting in performance hiccups like frame drops.
Solution: Reuse Preallocated Lists
To avoid this, preallocate the list and clear it before each use:
using System.Collections.Generic;
using UnityEngine;
public class ListTest : MonoBehaviour
{
private List<Enemy> _enemies = new List<Enemy>();
// Preallocated list
private List<Enemy> _matchingEnemies = new List<Enemy>();
private void Start()
{
for (int i = 0; i < 1000; i++)
{
EnemyType enemyType = (EnemyType)Random.Range(0, 1);
_enemies.Add(new Enemy(enemyType));
}
}
private void Update()
{
FindEnemiesByType(EnemyType.Water);
}
private List<Enemy> FindEnemiesByType(EnemyType enemyType)
{
_matchingEnemies.Clear(); // Reuse the list
for (int i = 0; i < _enemies.Count; i++)
{
if (_enemies[i].EnemyType == enemyType)
_matchingEnemies.Add(_enemies[i]);
}
return _matchingEnemies;
}
public class Enemy
{
private EnemyType _enemyType;
public EnemyType EnemyType => _enemyType;
public Enemy(EnemyType type)
{
_enemyType = type;
}
}
public enum EnemyType
{
Fire,
Water,
}
}
By clearing and reusing the preallocated list, we avoid creating new memory allocations every frame. This reduces the pressure on the garbage collector and minimizes performance spikes during gameplay.
Conclusion
Minimizing memory allocations, especially in frequently called methods, is essential for maintaining performance in Unity. Preallocating and reusing collections is a simple yet effective optimization that can prevent frame drops and GC spikes, leading to smoother gameplay and better user experiences.