From unity
Unity 6 performance profiling and optimization guide. Use when profiling, optimizing frame rate, reducing memory usage, debugging performance bottlenecks, or using the Profiler, Memory Profiler, or Frame Debugger. Based on Unity 6.3 LTS documentation.
npx claudepluginhub cdata/aria-skills --plugin unityThis skill uses the workspace's default tool permissions.
> Based on Unity 6.3 LTS (6000.3) official documentation
Profiles and optimizes Unity game performance using Profiler, Frame Debugger, Memory Profiler; covers CPU/GPU bottlenecks, GC reduction, object pooling, batching, LOD, occlusion culling, and platform tweaks.
Profiles and optimizes Unity projects using player-build data on target hardware to separate CPU, GPU, GC, loading, and memory issues, prioritizing fixes by impact.
Profiles Unity performance: starts/stops profiler, captures snapshots/frames, analyzes CPU/GPU/GC/memory bottlenecks, suggests optimizations for FPS drops and high GC.
Share bugs, ideas, or general feedback.
Based on Unity 6.3 LTS (6000.3) official documentation
Unity provides several built-in and package-based tools for profiling and optimization:
| Tool | Access | Purpose |
|---|---|---|
| Unity Profiler | Window > Analysis > Profiler | CPU, GPU, memory, rendering metrics per frame |
| Memory Profiler | Package: com.unity.memoryprofiler | Deep memory snapshots, comparison, leak detection |
| Frame Debugger | Window > Analysis > Frame Debugger | Inspect every draw call in a rendered frame |
| Profile Analyzer | Package: com.unity.performance.profile-analyzer | Statistical analysis across many Profiler frames |
The Profiler captures per-frame performance data. It runs in the Editor or connects to a Development Build on a target device.
The CPU module is the primary tool for finding performance bottlenecks.
Views:
Key threads:
Profiler markers:
PlayerLoop, Update.ScriptRunBehaviourUpdate, Physics.Simulate)Unity.Profiling.ProfilerMarker (see references/profiler-guide.md)Deep Profiling:
ProfilerMarker for targeted instrumentation insteadCall stacks for GC allocations:
Displays per-frame memory summary:
| Module | Key Metrics |
|---|---|
| Rendering | Batches, SetPass calls, triangles, vertices, shadow casters |
| Physics | Active/sleeping rigidbodies, contacts, broadphase pairs |
| Audio | Playing sources, DSP load, memory usage |
| UI | Canvas rebuilds, layout rebuilds, batches |
| Video | Video player decode time |
Development Build and Autoconnect Profiler in Build Settings. The Profiler connects automatically when the build launches.Every managed allocation contributes to GC pressure. In hot paths (Update, FixedUpdate, LateUpdate), target zero allocations.
| Pattern | Problem | Fix |
|---|---|---|
String concatenation (+) | Creates new string each time | Use StringBuilder or string.Create |
| LINQ queries | Iterator allocation, closure boxing | Use manual for/foreach loops |
| Lambda closures capturing locals | Compiler generates a class allocation | Cache delegates or use static lambdas |
Boxing (int to object) | Allocates on managed heap | Use generic APIs, avoid object params |
foreach on non-generic IEnumerable | Enumerator boxing | Use typed collections or for loops |
params arrays | Array allocated each call | Provide overloads or use Span<T> |
ToString() on value types | Allocates string | Cache or avoid in hot paths |
Textures (often the largest memory consumer):
Texture.streamingMipmaps) to limit VRAM usageMeshes:
Audio:
Decompress on Load -- Fast playback, high memory (short SFX only)Compressed in Memory -- Lower memory, moderate CPU (medium clips)Streaming -- Minimal memory, continuous I/O (music, ambient)Load Type per-clip based on length and frequency of useUnloading:
Resources.UnloadUnusedAssets() -- Frees assets with no references (slow, use during loading screens)Addressables.Release() to decrement ref count and unload when zeroReuse frequently created/destroyed objects to avoid GC pressure and instantiation cost.
using UnityEngine.Pool;
public class BulletSpawner : MonoBehaviour
{
[SerializeField] private Bullet bulletPrefab;
private ObjectPool<Bullet> _pool;
private void Awake()
{
_pool = new ObjectPool<Bullet>(
createFunc: () => Instantiate(bulletPrefab),
actionOnGet: b => b.gameObject.SetActive(true),
actionOnRelease: b => b.gameObject.SetActive(false),
actionOnDestroy: b => Destroy(b.gameObject),
defaultCapacity: 20,
maxSize: 100
);
}
public Bullet GetBullet() => _pool.Get();
public void ReturnBullet(Bullet b) => _pool.Release(b);
}
See also: unity-scripting for additional pooling patterns.
GetComponent results -- Call in Awake/Start, store in a fieldCompareTag("Tag") -- Avoids string allocation from gameObject.tagFind methods in hot paths -- Find, FindObjectOfType, FindObjectsByType are expensive; cache referencesAwaitable -- Non-blocking scene loading, asset loading, web requests// Cache component references
private Rigidbody _rb;
private void Awake() => _rb = GetComponent<Rigidbody>();
// Use CompareTag instead of string comparison
if (other.CompareTag("Enemy")) { /* ... */ }
AddForce on idle objects.Physics.RaycastNonAlloc, Physics.OverlapSphereNonAllocBatching Static to merge meshes at build time.The Frame Debugger (Window > Analysis > Frame Debugger) lets you step through every draw call in a frame.
Workflow:
Common uses:
Unity 6 uses IL2CPP by default for most platforms. Key performance considerations:
sealed classes/methods enable devirtualization and inlining[StructLayout(LayoutKind.Sequential)] for data passed to native code. Keep structs small (under 16 bytes) for efficient passing.// sealed enables devirtualization -- IL2CPP can inline the call
public sealed class FastEnemy : EnemyBase
{
public override void Think()
{
// This can be inlined by IL2CPP when called through a typed reference
}
}
// Switch quality level at runtime (e.g., from settings menu)
QualitySettings.SetQualityLevel(qualityIndex, applyExpensiveChanges: true);
// Adjust individual settings
QualitySettings.shadows = ShadowQuality.Disable;
QualitySettings.vSyncCount = 0;
Application.targetFrameRate = 60;
The Adaptive Performance package (com.unity.adaptiveperformance) dynamically adjusts quality based on device thermal state and performance metrics:
IAdaptivePerformance interface for custom scaling logicusing UnityEngine.SceneManagement;
public async Awaitable LoadSceneAsync(string sceneName)
{
AsyncOperation op = SceneManager.LoadSceneAsync(sceneName);
op.allowSceneActivation = false;
while (op.progress < 0.9f)
{
// Update loading bar: op.progress / 0.9f
await Awaitable.NextFrameAsync();
}
op.allowSceneActivation = true;
}
// Process a large list over multiple frames
private IEnumerator ProcessChunked<T>(List<T> items, int chunkSize, System.Action<T> process)
{
for (int i = 0; i < items.Count; i++)
{
process(items[i]);
if ((i + 1) % chunkSize == 0)
yield return null; // Resume next frame
}
}
using System.Diagnostics;
private readonly Stopwatch _sw = new();
private const float BUDGET_MS = 4f; // Portion of frame budget
private void Update()
{
_sw.Restart();
while (_workQueue.Count > 0 && _sw.Elapsed.TotalMilliseconds < BUDGET_MS)
{
ProcessNextItem(_workQueue.Dequeue());
}
}
using UnityEngine.SceneManagement;
// Load adjacent area additively without blocking
public async Awaitable StreamArea(string sceneName)
{
AsyncOperation op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
while (!op.isDone)
await Awaitable.NextFrameAsync();
}
// Unload area when player moves away
public async Awaitable UnloadArea(string sceneName)
{
AsyncOperation op = SceneManager.UnloadSceneAsync(sceneName);
while (!op.isDone)
await Awaitable.NextFrameAsync();
// Reclaim memory from unloaded assets
Resources.UnloadUnusedAssets();
}
using Unity.Profiling;
public class AISystem : MonoBehaviour
{
private static readonly ProfilerMarker s_AIUpdate = new("AISystem.Update");
private static readonly ProfilerMarker s_Pathfind = new("AISystem.Pathfind");
private void Update()
{
using (s_AIUpdate.Auto())
{
foreach (var agent in _agents)
{
using (s_Pathfind.Auto())
{
agent.RecalculatePath();
}
}
}
}
}
| Symptom | Likely Cause | Fix |
|---|---|---|
| Low FPS, high CPU main thread | Too much logic in Update | Profile with Profiler; optimize, jobify, or spread across frames |
| GC spikes (frame hitches) | Allocations in hot paths | Pool objects, cache, avoid LINQ/string concat in Update |
| High draw calls / SetPass calls | No batching or too many materials | Enable SRP Batcher, use GPU instancing, atlas textures |
| Memory keeps growing | Asset leaks, unreleased Addressables | Call Resources.UnloadUnusedAssets(), check Addressable ref counts |
| Physics lag | Too many active colliders or contacts | Simplify colliders, use collision matrix layers, increase timestep |
| Shader compilation stutter | Shader variants compiled on demand | Use shader warmup (ShaderVariantCollection.WarmUp()), reduce variants |
| Long loading times | Synchronous asset loading | Use Addressables.LoadAssetAsync or Resources.LoadAsync |
| Render thread bound | Too many draw calls or GPU commands | Reduce batches, simplify shaders, lower resolution |
| What | Why It Hurts | Fix |
|---|---|---|
Debug.Log in production | String formatting + managed alloc + I/O | Use [Conditional("UNITY_EDITOR")] or strip via scripting defines |
Camera.main in Update | Calls FindGameObjectWithTag internally each time | Cache in Awake or Start |
Instantiate/Destroy every frame | GC allocation + native overhead per call | Use ObjectPool<T> |
GetComponent<T>() in Update | Lookup cost each frame | Cache in Awake/Start |
OnGUI for game UI | IMGUI redraws every frame, heavy GC | Use UI Toolkit or uGUI (Canvas) |
| Deep Profile for perf tests | 10-100x overhead invalidates results | Use ProfilerMarker for targeted instrumentation |
Allocating in FixedUpdate | Runs at physics rate (50Hz default), amplifies GC | Pre-allocate, pool, use NativeArray |
SendMessage / BroadcastMessage | Reflection-based, allocates | Use direct method calls, events, or UnityEvent |
| API | Namespace | Purpose |
|---|---|---|
ProfilerMarker | Unity.Profiling | Zero-overhead custom profiler instrumentation |
ProfilerRecorder | Unity.Profiling | Read profiler counter values at runtime |
Profiler.BeginSample / EndSample | UnityEngine.Profiling | Legacy custom profiler samples (stripped in non-dev builds) |
ObjectPool<T> | UnityEngine.Pool | Generic object pooling |
ListPool<T> / HashSetPool<T> | UnityEngine.Pool | Temporary collection pooling |
NativeArray<T> | Unity.Collections | Unmanaged, GC-free array for Jobs/Burst |
NativeList<T> | Unity.Collections | Unmanaged, resizable list |
Stopwatch | System.Diagnostics | High-resolution timing |
ShaderVariantCollection | UnityEngine.Rendering | Shader warmup to prevent compilation stutter |
QualitySettings | UnityEngine | Runtime quality level switching |