Version: 2021.1
Unity 아키텍처
.NET 프로파일 지원

Unity의 .NET 개요

Unity는 오픈 소스 .NET 플랫폼을 사용하여 Unity로 만든 애플리케이션이 다양한 하드웨어 설정에서 실행될 수 있도록 지원합니다. .NET는 다양한 언어와 API 라이브러리를 지원합니다.

스크립팅 백엔드

Unity에는 두 가지 스크립팅 백엔드, 즉 Mono와 IL2CPP(Intermediate Language To C++)가 있으며, 각각 다른 컴파일 기술을 사용합니다.

  • Mono는 JIT(Just-In-Time) 컴파일을 사용하고 런타임 시점에 요청 시 코드를 컴파일합니다.
  • IL2CPP는 AOT(Ahead-of-Time) 컴파일을 사용하며 실행 전에 전체 애플리케이션을 컴파일합니다.

JIT 기반 스크립팅 백엔드를 사용할 때의 장점은 일반적으로 AOT보다 컴파일 시간이 훨씬 빠르며 플랫폼 독립적이라는 점입니다.

Unity 에디터는 JIT 기반이며 Mono를 스크립팅 백엔드로 사용합니다. 애플리케이션용 플레이어를 빌드할 때 사용할 스크립팅 백엔드를 선택할 수 있습니다. 에디터를 통해 이 작업을 수행하려면 Edit > Project Settings > Player로 이동하여 Other Settings패널을 열고 Scripting Backend 드롭다운을 클릭한 후 원하는 백엔드를 선택하십시오.

관리되는 코드 스트리핑

애플리케이션을 빌드하는 경우 Unity는 컴파일된 어셈블리(.DLL)를 스캔하여 사용되지 않는 코드를 감지하고 제거합니다. 이 프로세스는 빌드의 최종 바이너리 크기를 줄이지만, 빌드 시간이 늘어납니다.

Mono를 사용하면 기본적으로 코드 스트리핑이 비활성화되지만, IL2CPP의 경우에는 코드 스트리핑을 비활성화할 수 없습니다. 코드 스트리핑 시 Unity의 적극성을 제어할 수 있습니다. Edit > Project Settings > Player로 이동하여 Other Settings 패널을 열고 Managed Stripping Level 드롭다운을 클릭한 후 원하는 코드 스트리핑 레벨을 선택하십시오. 코드 스트리핑에 대한 자세한 내용은 관리되는 코드 스트리핑 문서를 참조하십시오.

참고: 코드 스트리핑은 경우에 따라 너무 적극적일 수 있으며, 특히 반사를 사용하는 경우 사용하는 코드가 제거될 수 있습니다. Preserve 속성과 link.xml 파일을 사용하면 특정 타입 및 함수가 스트리핑되는 것을 방지할 수 있습니다.

가비지 컬렉션

Unity는 Mono 및 IL2CPP 백엔드 모두에 대해 Boehm 가비지 컬렉터를 사용합니다. Unity는 기본적으로 Incremental 모드를 사용합니다. Incremental 모드를 비활성화하고 “stop the world” 가비지 컬렉션을 사용할 수도 있지만, Unity는 Incremental 모드를 사용할 것을 권장합니다.

Incremental 모드와 “stop the world” 간에 전환하려면 Edit > Project Settings > Player로 이동한 후 Other Settings 패널을 열고 Use incremental GC 체크박스를 클릭하십시오. Incremental 모드에서 Unity의 가비지 컬렉터는 제한된 시간 동안에만 실행되며, 모든 오브젝트를 반드시 한 번에 수집하지는 않습니다. 이렇게 하면 오브젝트 수집에 걸리는 시간이 여러 프레임에 걸쳐 분산되고, 불안정한 현상과 CPU 스파이크가 줄어듭니다. 자세한 내용은 자동 메모리 관리 이해를 참조하십시오.

애플리케이션에서 할당 수와 CPU 스파이크 가능성을 확인하려면 Unity 프로파일러를 사용하십시오. 또한 GarbageCollector API를 사용하여 플레이어에서 가비지 컬렉션을 완전히 비활성화할 수도 있습니다. 컬렉터가 비활성화되면 초과 메모리 할당에 주의해야 합니다.

.NET 시스템 라이브러리

Unity는 다양한 플랫폼을 지원하며 플랫폼에 따라 다른 스크립팅 백엔드를 사용할 수 있습니다. .NET 시스템 라이브러리는 경우에 따라 올바른 작동을 위해 플랫폼별 구현이 필요합니다. Unity는 최대한 많은 .NET 에코시스템을 지원하기 위해 최선을 다하지만, Unity가 명시적으로 지원하지 않는 일부 .NET 시스템 라이브러리에는 몇 가지 예외가 있습니다.

Unity는 모든 Unity 버전에서 .NET 시스템 라이브러리의 성능 또는 할당을 보장하지 않습니다. 일반적으로 Unity는 .NET 시스템 라이브러리의 성능 회귀를 수정하지 않습니다.

Unity는 System.Drawing 라이브러리를 지원하지 않으며, 이 라이브러리는 모든 플랫폼에서 작동하지 않을 수도 있습니다.

JIT 스크립팅 백엔드를 사용하면 애플리케이션 런타임 중에 동적 C#/.NET IL(Intermediate Language) 코드 생성을 구현할 수 있지만, AOT 스크립팅 백엔드는 동적 코드 생성을 지원하지 않습니다. 타사 라이브러리를 사용할 경우에는 이러한 점을 고려해야 합니다. 타사 라이브러리는 JIT 및 AOT에 대해 서로 다른 코드 경로를 사용하거나, 동적으로 생성된 코드에 의존하는 코드 경로를 사용할 수 있기 때문입니다. 런타임 시점에 코드를 생성하는 방법에 대한 자세한 내용은 Microsoft의 ModuleBuilder 문서를 참조하십시오.

Unity는 다양한 .NET API 프로파일을 지원하지만, 다음과 같은 이유로 모든 신규 프로젝트에 .NET Standard 2.0 API 호환성 레벨을 사용해야 합니다.

  • .NET Standard 2.0은 더 작은 API 표면이므로 구현이 더 작습니다. 따라서 최종 실행 파일을 크기가 줄어듭니다.
  • .NET Standard 2.0은 뛰어난 크로스 플랫폼 지원을 제공하므로, 코드가 모든 플랫폼에서 작동할 가능성이 더 높습니다.
  • .NET Standard 2.0은 모든 .NET 런타임에서 지원되므로, 더 많은 VM/런타임 환경(예: .NET Framework. .NET Core, Xamarin, Unity)에서 코드가 작동합니다.
  • .NET Standard는 컴파일 시간으로 더 많은 오류를 이동시킵니다. .NET 4.7.1의 여러 API를 컴파일 시점에 사용할 수 있지만, 일부 플랫폼에는 런타임 시점에 예외를 발생시키는 구현이 있습니다.

오래된 기존 애플리케이션에 대한 지원을 제공해야 하는 경우 다른 프로파일을 사용하면 유용할 수 있습니다. 다른 API 호환성 레벨을 사용하려면 플레이어 설정에서 .NET Profile을 변경해야 합니다. 이렇게 하려면 Edit > Project Settings > Player > Other Settings로 이동한 후 Api Compatibility Level 드롭다운에서 원하는 레벨을 선택하십시오.

타사 .NET 라이브러리 사용

다양한 Unity 설정 및 플랫폼에서 광범위하게 테스트된 타사 .NET 라이브러리만 사용해야 합니다.

참고: 타사 라이브러리에서는 JIT 및 AOT 코드 경로의 성능 특성이 크게 다를 수 있습니다. AOT는 일반적으로 시작 시간을 단축하므로 큰 애플리케이션에 적합하지만, 컴파일된 코드를 수용하기 위해 바이너리 파일 크기를 늘립니다. 또한 AOT는 개발 과정에서 빌드하는 데 더 오래 걸리고, 하나의 특정 플랫폼을 대상으로 하기 위해 컴파일된 코드의 동작을 변경할 수 없습니다. JIT는 실행 중인 플랫폼을 기반으로 런타임 시점에 조정되므로, 잠재적으로 실행 성능이 향상되지만 애플리케이션 시작 시간이 더 길어질 수 있습니다. 따라서 에디터와 타겟 플랫폼 모두에서 애플리케이션을 프로파일링해야 합니다. 자세한 내용은 Unity 프로파일러 문서를 참조하십시오.

사용하는 스크립팅 백엔드, .NET 버전 및 프로필에 따라 성능 특성이 달라질 수 있으므로 모든 타겟 플랫폼에서 .NET 시스템 라이브러리의 사용을 프로파일링해야 합니다.

타사 라이브러리를 검토할 때는 다음 부분을 고려하십시오.

  • 호환성: 타사 라이브러리는 일부 Unity 플랫폼 및 스크립팅 백엔드와 호환되지 않을 수 있습니다.
  • 성능 : 타사 라이브러리는 다른 .NET 런타임 대비 Unity의 성능 특성이 크게 다를 수 있습니다.
  • AOT 바이너리 크기 : 타사 라이브러리는 라이브러리가 사용하는 종속성 개수로 인해 AOT 바이너리 크기가 크게 늘어날 수 있습니다.

C# 반사 오버헤드

Mono 및 IL2CPP는 모든 C# 반사(System.Reflection) 오브젝트를 내부적으로 캐싱하며, Unity는 이러한 오브젝트에 대해 가비지 컬렉션을 수행하지 않습니다. 이러한 동작을 수행하면 가비지 컬렉터가 애플리케이션 생애 주기 동안 캐싱된 C# 반사 오브젝트를 지속적으로 스캔하며, 이로 인해 불필요하고 잠재적으로 매우 큰 가비지 컬렉터 오버헤드가 발생합니다.

가비지 컬렉터 오버헤드를 최소화하려면 애플리케이션에서 Assembly.GetTypesType.GetMethods() 등과 같은 메서드를 피해야 합니다. 이러한 메서드는 런타임 시점에 많은 C# 반사 오브젝트를 생성합니다. 대신에 에디터에서 어셈블리를 스캔하여 필요한 데이터를 찾고 런타임 시점에 사용할 수 있도록 직렬화하거나 코드를 생성해야 합니다.

UnityEngine.Object 특수 동작

UnityEngine.Object는 Unity의 특수 타입 C# 오브젝트로, 네이티브 C++ 카운터파트 오브젝트에 연결되어 있습니다. 예를 들어 Camera 컴포넌트를 사용하는 경우 Unity는 오브젝트의 상태를 C# 오브젝트가 아니라 네이티브 C++ 카운터파트에 저장합니다.

Unity는 현재 UnityEngine.Object에 C# WeakReference 클래스를 사용하는 것을 지원하지 않습니다. 이러한 이유로 로드된 에셋을 참조하기 위해 WeakReference를 사용하면 안 됩니다. WeakReference 클래스에 대한 자세한 내용은 Microsoft의 WeakReference 문서를 참조하십시오.

Unity C# 및 Unity C++가 공유하는 UnityEngine 오브젝트

Object.Destroy 또는 Object.DestroyImmediate 등과 같은 메서드를 사용하여 UnityEngine.Object 파생 오브젝트를 파괴하면 Unity는 네이티브 카운터 오브젝트를 파괴(언로드)합니다. 가비지 컬렉터가 메모리를 관리하므로 명시적 호출로 C# 오브젝트를 파괴할 수 없습니다. 관리되는 오브젝트에 대한 레퍼런스가 없으면 가비지 컬렉터가 수집 후 파괴합니다.

파괴된 UnityEngine.Object에 다시 액세스하면 Unity는 대부분의 타입에 대한 네이티브 카운터파트 오브젝트를 다시 생성합니다. 이러한 재생성 동작의 두 가지 예외는 MonoBehaviourScriptableObject입니다. 이 경우 Unity는 파괴된 오브젝트를 다시 로드하지 않습니다.

MonoBehaviour와 ScriptableObject는 같음(==) 및 같지 않음(!=) 연산자를 오버라이드합니다. 따라서 파괴된 MonoBehaviour 또는 ScriptableObject를 null과 비교하면, 관리되는 오브젝트가 아직 존재하고 가비지 컬렉션이 아직 수행되지 않은 경우 연산자는 true를 반환합니다.

???. 연산자는 오버로드가 불가능하므로 UnityEngine.Object에서 파생된 오브젝트와 호환되지 않습니다. 관리되는 오브젝트가 여전히 존재하는 동안 파괴된 MonoBehaviour 또는 ScriptableObject에 이러한 연산자를 사용할 경우 같음 및 같지 않음 연산자와 동일한 결과를 반환하지 않습니다.

async 및 await 사용 지양

Unity API는 스레드에 안전하지 않으므로 async 및 await 작업을 사용하면 안 됩니다. async 작업은 종종 호출될 때 오브젝트를 할당하므로, 과도하게 사용하면 성능 문제가 발생할 수 있습니다. 또한 Unity는 플레이 모드 종료 시 관리되는 스레드에서 실행되는 async 작업을 자동으로 중지하지 않습니다.

Unity는 기본 SynchronizationContext를 커스텀 UnitySynchronizationContext로 덮어쓰고, 편집플레이 모드 둘 다에서 메인 스레드에서 모든 작업을 실행합니다. async 작업을 활용하려면 TaskFactory를 사용하여 자체 스레드를 생성 및 처리하고, Unity 버전 대신 기본 SynchronizationContext를 사용해야 합니다. 플레이 모드 진입 및 종료 이벤트를 수신하여 작업을 수동으로 중지하려면 EditorApplication.playModeStateChanged를 사용하십시오. 하지만 이 방식을 사용할 경우 UnitySynchronizationContext를 사용할 수 없으므로 대부분의 Unity 스크립팅 API를 이용할 수 없습니다.

Unity 아키텍처
.NET 프로파일 지원