Unity IL2CPP 최적화 전략
IL2CPP(Intermediate Language To C++)는 Unity의 C# IL 바이트코드를 C++ 소스로 변환한 뒤 플랫폼 네이티브 코드로 컴파일하는 백엔드입니다. Mono보다 런타임 성능이 우수하고 iOS/콘솔 플랫폼에서 필수이지만, 빌드 시간이 길고 리플렉션에 제약이 있습니다.
1. IL2CPP 동작 원리
섹션 제목: “1. IL2CPP 동작 원리”C# 소스 → IL 바이트코드 → IL2CPP 변환 → C++ 코드 → 네이티브 바이너리 (csc/roslyn) (IL2CPP 툴) (MSVC/Clang/GCC)
- AOT(Ahead-of-Time) 컴파일: JIT 없음- 스트리핑: 사용하지 않는 타입/메서드 제거- 제네릭: 사용된 타입 조합만 코드 생성2. 스트리핑 제어 — link.xml
섹션 제목: “2. 스트리핑 제어 — link.xml”<!-- 스트리핑으로 제거되면 안 되는 타입/어셈블리 보존 --><linker> <!-- 어셈블리 전체 보존 --> <assembly fullname="UnityEngine.UI" preserve="all"/>
<!-- 특정 타입만 보존 --> <assembly fullname="Assembly-CSharp"> <type fullname="MyGame.NetworkMessage" preserve="all"/> <type fullname="MyGame.SaveData" preserve="all"/> </assembly>
<!-- 리플렉션으로 접근하는 타입 보존 --> <assembly fullname="System"> <type fullname="System.Collections.Generic.Dictionary`2" preserve="all"/> </assembly>
<!-- Newtonsoft.Json 사용 시 --> <assembly fullname="Newtonsoft.Json" preserve="all"/></linker>3. 리플렉션 제한 대응
섹션 제목: “3. 리플렉션 제한 대응”// IL2CPP에서 리플렉션은 스트리핑으로 실패할 수 있음
// ❌ 위험: 런타임 타입 검색var types = Assembly.GetExecutingAssembly() .GetTypes() .Where(t => t.HasAttribute<RegisteredComponent>());
// ✅ 안전: Source Generator나 수동 등록[assembly: RegisterComponent(typeof(PlayerController))][assembly: RegisterComponent(typeof(EnemyAI))]
// 또는 PreserveAttribute로 스트리핑 방지[UnityEngine.Scripting.Preserve]public class MyReflectedClass{ [UnityEngine.Scripting.Preserve] public void ReflectedMethod() { }}4. 제네릭 AOT 문제 해결
섹션 제목: “4. 제네릭 AOT 문제 해결”// IL2CPP는 사용된 제네릭 조합만 컴파일// 런타임에 새 조합이 생기면 실패
// 문제 발생 패턴var list = (IList)Activator.CreateInstance( typeof(List<>).MakeGenericType(someType)); // 위험
// 해결 1: 더미 메서드로 타입 조합 사전 등록[UnityEngine.Scripting.Preserve]static void RegisterGenericInstances(){ _ = new List<MyStruct>(); // List<MyStruct> AOT 생성 _ = new Dictionary<int, MyStruct>(); // 이 조합도 생성 // 실제로 호출되지 않아도 됨 — 컴파일러에게 힌트만 제공}
// 해결 2: 비제네릭 인터페이스로 추상화public interface ISerializable{ byte[] Serialize(); void Deserialize(byte[] data);}5. 빌드 최적화 설정
섹션 제목: “5. 빌드 최적화 설정”Player Settings > Other Settings:- Scripting Backend: IL2CPP- Api Compatibility Level: .NET Standard 2.1 (크기 감소)- IL2CPP Code Generation: Faster runtime (기본) 또는 Faster (smaller) builds (빌드 크기 우선)- Managed Stripping Level: - Minimal: 안전, 크기 절감 적음 - Low: 권장 시작점 - Medium: link.xml 필요 - High: 적극적 제거, 꼼꼼한 테스트 필요- Strip Engine Code: ON (엔진 미사용 모듈 제거)6. 런타임 성능 최적화
섹션 제목: “6. 런타임 성능 최적화”// IL2CPP에서 성능 향상 패턴
// 1. 박싱 제거: 값 타입 인터페이스 사용 주의interface IProcess { void Run(); }struct MyStruct : IProcess{ public void Run() { }}// (IProcess)new MyStruct() → 박싱 발생!// → class로 변경하거나 제네릭 제약 사용void Execute<T>(T item) where T : IProcess => item.Run(); // 박싱 없음
// 2. 델리게이트 캐싱// ❌ 매 프레임 델리게이트 생성void Update() => StartCoroutine(DoSomething());
// ✅ 델리게이트 캐시private Action _cachedAction;void Awake() => _cachedAction = DoSomething;void Update() => _cachedAction();
// 3. string.Format 대신 StringBuilder 또는 보간// ❌ GC 발생string msg = string.Format("HP: {0}/{1}", hp, maxHp);
// ✅ Span 기반 (IL2CPP에서도 동작)Span<char> buf = stackalloc char[32];hp.TryFormat(buf, out _);7. 빌드 크기 감소
섹션 제목: “7. 빌드 크기 감소”// 1. 불필요한 패키지 제거// Package Manager에서 미사용 패키지 언인스톨
// 2. 텍스처 압축 설정 확인// Player Settings > Android: ETC2 or ASTC
// 3. 스크립트 최적화 레벨// Build Settings > Development Build 해제 시// IL2CPP compiler configuration: Release
// 4. 어셈블리 분리로 스트리핑 효율화// Editor-only 코드를 별도 Assembly Definition으로 분리// [assembly: AssemblyIsEditorAssembly] → 런타임 빌드 제외IL2CPP 빌드에서 런타임 오류의 90%는 스트리핑 문제입니다. link.xml로 리플렉션 대상 타입을 보존하고, [Preserve] 어트리뷰트로 동적 접근 메서드를 보호하세요. 제네릭 AOT 문제는 더미 등록 메서드로 사전에 차단하고, Managed Stripping Level은 Medium부터 시작해 디바이스 테스트를 통해 조금씩 올리세요.