Skip to content

Unity CoreCLR 마이그레이션 가이드

Unity는 2026년 로드맵을 통해 장기간 사용되어 온 Mono 런타임을 완전히 제거하고, 최신 CoreCLR(.NET 10) 기반 런타임으로 전환합니다. Unity 6.8부터는 데스크톱 플레이어와 에디터 모두 CoreCLR 단일 런타임으로 작동하며, 모바일/콘솔/WebGL은 기존과 같이 IL2CPP를 유지합니다.

이 전환은 단순한 업그레이드가 아니라, JIT 품질과 GC 아키텍처가 근본부터 달라지는 구조적 변화입니다. 실측 기준으로 동일 시뮬레이션 코드에서 2.5배 ~ 최대 15배 성능 향상이 보고되고 있으며, BinaryFormatter·AppDomain 등 일부 API는 컴파일 오류를 유발합니다. 따라서 지금부터 코드베이스를 “런타임 중립적”으로 정리해 두는 것이 중요합니다.

배경 / 문제 상황: Mono는 왜 느린가

Section titled “배경 / 문제 상황: Mono는 왜 느린가”

Unity는 2006년부터 오픈소스 Mono 프레임워크를 C# 런타임으로 사용해 왔습니다. 하지만 2016년 이후 Microsoft의 .NET Core / CoreCLR은 대규모로 개선된 반면, Unity의 Mono 포크는 단절된 채 유지되면서 성능 격차가 크게 벌어졌습니다.

Mono의 JIT는 CoreCLR의 RyuJIT에 비해 레지스터 할당, 인라이닝, 루프 최적화 품질이 현저히 낮습니다.

  • Marek Fiser 실측 기준, 동일 게임 시뮬레이션 릴리스 빌드:
    • Mono: 30초 / .NET 10: 12초 (약 2.5배)
  • BCL(Base Class Library) 집약적 워크로드에서는 최대 15배 격차.
항목MonoCoreCLR
GC 종류Boehm-Demers-Weiser (보수적)정밀(Precise) GC
객체 이동비이동식이동식(Compacting)
스캔 방식모든 스레드 스택 전수 스캔관리 코드 영역만 추적
단편화누적됨컴팩션으로 해소

Mono의 Boehm GC는 보수적이고 비이동식이므로, 장시간 구동되는 게임에서 메모리 단편화가 누적되기 쉽습니다. CoreCLR은 정밀 이동식 GC를 사용하므로 단편화가 완화되지만, 객체가 이동하기 때문에 unsafe 코드에서 포인터 핀(pin) 처리가 필수입니다.

CoreCLR은 System.Runtime.Intrinsics 네임스페이스를 통해 SSE/AVX/NEON 명령어를 직접 활용할 수 있지만, Mono에는 이러한 지원이 없습니다. 벡터/수학 계산이 많은 시뮬레이션 코드는 CoreCLR에서 수십 배 빨라지는 경우가 흔합니다.

버전시기핵심 변화
Unity 6.52026년 상반기Editor Lifecycle API 도입, InstanceIDEntityId 전환 시작
Unity 6.62026년.NET 10 + C# 14 채택, Fast Enter Play Mode 기본값 전환
Unity 6.7 LTS2026년 하반기CoreCLR 기반 데스크톱 플레이어 실험적 릴리스. 마지막 Mono 릴리스
Unity 6.82026년 말~2027년CoreCLR 에디터 + 비실험적 CoreCLR. Mono 완전 제거

런타임에 무관하게 동작하도록 순수 C# 로직을 Unity API에서 분리하고, Span<T>/ReadOnlySpan<T>를 우선 사용합니다.

using System;
namespace GameSimulation
{
public readonly struct Vector3Sim
{
public readonly float X;
public readonly float Y;
public readonly float Z;
public Vector3Sim(float x, float y, float z) => (X, Y, Z) = (x, y, z);
}
public sealed class MovementSimulator
{
public static void UpdatePositions(
Span<Vector3Sim> positions,
ReadOnlySpan<Vector3Sim> velocities,
float deltaTime)
{
for (int i = 0; i < positions.Length; i++)
{
positions[i] = new Vector3Sim(
positions[i].X + velocities[i].X * deltaTime,
positions[i].Y + velocities[i].Y * deltaTime,
positions[i].Z + velocities[i].Z * deltaTime);
}
}
}
}

2) Editor Lifecycle API - 정적 상태 초기화 대응

Section titled “2) Editor Lifecycle API - 정적 상태 초기화 대응”

CoreCLR 에디터는 플레이 모드 진입 시 도메인 리로드를 건너뛸 수 있으므로, 정적 필드가 이전 상태를 들고 있을 수 있습니다. 새 Lifecycle API로 명시적 초기화/정리를 선언합니다.

using System.Collections.Generic;
using Unity.Scripting.LifecycleManagement;
using UnityEngine;
[AutoStaticsCleanup]
public static class GameRegistry
{
[AutoStaticsCleanup]
private static Dictionary<int, GameObject> s_RegisteredObjects = new();
[AutoStaticsCleanup]
private static bool s_IsInitialized = false;
}
public static class SceneBootstrapper
{
[BeforeCodeUnloading]
private static void OnBeforeUnload()
{
CancelBackgroundJobs();
}
[AfterCodeReloadSerialization]
private static void OnAfterReload()
{
InitializeSystems();
}
private static void CancelBackgroundJobs() { }
private static void InitializeSystems() { }
}

BinaryFormatter는 Unity 6.8에서 컴파일 오류를 유발합니다. JsonUtility 또는 System.Text.Json으로 교체합니다.

using System.IO;
using UnityEngine;
[System.Serializable]
public class PlayerSaveData
{
// Unity 인스펙터 직렬화 관례상 필드는 camelCase를 유지합니다.
public int level;
public float health;
public string playerName;
public int[] inventory;
}
public static class SaveSystem
{
private static readonly string SavePath =
Path.Combine(Application.persistentDataPath, "save.json");
public static void Save(PlayerSaveData data)
{
string json = JsonUtility.ToJson(data, prettyPrint: false);
File.WriteAllText(SavePath, json);
}
public static PlayerSaveData Load()
{
if (!File.Exists(SavePath))
{
return new PlayerSaveData();
}
return JsonUtility.FromJson<PlayerSaveData>(File.ReadAllText(SavePath));
}
}

4) CoreCLR 이동식 GC 대응 - unsafe 포인터 핀 처리

Section titled “4) CoreCLR 이동식 GC 대응 - unsafe 포인터 핀 처리”

CoreCLR GC는 객체를 이동시키므로, 관리 배열의 포인터를 네이티브로 넘길 때 반드시 fixed 블록이나 GCHandle.Alloc(..., GCHandleType.Pinned)로 고정해야 합니다.

using System;
using System.Runtime.InteropServices;
public static class UnsafeShortLivedPin
{
// 짧은 네이티브 호출은 fixed 블록으로 충분합니다.
public static unsafe void SafeNativeCall(byte[] data)
{
fixed (byte* ptr = data)
{
// NativeLib.Process(ptr, data.Length);
_ = ptr;
}
}
}
public sealed class PinnedBuffer : IDisposable
{
// 장시간 유지해야 하는 핀은 GCHandle.Pinned로 명시적으로 관리합니다.
private GCHandle _pinnedHandle;
public void Pin(byte[] buffer)
{
if (_pinnedHandle.IsAllocated)
{
_pinnedHandle.Free();
}
_pinnedHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
}
public unsafe byte* GetPointer() =>
(byte*)_pinnedHandle.AddrOfPinnedObject();
public void Dispose()
{
if (_pinnedHandle.IsAllocated)
{
_pinnedHandle.Free();
}
}
}

Update() 루프에서 피해야 할 패턴

Section titled “Update() 루프에서 피해야 할 패턴”
패턴문제대안
GetComponent<T>() 매 프레임탐색 오버헤드Awake()에서 캐싱
string.Format(), 문자열 보간매 프레임 힙 할당StringBuilder 재사용
LINQ Where, SelectIEnumerable 래퍼 할당직접 for/foreach
new List<T>() 반복 생성힙 할당 폭증사전 할당 후 Clear() 재사용
AppDomain.CurrentDomain.GetAssemblies()CoreCLR에서 불안정CurrentAssemblies.GetLoadedAssemblies()
  • 데스크톱 (Windows / macOS / Linux)
    • Unity 6.7: Mono(기본) 또는 CoreCLR(실험적) 선택 가능
    • Unity 6.8 이후: CoreCLR 전용
  • 모바일 (iOS / Android) / 콘솔 / WebGL
    • Unity 6.8 이후에도 IL2CPP 유지
    • .NET 10 BCL + C# 14 문법은 동일하게 지원
  • 시뮬레이션 / 순수 C# 로직: 2.5배 수준 속도 향상
  • BCL 집약 워크로드: 최대 15배 향상
  • GC 정지 시간: 정밀 이동식 GC로 장시간 세션의 단편화 해소
  • SIMD 경로: System.Runtime.Intrinsics 도입으로 벡터 연산 대폭 가속
  • 에디터 워크플로우: Fast Enter Play Mode 기본화로 반복 테스트 루프 단축

핵심 교훈 / 마이그레이션 체크리스트

Section titled “핵심 교훈 / 마이그레이션 체크리스트”

지금 당장 실행할 7가지 체크리스트입니다. (아래 grep 예시는 macOS/Linux/Git Bash 기준이며, PowerShell에서는 Select-String -Path Assets\* -Pattern "..." -Recurse를 사용하세요.)

  1. BinaryFormatter 사용 검색
    Terminal window
    grep -r "BinaryFormatter" Assets/
    JsonUtility 또는 System.Text.Json으로 교체.
  2. Assembly.Load 검색
    Terminal window
    grep -r "Assembly.Load" Assets/
    CurrentAssemblies.LoadFromPath()로 교체.
  3. AppDomain 사용 검색
    Terminal window
    grep -r "AppDomain" Assets/
    → Unity가 제공하는 대체 API로 전환.
  4. MonoPosixHelper P/Invoke 검색: Mono 전용 DLL 의존성 제거.
  5. 정적 생성자 순서 의존성 검토: 필요 시 RuntimeHelpers.RunClassConstructor()를 명시적으로 호출.
  6. Fast Enter Play Mode 활성화 테스트: Project Settings > Editor > Enter Play Mode Options.
  7. Project Auditor 실행: 비호환 정적 변수를 일괄 검출.
  • Unity 공식 블로그: CoreCLR and Modern .NET in Unity 로드맵 글
  • Marek Fiser 실측 벤치마크: Mono vs .NET 10 게임 시뮬레이션 비교
  • Microsoft Docs: System.Runtime.Intrinsics SIMD 명령어 매핑
  • Unity Manual: Scripting Backends (Mono / IL2CPP / CoreCLR)