---
title: "CoreCLR 이 와도 모바일은 그대로다"
description: "유니티가 Mono 를 걷어내고 세대 GC 를 들여옵니다. 그런데 IL2CPP 는 이 변화에서 빠져 있습니다."
author: "유니얼"
pubDate: 2026-09-09
category: "game-dev/unity/runtime"
tags: ["Unity", "최적화", "GC"]
canonical: https://blog.unialgames.com/posts/unity-coreclr/
---
앞의 두 편은 Boehm 을 그대로 두고 할 수 있는 일이었습니다. 쪼개고, 미루고, 잠깐 끄는 것입니다.
GC 자체가 나아지지는 않았습니다.

유니티가 지금 하는 것은 다른 쪽입니다. **런타임을 통째로 갈아 끼웁니다.**

Mono 를 걷어내고 CoreCLR 을 넣습니다. 요즘 .NET 이 쓰는 그 런타임입니다.
6.7 에서 실험적으로 들어왔고 **6.8 에서 Mono 가 없어집니다.**

그런데 이 글에서 제일 중요한 문장은 좋아지는 쪽이 아닙니다.

> **IL2CPP 는 이 변화의 영향을 받지 않습니다. 계속 Boehm 을 씁니다.**

## 무엇이 달라지나

CoreCLR 의 GC 는 앞에서 본 Boehm 의 "안 하는 것" 세 개를 전부 합니다.

| | Boehm (Mono·IL2CPP) | CoreCLR |
| --- | --- | --- |
| 주소를 아는가 | **보수적**(conservative). 주소처럼 생기면 주소로 침 | **정밀**(precise). 무엇이 참조인지 정확히 앎 |
| 객체를 옮기는가 | **비압축**(non-compacting). 빈틈이 남음 | **압축**(compacting). 살아남은 것을 붙여서 빈틈을 없앰 |
| 나이를 따지는가 | **비세대**(non-generational). 매번 전체를 훑음 | **세대**(generational). 방금 만든 것만 자주, 싸게 |

셋이 서로 물려 있습니다. 정확히 아니까 옮길 수 있고, 옮길 수 있으니 세대로 나눌 수 있습니다.
Boehm 이 셋을 다 못 한 것도 같은 이유입니다. 첫 단추가 없으면 나머지가 안 됩니다.

유니티는 이걸 "much less work" 라고 표현합니다. GC 가 할 일이 줄어서 프레임에 남는 시간이 늘어난다고요.

실제로 체감되는 것은 이쯤입니다.

- **짧게 살다 죽는 객체가 싸집니다.** 세대 덕입니다.
  매 프레임 만들고 버리는 것들이 대부분 여기 해당합니다
- **조각이 안 쌓입니다.** 옮기니까요. 빈 메모리가 있는데 힙이 커지는 일이 줄어듭니다
- **잘못된 보존이 사라집니다.** 정수를 주소로 착각할 일이 없습니다

## 그래서 이 시리즈가 필요 없어지나

아닙니다. 두 가지 때문입니다.

**첫째, 바닥은 안 바뀝니다.** 할당이 GC 를 부르는 것도, 뿌리에서 손이 닿는 것만 사는 것도 그대로입니다.
세대 GC 도 결국 할당이 임계치를 넘기면 돕니다. 덜 아플 뿐입니다.
그리고 오래 사는 객체가 많으면 2세대 수집이 돌고, 그건 여전히 비쌉니다.

**둘째, 지금 만드는 게임이 모바일이라면 이 글은 남 얘기입니다.**

## IL2CPP 는 빠져 있다

유니티 문서가 명시합니다. IL2CPP 는 이 변화의 영향을 받지 않고,
IL2CPP 가 쓰는 Boehm GC 의 동작에는 변경이 없다고요.

CoreCLR 은 JIT 입니다. 실행하면서 기계어로 바꿉니다.
그런데 iOS 같은 플랫폼은 실행 중에 코드를 만들어 내는 것을 허용하지 않습니다.
그래서 미리 다 컴파일해 두는 IL2CPP 가 필요했고, 그 사정은 CoreCLR 이 와도 그대로입니다.

정리하면 이렇게 갈립니다.

- **데스크톱 · 콘솔 일부 → CoreCLR.** 세대 GC 가 옵니다
- **모바일 → IL2CPP.** 계속 Boehm 입니다. 세대 GC 는 안 옵니다

모바일 게임을 만들고 있다면 앞의 두 편이 앞으로도 계속 내 이야기입니다.
"곧 좋아진다" 는 글을 보고 대응을 미루면 안 됩니다.

같은 프로젝트를 데스크톱과 모바일로 둘 다 빌드한다면 **한쪽에서만 재는 것도 위험합니다.**
에디터에서 멀쩡한 것이 기기에서 튈 수 있습니다. 지금도 그렇지만 앞으로는 GC 종류까지 달라집니다.

## 지금 무엇을 하면 되나

바꿀 것이 별로 없다는 게 답입니다. **어느 GC 든 할당을 줄이는 것이 이득이기 때문입니다.**

앞 시리즈에서 정리한 것이 그대로 유효합니다. 박싱을 안 하고, 문자열을 반복문에서 잇지 않고,
매 프레임 도는 자리에 LINQ 를 안 쓰고, 만든 것을 다시 쓰는 것입니다.

CoreCLR 로 옮길 준비로 지금 할 만한 것은 따로 있습니다.

- **재 두기.** 지금 프레임당 할당이 얼마인지 기록해 두면 옮긴 뒤에 비교할 수 있습니다
- **`Reflection.Emit` 이나 `dynamic` 을 쓰고 있다면 확인.** IL2CPP 에서는 원래 안 되는 것들입니다
- **부동소수점 결과가 미세하게 달라질 수 있다는 것.** 리플레이나 결정론적 로직을 쓴다면 확인이 필요합니다

## 세대 GC 자체가 궁금하다면

CoreCLR 의 GC 는 일반 .NET 의 그것과 같은 것입니다.
0세대·1세대·2세대가 어떻게 나뉘고 왜 그게 싸게 먹히는지, 큰 객체는 왜 따로 관리되는지는
[C# 쪽 글](/posts/csharp-gc/)에 정리해 뒀습니다.

그 글을 쓸 때는 "유니티는 다르다" 는 단서를 달아야 했는데, CoreCLR 로 가면 그 단서가 없어집니다.
데스크톱 한정으로요.

## 정리

- 유니티가 Mono 를 걷어내고 CoreCLR 을 넣습니다. **6.8 에서 Mono 가 없어집니다**
- CoreCLR 의 GC 는 **정밀하고, 옮기고, 세대를 나눕니다.** Boehm 이 안 하던 셋을 전부 합니다
- 짧게 살다 죽는 객체가 싸지고, 조각이 안 쌓입니다
- **IL2CPP 는 이 변화에서 빠져 있습니다. 계속 Boehm 입니다**
- 그래서 **모바일에는 세대 GC 가 오지 않습니다.** 앞의 두 편이 계속 유효합니다
- 어느 쪽이든 할 일은 같습니다. 할당을 줄이는 것입니다