Things I Learned About
Unity 6 Performance
I wanted to share some messy notes from profiling and debugging our Unity 6 project recently. Some of these are basic mistakes I still catch myself making when rushing, others are small habits that saved our frame rates.
1. Allocations Inside Update
Creating new objects inside Update() forces the Garbage Collector to run constantly. You will get micro stutters and sudden spikes out of nowhere.
Bad:
void Update()
{
var list = new List<int>();
list.Add(1);
}
Good:
List<int> cachedList = new List<int>();
void Update()
{
cachedList.Clear();
cachedList.Add(1);
}
Reusing the same list instance keeps memory allocations at zero during runtime.
2. Event Subscriptions Churn
Subscribing and unsubscribing in OnEnable and OnDisable seems fine on small scripts. But if you have hundreds of objects toggling every second, event registration overhead builds up fast.
Bad:
void OnEnable()
{
EventManager.OnSomething += Handle;
}
void OnDisable()
{
EventManager.OnSomething -= Handle;
}
Good:
void Awake()
{
EventManager.OnSomething += Handle;
}
void OnDestroy()
{
EventManager.OnSomething -= Handle;
}
Bind once when the component wakes up and unbind when destroyed. It drastically reduces lifecycle churn.
3. GetComponent in Hot Paths
Calling GetComponent in Update or heavy loops is an easy way to kill performance.
Bad:
void Update()
{
GetComponent<Rigidbody>().AddForce(Vector3.up);
}
Good:
Rigidbody rb;
void Awake()
{
rb = GetComponent<Rigidbody>();
}
void Update()
{
rb.AddForce(Vector3.up);
}
Cache the reference in Awake or Start so Unity does not have to perform lookup searches every frame.
4. Coroutine Spam
Spinning up hundreds of coroutines for simple timers adds unnecessary heap allocations and background ticking overhead.
Bad:
IEnumerator Timer()
{
yield return new WaitForSeconds(1f);
DoSomething();
}
Good:
Use a preallocated object pool for timers or run a central manager script that ticks active timers in a single loop.
5. Unoptimized Physics Queries
Running broad physics checks every frame drains CPU cycles fast, especially on lower end hardware.
Bad:
void Update()
{
Physics.OverlapSphere(transform.position, 5f);
}
Good:
Throttle physics queries to run every few frames, filter with LayerMasks, or use non allocating methods like Physics.OverlapSphereNonAlloc with preallocated arrays.
6. String Allocations in Hot Code
String concatenation inside hot paths creates garbage allocations every frame.
Bad:
void Update()
{
Debug.Log("Score: " + score);
}
Good:
if (debugMode)
{
Debug.Log($"Score: {score}");
}
Only format or log strings when values actually change, or wrap logs in conditional checks so production builds stay clean.
7. Blind Optimization
Guessing where performance bottlenecks are usually leads to wasted effort or breaking stuff that worked fine.
Bad:
Blindly refactoring code because it looks slow without checking profiler data.
Good:
Fire up the Unity Profiler. Attach it to actual target hardware, record frame spikes, fix the worst offender, and measure again.
Sticky Note Checklist on My Monitor
- Cache components and delegates early
- Zero allocations inside Update loops
- Object pool everything that spawns often
- Avoid GetComponent in hot execution paths
- Limit active coroutines
- Filter physics queries with layers and masks
- Profile first, then fix one spike at a time
- Spread heavy operations across multiple frames
These are not strict rules carved in stone, just handy reminders I keep around. Hopefully some of these help you spot easy wins in your own Unity projects.
Top comments (0)