.NET 관련 개선 사항 및 향후 계획

최근 저희는 여러분이 더 빠른 속도로 더 효율적인 코드를 작성하고 장기적인 안정성과 호환성을 확보할 수 있도록 지원하는 다년간의 프로젝트를 시작했습니다. 스크립트 실행에 필요한 핵심 기술 스택을 업데이트하기 위해 어떤 노력을 기울이고 있는지 알아보려면 계속 읽어보세요.
.NET 생태계는 여러 가지 유익한 방향으로 역동적으로 발전하고 있으며, 저희는 이러한 개선 사항을 최대한 빨리 여러분께 제공하고자 합니다. 저희 내부 .NET 기술 그룹은 최신 C# 기능 및 .NET Standard 2.1을 포함하여 .NET 통합을 지속적으로 개선하기 위해 노력하고 있습니다. 최근에는 사용자 피드백을 바탕으로 전체적인 개발자 경험을 대폭 개선하고 있습니다.
이번 블로그 포스팅에서는 유니티가 현재 개발 중인 기능을 소개합니다. GDC 2022의 Unity 개발자 서밋(Unity Dev Summit)에서도 이 주제에 대해 논의한 바 있습니다. 여기에서 전체 세션 시청하실 수 있습니다.
이 이야기는 지금으로부터 17년 전, 유니티의 CTO가 C#과 Mono .NET 런타임을 동시에 활용하면서 시작됩니다. 유니티는 C# 코드를 상대적으로 효율성 높은 네이티브 코드로 변환하는 JIT(Just-In-Time) 컴파일러와 언어의 간결한 매력 때문에 C#을 선호했습니다. Unity 엔진에서 훨씬 더 큰 비중을 차지하는 나머지 부분은 성능의 적절한 균형과 제어를 위해 C++를 사용하여 개발되었습니다.
수년간 Unity는 Mono .NET 런타임 및 C# 언어(2.0)를 기반으로 실행되었고, 그 기간 동안 다른 플랫폼에 대한 지원도 추가되었습니다. 유니티는 iOS 및 일부 콘솔 플랫폼을 타겟팅할 수 있도록 자체 컴파일러이자 런타임인 IL2CPP도 개발했습니다.
그동안 전체 Microsoft .NET 생태계도 진화하여 새로운 라이선싱과 비 Windows 플랫폼 지원이 생겼습니다. 이러한 발전 덕분에 2018년에 Unity .NET Mono 런타임을 업그레이드하고 최신 C# 언어 버전(7.0 이상)을 지원할 수 있게 되었습니다. 같은 해에 Burst 컴파일러의 최초 버전도 릴리스하여 일부 C# 언어로 최적화된 네이티브 코드를 생성할 수 있게 되었습니다. 이러한 획기적인 발전 덕분에 Unity 엔진의 다른 중요한 부분에서도 C# 사용을 확장할 수 있고, 이러한 부분을 C++로 개발할 필요가 없는 세상을 구상할 수 있었으며, 이는 DOTS 런타임 개발로 이어졌습니다.
Unity 2020 LTS와 Unity 2021 LTS 에는 새로운 C# 언어 버전과 새로운 .NET API가 포함되었습니다. 이와 동시에 .NET 생태계에서는 엄청난 성능 개선이 이루어졌고, SDK 스타일의 csproj의 도입과 NuGet 생태계의 성장을 통해 보다 친근한 개발 환경이 구축되었습니다.
Unity 플랫폼은 오랜 진화의 결과로 Mono .NET 런타임에서 상속한 구체적 가정을 사용하여 .NET 오브젝트와 직접 상호 작용하는 대규모 C++ 코드베이스를 포함하게 되었습니다. 이는 .NET (Core) 런타임에는 더 이상 유효하지 않으며 효율이 떨어집니다.
또한 Unity 에디터와 함께 사용하는 복잡한 컴파일 파이프라인은 MSBuild를 사용하지 않기 때문에 제공되는 모든 기본 기능을 활용할 수 없게 됩니다.
저희는 지난 몇 년 동안 인터뷰와 Unity 포럼 을 통해 많은 분들과 소통하며 여러분의 성공을 더욱 효과적으로 지원하기 위해 무엇을 개선할 수 있을지 논의해 왔습니다. 저희가 들은 바로는 귀하께서 최신 C# 언어, .NET 런타임 기술, 그리고 NuGet에서 제공하는 타사 C# 코드를 사용하고 싶어하시는 것 같습니다. Unity 플랫폼 사용에 관해서는 고품질 C# 테스트, 디버깅, 프로파일링 툴, 그리고 기본 .NET API와 Unity API 간의 적절한 통합을 통해 타겟 하드웨어를 최대한 활용하고 싶다는 의견이 많았습니다. C# Unity 프로그래머라면 다른 툴박스와 원활하게 연동되고 빠른 반복 개발을 가능하게 하여 최고의 런타임 성능을 달성할 수 있는 Unity 툴을 원할 것입니다.
이러한 요청을 모두 현실화하려면 수 년이 걸릴 것으로 예상합니다. 유니티는 개발 과정에서 직면하는 기술적인 문제에 관해 블로그와 포럼을 수시로 업데이트하여 상황을 계속 공유할 예정입니다.
이번 이니셔티브의 첫 단계에서는 C# 및 .NET에 열의를 가진 내부 직원을 모두 한 데 모아 C#/.NET Tech Group을 구성하였습니다.
커스텀 솔루션이 아닌 .NET 생태계를 기반으로 빌드하고자 했으며, 최신 .NET SDK/Runtime 및 MSBuild의 성능과 생산성 개선을 활용할 수 있도록 Mono .NET 런타임에서 현대적인 .NET (Core) 런타임인 CoreCLR로 마이그레이션할 계획을 세웠습니다.
또한 이 이니셔티브는 C# 스크립트에서 더 빠른 .NET 반복 작업이 가능하도록 기존의 .NET 생태계를 넘어서는 혁신을 제공합니다. 유니티는 JIT 및 AOT(Ahead-Of-Time) 솔루션, 즉 IL2CPP 및 Burst의 융합을 통해 컴파일 소요 시간과 코드 생성 품질 간에 최적의 균형을 제공하려고 합니다.
외부적으로는 Unity 크리에이터가 최신 .NET 기술을 사용할 수 있도록 Microsoft, JetBrains 등의 업계 파트너와 협력하면서 오픈 소스 커뮤니티에서도 참여도를 높일 예정입니다. 우리는 이 작업을 여러 단계로 나누어 진행할 것입니다. 그럼 향후 계획을 살펴보겠습니다.
유니티 팀은 올해에 다음 사항에 집중할 예정입니다.

저희는 고객 여러분이 시간을 더욱 효율적으로 활용하시기를 바라기 때문에 , 반복 작업 시간을 최우선 순위로 두고 있습니다. 다음은 반복 작업 시간을 개선하기 위한 방안의 일부입니다.
- 컴파일 파이프라인에서 C# 컴파일 후에 컴파일된 .NET 어셈블리의 수정을 담당하는 IL Post Processing에 의해 소모되는 시간을 개선하고 있습니다. 이제 컴파일 단계 이후 영속적인 프로세스를 사용하여 IL Post Processing을 실행하며 이를 통해 수백 밀리초를 단축할 수 있습니다.
- Burst 컴파일러가 더 빈번하게 사용되고 있으므로 추이적 해싱(transitive hashing) 알고리즘을 사용하여 코드 변경을 더 세밀하게 검출할 수 있도록 개선하고 있습니다. 이에 따라 컴파일이 필요한 버스트 가능 코드를 더 빨리 식별할 수 있습니다. 유니티는 Burst 컴파일러가 프로세스를 벗어나 별도 .NET 6.0 실행 파일에서 실행됨으로써 코드를 더 빠르게 컴파일할 수 있도록 하고 있습니다.
- 또한 TypeCache 가 사용될 때마다 백그라운드에서 생성되는 리플렉션 데이터를 개선하여 도메인 재로딩 속도를 향상시키고 있습니다.
- 패키지 및 프로젝트 템플릿의 반복 작업 시간을 더 면밀히 추적하기 위해 테스트와 검증이 추가됩니다.
MSBuild로 마이그레이션하기 위한 첫 번째 단계는 컴파일 파이프라인을 Unity 에디터에서 분리하여 별도의 프로세스로 옮기는 것입니다. 몇 년간 축적된 수천 줄의 C++, C# 레거시 코드를 분리해내는 동시에 하위 호환성도 유지해야 하기 때문에 이는 복잡한 작업입니다. 외부적인 변화는 없지만 이 작업을 통해 MSBuild로 이전하기 위한 기반을 마련하고 유지 관리를 간소화할 수 있습니다.
또한 Burst 사용하여 실행되는 코드 경로에 중단점을 설정하면 디버거가 자동으로 관리형 디버깅으로 전환되는 모드를 도입하여 Burst 사용한 C# IDE 디버깅 환경을 개선 할 예정입니다. 이렇게 하면 디버깅되는 코드 경로에서 [BurstCompile] 속성을 수동으로 제거하지 않아도 됩니다.
.NET CoreCLR 런타임으로의 마이그레이션 작업은 이미 시작되었으며, 매우 어려운 여정입니다. 이 마이그레이션을 완수하기 위해 유니티는 문제를 점진적으로 해결하면서 기존 Unity 프로젝트의 안정성을 유지하는 방향으로 결과물을 전달하려 합니다.
다음과 같이 여러 단계로 이 마이그레이션을 완수할 계획입니다.
- 우선, 데스크톱 플랫폼용 독립 실행형 플레이어에 .NET CoreCLR 지원을 제공할 예정입니다. 크리에이터는 기존 Mono 및 IL2CPP 백엔드와 함께 플레이어 설정에서 이 런타임을 선택할 수 있게 됩니다. 이 첫 번째 단계는 Unity 엔진의 핵심 부분(에디터 부분보다 훨씬 작음)을 마이그레이션하는 데 도움이 될 것이며, 마이그레이션과 관련된 기술적 과제들을 상당 부분 해결할 수 있기를 바랍니다. 이전과 마찬가지로 .NET Standard 2.1 API를 통해 .NET 런타임에 액세스할 수 있으며, 유니티는 2023년 중으로 새로운 런타임을 릴리스하는 것을 목표로 하고 있습니다.
- 둘째로, Unity 에디터를 .NET CoreCLR로 포팅 하고 동시에 .NET Mono 런타임 지원을 제거할 예정입니다. 이 두 번째 단계에서는 AppDomains를 사용하지 않고 편집기에서 스크립트를 다시 로드하는 방법과 .NET CoreCLR로의 전환을 완료하는 방법을 다룰 것입니다. dotnet/runtime 저장소에서 기본 클래스 라이브러리를 지원하기 위해 IL2CPP도 업그레이드해야 합니다. 최종적으로 전체 .NET 7.x 또는 8.0 API에 액세스할 수 있게 됩니다. 2024년 중으로 이 기능을 갖춘 새로운 에디터를 릴리스할 수 있기를 바랍니다.
Unity 2021 LTS 의 .NET Standard 2.1 지원 덕분에 Unity 런타임을 여러 가지 방식으로 현대화할 수 있게 되었습니다. 유니티는 현재 아래와 같은 두 가지 측면을 개선하고 있습니다.
async/await 프로그래밍 모델 개선 . async/await는 기초적인 프로그래밍 접근 방식으로, 엔진의 메인루프 차단 없이 비동기 연산이 완료될 때까지 대기해야 하는 게임플레이 코드를 작성합니다.
2011년, async/await가 .NET에서 주류가 되기 전에 Unity 이터레이터 기반 코루틴을 사용하여 비동기 작업을 도입했지만, 이 접근 방식은 async/await와 호환되지 않으며 효율성이 떨어질 수 있습니다. 한편, .NET Standard 2.1은 ValueTask를 통해 async/await 작업을 보다 효율적으로 처리하는 방식을 도입하고, AsyncMethodBuilder를 통해 사용자 정의 작업과 유사한 시스템을 구현할 수 있도록 함으로써 C# 및 .NET에서 async/await 지원을 개선해 왔습니다.
이제 이러한 개선 사항을 활용할 수 있으며 Unity의 기존 비동기 연산을 통해 async/await 연산을 사용할 수 있도록 수정하고 있습니다(다음 프레임 대기 또는 UnityWebRequest 완료 대기 등). 첫 단계로, MonoBehavior가 소멸되거나 플레이 모드를 종료할 때 취소 토큰을 사용하여 보류 중인 비동기 작업을 취소할 수 있도록 지원을 개선하고 있습니다. 또한, 저희는 UniTask 개발자와 같은 주요 커뮤니티 기여자들과 긴밀히 협력하여 그들이 이러한 새로운 기능을 활용할 수 있도록 노력하고 있습니다.
Span를 활용하여 메모리 할당 및 복사 횟수를 줄입니다. Unity는 C# 스크립팅 레이어가 있는 C++ 엔진이기 때문에 둘 사이에 많은 데이터가 교환됩니다. 데이터를 상호 복사하거나, 관리되는 오브젝트를 새로 할당해야 하는 경우가 종종 있으므로 이는 비효율적일 수 있습니다.
Span 는 이러한 시나리오를 개선하기 위해 C# 7.2에서 도입 되었으며 .NET Standard 2.1에서 기본적으로 사용할 수 있습니다. 최근 몇 년 동안 Span 덕분에 .NET 런타임의 성능이 크게 향상되었다는 소식을 듣거나 읽어보셨을 것입니다( .NET Core 2.1 , .NET Core 3.0 , .NET 6 , .NET 6 의 개선 사항 세부 정보를 참조하세요). Unity에서는 Span를 활용하여 할당과 가비지 컬렉션의 멈춤 현상을 줄이고 API 다수의 전반적인 성능을 개선할 계획입니다.
이번 포스팅에서 소개한 변경 사항과 기능에 큰 기대를 가져주셨으면 합니다.
저희 계획에 대한 여러분의 의견을 포럼 에 남겨주세요. 저희는 Unity 플랫폼 로드맵 의 엔지니어링 섹션을 정기적으로 업데이트할 예정이며, 그곳에서 기능 요청 및 우선순위 제안을 공유하실 수 있습니다.
편집자 주: 이 기사는 2023년 2월에 마지막으로 업데이트되었습니다.
