Unity 검색

Unity 2020 LTS의 관리되는 코드 스트리핑으로 워크플로 최적화하기

2021년 10월 25일 테크놀로지 | 10 분 소요
Population one
Population one
다루는 주제
공유

관리되는 코드 스트리핑은 주요한 빌드 프로세스의 일부로, 애플리케이션의 바이너리 파일 크기를 줄이는 데 도움이 됩니다. 이 단계에서 사용되지 않는 코드가 제거됩니다. 

코드를 제거하여 최종 빌드에 포함되거나 컴파일되지 않도록 할 수 있습니다. 이 방식으로 IL2CPP 백엔드에서 실행되는 프로젝트의 메모리 사용량을 다소 낮출 수 있지만, 관리되는 코드 스트리핑 레벨이 높을 경우 런타임에서 타입이나 메서드가 누락될 위험을 비롯한 여러 문제가 발생할 수 있습니다.

빌드 프로세스 전반에서 일부 코드는 사용되지 않는 것으로 간주되어 제거됩니다. 이때 필요한 어셈블리를 link.xml 파일에 직접 추가하는 것이 가장 간단한 해결책이 아닐 수 있습니다. 유니티에서 소프트웨어 개발 컨설턴트로 근무하며 프로젝트 리뷰를 진행하는 동안, 관리되는 코드 스트리핑을 더 효율적으로 사용하는 방법에 대해 고객들의 문의가 있었습니다. 이번 포스팅에서는 관리되는 코드 스트리핑의 새로운 주석 속성을 이용하여 워크플로를 개선할 수 있는 팁과 베스트 프랙티스를 소개합니다.

Unity linker를 이용한 관리되는 코드 스트리핑

IL2CPP 스크립팅 백엔드를 사용할 때는 사용되지 않는 코드를 제거하는 것이 특히 중요합니다. Unity linker는 Unity와 호환되도록 커스터마이즈된 Mono IL linker의 한 버전으로, 정적 분석을 수행하여 관리되는 코드를 제거합니다.

Unity는 IL2CPP에 세 가지 레벨(Low, Medium, High)의 관리되는 코드 스트리핑을 지원합니다. 관리되는 코드 스트리핑 매뉴얼에서 코드 스트리핑 과정이 어떻게 진행되는지, 어떤 인자가 어떤 코드를 제거하고, 여러 스트리핑 레벨 사이에 어떤 차이점이 있는지 확인할 수 있습니다. 간단히 말하면 코드 스트리핑 레벨이 높을수록 linker가 사용되지 않는 코드를 찾아 삭제하는 수준도 올라갑니다. 프로젝트의 플레이어 설정에서 관리되는 스트리핑 레벨을 변경할 수 있습니다.

특정 오브젝트의 타입이 런타임에서만 정의될 경우 사용되지 않는 코드를 식별하기 위해 linker가 활용하는 정적 분석은 모든 사례를 처리하지 못합니다. 이러한 경우 오탐지로 이어집니다. 컴파일 시에는 클래스나 메서드에 대한 참조가 없을 수 있지만, 런타임에서는 일부 코드에서 여전히 클래스나 메서드가 필요합니다. Reflection을 사용하는 코드가 이를 잘 보여 줍니다. 다음 스니핏을 살펴보세요.

이는 일반적으로 사용되는 유효한 코드 유형이지만, linker는 MyAssembly, MyType, MyMethod가 런타임에서 실제로 사용되는지 여부를 알 수 없습니다. 따라서 코드가 제거될 수 있으며, 나아가 런타임 오류가 발생할 수 있습니다. 자세한 내용은 스트리핑 제한 매뉴얼을 참조하세요.

Zenject와 같은 Dependency Injection 프레임워크나 Newtonsoft.Json과 같은 직렬화 라이브러리를 사용하는 개발자는 오탐지로 인한 코드 스트리핑이 발생할 수 있다는 점을 감안하고 적절한 조치를 취해야 합니다. 가장 일반적인 접근법은 다음과 같습니다.

  • linker는 다양한 속성을 인식하며, 식별할 수 없는 종속 관계가 있을 경우 사용자가 주석을 남길 수 있습니다. 따라서 제거되지 않아야 하는 어셈블리, 클래스, 메서드에 [Preserve] 속성을 추가할 수 있습니다.
  • link.xml 파일은 프로젝트별 목록이며, 프로젝트의 어셈블리, 타입, 그리고 기타 코드 엔티티를 보존하는 방법이 기술되어 있습니다. link.xml에 필요한 어셈블리, 클래스, 메서드를 직접 추가하거나, 빌드 프로세스에서 UnityEditor API GenerateAdditionalLinkXmlFile을 사용하여 link.xml 파일을 생성할 수 있습니다.

Addressables 패키지도 LinkXmlGenerator를 사용합니다. 어드레서블의 빌드 스크립트는 그룹의 에셋 목록을 검토하고 에셋에서 사용되는 타입을 link.xml 파일에 추가합니다. 또한 Reflection을 통해 런타임에 어드레서블 내부에서 사용되는 타입을 추가합니다. 스크립터블 빌드 파이프라인과 같은 빌드 프로세스에서 이와 같은 솔루션을 구현하려면 기본 빌드 스크립트인 BuildScriptPackedMode.cs를 참조해 보세요.

Unity는 Assets 폴더 또는 그 하위 폴더 중 하나에 있는 여러 link.xml 파일을 지원합니다. 빌드 프로세스에서 linker는 여러 link.xml 파일의 항목을 병합하고 검토합니다.

Unity 2020 LTS에서 선보이는 관리되는 코드 스트리핑의 새로운 기능

[Preserve] 속성을 사용하려면 어느 정도 수작업이 필요할 수 있습니다. 하지만 이미 Unity 2020 LTS 버전에서 프로젝트를 개발하고 있다면, 관리되는 코드 스트리핑의 다양한 최신 주석 속성을 사용하여 제거되면 안 되는 어셈블리, 클래스, 메서드를 쉽고 정확하게 표시할 수 있습니다. 제공되는 속성 중 일부는 다음과 같습니다.

  • RequireAttributeUsagesAttribute: 속성 타입이 표시되면 해당 타입의 모든 CustomAttributes도 표시되므로, High 스트리핑 레벨에서 작업할 때 복잡도가 줄어듭니다.
  • RequireDerivedAttribute: 타입이 표시되면, 해당 타입에서 파생된 모든 타입도 유사하게 표시됩니다.
  • RequiredInterfaceAttribute: 타입이 표시되면, 지정된 타입의 모든 인터페이스 구현이 표시됩니다.
  • RequiredMemberAttribute: 타입이 표시되면, 해당 타입에서 [RequiredMember]를 가지는 모든 멤버가 표시됩니다. 이렇게 하면 선언하는 타입이 스트리핑 불가로 처리되지 않으므로, 더 정확하게 코드 스트리핑을 수행할 수 있습니다. 하지만 클래스 자체가 사용되지 않는 경우에는 [RequiredMember] 속성으로 표시되더라도 그 멤버는 스트리핑됩니다.
  • RequireImplementorsAttribute: 이 인터페이스 타입이 표시되면, 해당 인터페이스를 구현하는 모든 타입이 표시됩니다. 따라서 구현마다 표시할 필요가 없습니다. 그러나 인터페이스가 코드 베이스에서 구현되지 않으면, [RequireImplementors] 속성이 표시되어 있더라도 제거됩니다.

Unity 2020.1과 2020.2에서는 Mono IL linker에 대응하도록 툴의 API가 업데이트되었습니다. 이제 간단한 반사 패턴을 일부 감지할 수 있으므로, Unity 2020 LTS로 업그레이드했다면 link.xml 파일의 필요성이 줄어듭니다.

Unity 2020 LTS로 코딩 워크플로를 최적화하는 방법을 자세히 알아보려면, 기능을 소개한 유니티 페이지와 Unity 2020 LTS 매뉴얼의 업데이트 사항을 참조하세요.

향후 전망

테스터와 플레이어에게 고품질의 빌드를 더 쉽게 제공할 수 있도록 2021년에는 코드 스트리핑 워크플로 개선에 집중했습니다. 예를 들어 2021.2 릴리스에는 관리되는 스트리핑 레벨에 Minimal이 새로 추가되었습니다. 이는 IL2CPP 백엔드에 대한 기본값이 될 예정이니 앞으로 업데이트되는 정보를 확인해 주시기 바랍니다.

Managed Stripping Level
Managed Stripping Level 프로퍼티에 새로운 옵션 추가 예정
2021년 10월 25일 테크놀로지 | 10 분 소요
다루는 주제