Unity를 많이 사용하셨던 분들은, 닷넷 프레임워크(.NetFramework)에 대해서 익히 알고 계실 겁니다. 유니티는 게임 스크립트 작성에 C#을 차용하고 있기 때문이기도 하고, 덕분에 C# 콘솔게임 제작 경험도 아마 있으실 것이기 때문입니다.
C#은 닷넷 아키텍쳐 위에서 동작합니다. 그러나 같아보일지도 모르는 Unity에서의 닷넷은 MS의 닷넷과 다릅니다. 이 때문에 프로그래머는 C# 자체에 능통한 것과 별개로 유니티라는 프레임워크에 쓰이는 .NET을 잘 숙지하고 있어야합니다,특히 엔진 개발자라면 더욱 더 말이죠. 즉 차이를 알고 활용하는 것이 유니티 상에서의 메모리 관리에 핵심이 되기 때문입니다.
유니티에서 사용하는 .NET은 두가지 종류가 있습니다. 각각 MONO와 ILCPP입니다. 차이점을 간략하게 정리하면 아래와 같습니다.
유니티에서 사용하는 .NET : MONO, ILCPP
Mono :
• 자바처럼 크로스 플랫폼 대응
• JIT(Just-In-Time) 컴파일 이용
• 현재는 MS가 인수하여 관리
• 현재 Xamarin. (물론 이것도 MS가 인수했습니다)
IL2CPP:
• Mono는 32비트만을 대응하기에 64비트를 대응하기 위해 제작
• AOT(Ahead-Of-Time) 컴파일 지원
참고로 MONO는 32비트 지원만 하는데다가, 성능도 IL2CPP에서 많이 차이가 나기 때문에 요즘은 사용하지 않습니다.(최종 배포빌드에서 잘 사용하지 않는 다는 뜻입니다. 빌드속도가 상대적으로 빨라서 개발과정에서는 많이 사용합니다.) 근데 ILCPP도 단점은 있습니다. 바로 AOT방식이라 최종 빌드되는 앱의 크기가 증가하게 된다는 점 인데요, 시간자원과 공간자원을 동시에 해결하는건 흔치않다보니 결국엔 이런 메모리적 문제도 고성능 컴퓨터로 해결하는 추세입니다.
C# by Roslyn --> IL, IL Stripping --> C++, C++ in a target platform--> exe or dll file
과 같이 나타낼 수 있는데 세부사항을 정리하면 아래와 같습니다.
• 1. Roslyn 컴파일러가 C# 코드를 컴파일 합니다.
(참고로 Roslyn은 C#을 컴파일 하기 위해 사용하는 플랫폼과 같은 개념으로, 대체로 Roslyn을 사용합니다.)
• 2. IL2CPP가 IL Stripping(Intermediate Language Stripping)을 실시해 애플리케이션의 크기를 줄입니다.
• 3. IL2CPP가 IL 코드를 표준 C++ 코드로 변환합니다.
• 4. C++ 컴파일러가 변환된 C++ 코드를 타겟 플랫폼으로 컴파일 합니다
•5. Unity가 타겟 플랫폼에 따라 실행 파일 혹은 DLL을 생성하게됩니다.
참고로 코드 스트리핑은 사용되지 않는 자동으로 코드를 삭제하는 기능입니다. 코드 스트리핑 때문에 원치않게 버그를 맞이할 수도 있다는 단점이 있지요. 대표적인 예시로는 동적 맴버변수를 활용하여 클래스를 참조하거나 할 때(동적 바인딩, 지연 평가, 또는 리플렉션 같은 메커니즘이 쓰일 때) 사용하지 않는 맴버나 클래스로 인식하여 의도치 않게 코드 스트리핑 대상이 되는 경우가 있습니다.
Unity는 C#을 사용하지만, 최신 .NET 라이브러리는 지원하고 있지 않습니다. 또한 모든 라이브러리를 지원하는 것도 아닙니다. 사용가능한 버전은 .NET Standard 2.0과 .NET 4.X 입니다. 다만 Unity가 권장하는 버전은 .NET Standard 2.0 입니다. 라이브러리 크기가 작아 실행 파일 크기도 줄이고, 크로스 플랫폼 지원도 원활하며, .NET 런터임도 지원하기 때문입니다. 컴파일 타임에 오류 또한 잡아냅니다. 하지만 이는 플랫폼과 Third Party Library에 따라 달라질 수 있습니다.
참고) A third-party library is a reusable software component that is developed and maintained by an entity other than the original vendor of the development platform)
. Unity에서 .NET은 세가지 메모리 관리 계층이 있습니다. 각각 네이티브 메모리, 관리되는 메모리 (Managed Memory), 관리 되지 않는 메모리(Unmanaged Memory)가 있습니다. 즉 어떤 메모리가 관리되는지, 안되는지 명확하게 구분함으로써 좀 더 최적의 메모리관리를 할 수 있게됩니다.
✨네이티브 메모리 : Unity 엔진이 내부적으로 사용하는 메모리 관리 시스템을 말합니다. 대부분 유저가 직접적으로 접근 할 수 없습니다. 유저가 직접적으로 접근하거나 조작할 수는 없는데, 여담으로 이는 언리얼이 오픈소스여서 접근 가능한 것이랑 상반되는 부분이긴 합니다. 다만 메모리를 미세조정 하고싶다면 Unity의 C# API를 활용해 간접적으로 수정은 가능합니다. 이 네이티브 메모리에는 씬, 에셋, 그래픽 API, 그래픽 드라이버, 서브시스템, 플러그인 버퍼 등이 저장되어있습니다.
✨관리되지 않는 메모리
직전에 말씀드린 네이티브 메모리에 접근해 메모리 할당을 미세 조정하기 위한 계층을 말합니다. 여기서 잡 시스템(Job System)과 버스트(Burst) 컴파일러를 위해 사용하게 됩니다. 잡 시스템과 버스트를 설명하기엔 내용이 조금 방대해지기 때문에 링크로 대체하겠습니다.
Unity Manual: Job System
Unity Manual: Burst
✨관리되는 메모리✨
가비지 컬렉터에 의해서 관리되는 메모리입니다. 이전에 살펴본 것 처럼, 메모리 할당과 해제시 일어나는 다양한 실수를 방지 할 수 있지만, 가비지 컬렉션을 제대로 이해하고 있지 않으면 성능저하가 일어날 수 있습니다. Unity의 가비지 컬렉션은 MS의 방식과는 다릅니다. Unity는 Boehm GC방식을 사용하고 있습니다. 또한 비세대 방식이며 메모리의 압축 또한 일어나지 않습니다. 이는 Unity에서 메모리 단편화가 자주 일어나 힙이 쉽게 확장되고, 최적화 이슈가 발생할 수 있다는 것을 의미합니다. 그리고 가비지 컬렉션아 너무 많은 성능자원이 소모되는 것을 방지하기 위해 조금씩 가바지 컬렉션을 나누어 하는 증분 방식 또한 채택하여 사용하고 있습니다.!
빈공간이 연속적이지 않아 할당이 불가능한 경우를 단편화라고 하며, 이 경우 힙의 확장이 일어나게 됩니다.