Unity Mobile 60 FPS: Draw Calls & Zero-GC Budgeting
Snapshot
~90 sec
Shipping 60 FPS on mobile devices requires three strict engineering boundaries: keeping Draw Calls under 100 via GPU Instancing/SRP Batcher, capping frame budget at 16.6ms, and eliminating per-frame Garbage Collection (GC) allocations.
You will learn
- Master the 16.6ms Mobile Frame Budget breakdown (CPU, GPU, VSync, Thermal Throttling).
- Combine Draw Calls using SRP Batcher, GPU Instancing, and UI Texture Atlases.
- Eliminate per-frame heap allocations (LINQ, string concatenation, unpooled lambdas) that cause GC stutter.
Target outcome
You can profile and optimize a stuttering Unity mobile build to run at a rock-solid 60 FPS with zero thermal throttling.
Visual walkthrough
Visual walkthrough
~7 min
Mobile Frame Budget & Performance Limits
16.6ms Total Budget
For 60 FPS, CPU logic + GPU rendering must complete in under 16.6ms (aim for 12ms to prevent thermal throttling).
Draw Call (Batches) Cap
Target < 80-120 batches per frame on mid-range Android/iOS devices to keep CPU driver overhead low.
Zero GC Alloc in Update
0 B/frame heap allocation during active gameplay. Garbage Collection spikes trigger unavoidable 30-80ms micro-stutters.
ASTC Texture Compression
Always use ASTC 6x6 or 4x4 on mobile to drastically reduce GPU memory bandwidth and battery heat.
Eliminating Common Per-Frame Allocations in C#
By replacing LINQ, string concatenation, and allocating physics methods with NonAlloc buffers and StringBuilders, per-frame GC allocations drop to exactly 0 Bytes.
Languagecsharp // BAD: Allocates heap memory every single frame void BadUpdate() { // 1. String concatenation generates a new string object scoreText.text = "Score: " + score; // 2. Non-cached Physics RaycastAlloc / OverlapSphereAlloc Collider[] hits = Physics.OverlapSphere(transform.position, 5f); // 3. LINQ operations allocate iterators and delegates var activeEnemies = enemies.Where(e => e.IsAlive).ToList(); } // GOOD: Zero heap allocation per frame private static readonly NonAllocArray<Collider> hitsBuffer = new(32); private readonly StringBuilder scoreBuilder = new(32); void GoodUpdate() { // 1. Reusable StringBuilder or StringFormatter scoreBuilder.Clear().Append("Score: ").Append(score); scoreText.SetText(scoreBuilder); // 2. NonAlloc physics queries reusing pre-allocated buffer int hitCount = Physics.OverlapSphereNonAlloc(transform.position, 5f, hitsBuffer.Array); // 3. Simple for-loops over arrays or lists for (int i = 0; i < enemies.Count; i++) { if (enemies[i].IsAlive) ProcessEnemy(enemies[i]); } }Code annotations
Line 4
String GC Alloc
Strings are immutable reference types in C#. 'Score: ' + score allocates memory on the managed heap every tick.
Effect: Triggers frequent Garbage Collection pauses every few seconds on mobile.
Line 23
NonAlloc Physics Buffer
Physics.OverlapSphereNonAlloc populates an existing pre-allocated array without allocating a single byte.
Effect: CPU cost is predictable and managed memory footprint stays perfectly flat.
Mobile Optimization Checkpoint
A mobile game runs at 60 FPS for the first 2 minutes, but then drops to 38 FPS and the phone gets warm. The Profiler shows no sudden CPU spike. What is happening?
Expected reasoning
Thermal Throttling. The game was using 100% of GPU/CPU capacity without headroom (>15ms active frame time). The phone's OS throttled the clock speed to prevent overheating. Target 10-12ms active time to keep devices cool.