The Hidden Performance Trap That's Killing Your Unity Game

By Fuat Can Koseoglu,

Your city builder runs perfectly in Unity's editor. You build for mobile and it becomes a slideshow. The killer? Something you'd never suspect. The Problem: Invisible Struct Copying • Many Unity developers choose structs over classes for performance • But improper struct usage creates significant overhead through excessive data copying • IL2CPP compilation particularly suffers from this issue • Every by-value parameter can trigger a full struct copy • Because IL2CPP is AOT, it can preserve more copies than the desktop JIT—especially under Faster (smaller) builds—so design APIs to avoid copies rather than relying on elision. The Impact: Death by a Thousand Copies Consider a typical tile update pipeline: • Each processing step accepts the tile by value • Creates an updated copy and returns it • IL2CPP copies the struct multiple times per operation: • Copy when passing as parameter • Copy when returning result • Copy during assignment The Numbers: • 24-byte tile struct across 10,000 tiles • ≈0.7 MiB/frame → ~41 MiB/s at 60 FPS — avoidable memory traffic that wastes bandwidth and battery on mobile. The Solution: Smart Reference Usage • Use Reference Semantics: • Pass mutable structs by ref to modify in-place • Use in (readonly references) for read-only access without copies • Avoid calling non-readonly members on in parameters to prevent defensive copies Consider Classes for Shared Mutable State: • When multiple systems mutate the same entity, references beat repeated struct copies • Pointer-sized reference (64-bit) per reference, plus object header — amortize with pooling • You trade alloc/GC for eliminated copy cost; pool aggressively on hot paths Performance Comparison ❌ Excessive Copying: • Wasted CPU cycles on data movement • Memory bandwidth consumed by redundant copies ✅ Reference Semantics: • CPU focused on actual computation • Minimal memory movement • Consistent performance across build targets Unity-Specific Guidelines • Profile device builds with IL2CPP Code Generation = Faster runtime; compare against Faster (smaller) builds to understand the perf/size trade. • Add constraints (e.g., where T : struct/class/unmanaged) to avoid slow fully-shared generics • Structs excel for immutable data and cache-friendly arrays • Choose data structures based on mutation patterns and sharing needs Key Principles • Frequent mutation + by-value passing = performance trap • Large structs aren't inherently slow when used with arrays or references • Readonly structs + readonly members prevent defensive copies on read paths • Always profile on device - IL2CPP behavior differs from editor The most expensive code is the code that does nothing. Don't let invisible copying destroy your frame rate. Follow for O(1) access to game dev tips! What's your worst mobile performance surprise?