오늘 배울 내용은 면접에도 나오는 중요한 성능과 관련된 내용입니다.
Mono는 자마린(Xamarin)으로 업그레이드 되었고 MSDN이 2개다 인수했다.
모바일 시장이 커지게 되어서 32bit 크기에 게임이 64bit 크기로 커졌습니다. 그런데, Mono는 32bit만 지원했습니다. 그래서 64bit를 지원하는 IL2CPP를 자주 사용하게 되었습니다. 또한 IL2CPP가 성능이 훨씬 좋습니다.
JIT : 필요할 때 그때 그때 명령어를 해석하고 네이티브 언어로 옮겨주기 때문에 느리다.
AOT : 미리 네이티브 코드로 전부 변환하기 때문에 빠르다.
Roslyn이란 C# 코드를 컴파일 하기 위한 플랫폼입니다.
IL 스트리핑(stripping)은 스티립쇼에 쓰는 영어와 같은 의미입니다.
벗기다 -> 삭제하다. 즉, 필요하지 않는 것은 삭제합니다.
Unity 엔진은 게임 콘텐츠 작성에 C#을 사용하고 있습니다. C#은 알고있듯이 .NET 아키텍처 위에서 동작하는 언어입니다.
Unity에서의 .NET은 MS의 .NET과는 다르기 때문에 프로그래머는 이를 잘 알고 있어야합니다. 그래야 개발자로서 효율적으로 개발이 가능합니다.
이번 시간에는 Unity의 스크립트 백엔드 환경에 대해서 알아보도록 하겠습니다.
Unity에서 사용되는 .NET은 2가지가 있습니다.
바로 Mono와 와 IL2CPP입니다.
Mono는 크로스 플랫폼을 지원하는 오픈 소스 .NET으로 예전부터 Unity가 사용했습니다. IL2CPP는 Unity가 만든 .NET입니다.
둘의 가장 큰 차이점은 컴파일 방식에 있습니다.
Mono는 JIT 컴파일
IL2CPP는 AOT 컴파일
이 때문에 지원되는 플랫폼에 차이가 생기게 되느데, 자세한 건 여기 참고하면 되겠습니다.
요즘은 IL2CPP를 사용하는 것이 성능적으로 더 좋으므로 Mono를 사용하는 프로젝트는 거의 없습니다.* 따라서 IL2CPP에서만 내용을 다루겠습니다.
*IL2CPP는 빌드 시간과 최종 빌드된 앱의 크기가 증가한다는 단점은 있습니다.
IL2CPP는 AOT 컴파일을 지원하기 때문에 JIT 컴파일 방식인 Mono하고는 빌드 과정이 다릅니다. 자세히 보면 아래와 같습니다.
Roslyn 컴파일러가 C# 코드를 컴파일합니다.
Unity가 IL 스트리핑(stripping)을 실시해 애플리케이션 크기를 줄입니다. 코드 스트리핑은 사용되지 않는 코드를 삭제하는 것입니다.
하지만 코드 스트리핑 때문에 원치 않게 버그를 맞이할 때도 있습니다.
자세한 내용은 여기를 참고하면 되겠습니다.
IL2CPP가 IL 코드를 표준 C++코드로 변환합니다.
C++ 컴파일러가 변환된 C++ 코드를 타켓 플랫폼으로 컴파일합니다.
Unity가 타겟 플랫폼에 따라 실행 파일 혹은 DLL을 생성합니다.
빌드 과정이 복잡해짐에 따라 빌드 시간이 꽤 오래 걸릴 수 있습니다. 이에 관해서는 여기를 참고하면 되겠습니다.
Tip
Mono는 IL 스트리핑 자체가 없습니다. 옵션으로 킬 수는 있지만...
또한 3, 4번 과정도 없습니다. 매우 간단합니다.
Unity는 최신의 .NET라이브러리를 지원하고 있지 않습니다. 또한 모든 라이브러리를 지원하지도 않습니다. Unity에서 사용 가능한 .NET 버전은 .NET Standard 2.0과 .NET 4.X입니다.
Unity가 권장하는 것은 .NET Standard 2.0입니다.
라이브러리 크기가 작아 실행 파일의 크기를 줄일 수 있고, 크로스 플랫폼 지원이 좋으며, 모든 .NET 런타임을 지원하고, 컴파일 타임에 오류를 잡아내기 용이하기 때문입니다.
다만, 써드 파티 라이브러리에 따라 다를 수 있습니다.
콘솔, 모바일로 가면 더욱 중요해지는 내용입니다.
왜냐면 모바일은 가상 메모리를 지원하지 않기 때문에, 실제 물리적 한계를 느낍니다.
단, 갤럭시는 램이 널널하지만, 아이폰은...작습니다.
어쨌든, 휴대폰은 컴퓨터에 비해 램에 크기가 너~~무 작다보니, 모바일 게임 만들 때 메모리를 신경써야 합니다.
성능 이슈 없이 애플리케이션이 동작하려면 Unity 엔진이 메모리를 어떻게 관리하는지 알아야합니다.
Unity는 3가지 메모리 관리 계층을 사용하는데 하나씩 알아보겠습니다.
네이티브 메모리(Native Memory)는 Unity 엔진이 내부적으로 사용하고 있는 메모리 관련 시스템입니다. 대부분의 경우 유저가 직접적으로 접근하거나 조작할 순 없습니다.
이 메모리에는 씬, 애셋, 그래픽 API, 그래픽 드라이버, 서브시스템, 플러그인 버퍼 등이 저장되어 있으며, 메모리를 미세 조정하기 위해 이 데이터를 수정하고 싶다면 Unity의 C# API를 이용해 간접적으로 수정할 수는 있습니다.
네이티브 메모리에 접근해 메모리 할당을 미세 조정하기 위한 계층입니다.
잡 시스템(Job System)이나 버스트(Burst) 컴파일러를 위해 사용합니다.
위에 두개는 이런게 있다. 라고 머릿속에 있으면 되는 거고, 관리되는 메모리는 정말 매우매우 중요하다.
가비지 컬렉터에 의해 자동으로 관리되는 메모리입니다.
이전에 살펴봤듯이 메모리 할당과 해제시 일어나는 다양한 실수를 방지할 순 있지만, 가비지 컬렉션에 대해서 명확히 이해하고 있지 않다면 성능 저하가 일어날 수 있습니다. Unity의 가비지 컬렉션은 MS의 방식과는 다릅니다.
Unity에서는 Boehm GC를 사용합니다. 특징은 다음과 같습니다.
세대별로 관리되지 않고 메모리의 압축또한 일어나지 않습니다.
그래서 Unity에서는 메모리 단편화가 일어나 힙이 쉽게 확장됩니다.
힙이 쉽게 확장되어 최적화 이슈가 발생할 수 있습니다.
힙이 쉽게 확장됐다는 건 가비지 컬렉션이 자주 일어난다는 것이고, 우리가 만든 게임이 성능이 떨어지는 것입니다.
그리고 가비지 컬렉션에 너무 많은 성능이 소모되는 것을 막기 위해 수행 제한 시간을 두어 조금씩 조금씩 가비지 컬렉션을 하는 증분 방식을 사용합니다.
위에 나온 공식 문서대로 주의하면서 코드를 작성하는 습관을 가져야합니다.
개발자는 문제를 해결하는 사람이지, 코드만 치는 단순 노동자가 아님을 늘 명심하고 최적화에 늘 신경써야합니다.