관리되는 코드 스트리핑은 빌드에서 사용되지 않는 코드를 제거하여 최종 빌드 크기를 대폭 줄이는 작업입니다. IL2CPP 스크립팅 백엔드를 사용하는 경우 관리되는 코드 스트리핑을 수행하면 C++로 전환하여 컴파일할 코드를 줄일 수 있어 빌드 시간을 단축할 수도 있습니다. 관리되는 코드 스트리핑을 수행하면 프로젝트의 C# 스크립트에서 빌드된 어셈블리, 패키지와 플러그인의 일부인 어셈블리 및 .NET 프레임워크의 어셈블리를 포함한 관리되는 어셈블리에서 코드를 제거할 수 있습니다.
관리되는 코드 스트리핑은 프로젝트의 코드를 정적으로 분석하여 클래스, 클래스 멤버 및 실행 중 도달할 수 없는 함수 부분을 감지하여 이루어집니다. Optimization 섹션의 Player Settings 창에 있는 Managed Stripping Level 설정을 통해 Unity가 도달할 수 없는 코드를 얼마나 적극적으로 프루닝할지 제어할 수 있습니다.
중요: 코드 또는 플러그인의 코드가 반사를 이용하여 동적으로 클래스나 멤버를 찾는 경우, 코드 스트리핑 툴은 이러한 클래스나 멤버가 프로젝트에 의해 사용된다는 사실을 감지하지 못하고 해당 클래스 및 멤버를 제거할 수 있습니다. link.xml 파일이나 Preserve 속성을 이용하여 프로젝트가 그러한 코드를 사용한다는 사실을 선언하십시오.
프로젝트의 Player Settings 에 있는 Managed Stripping Level 옵션을 사용하여 Unity가 사용되지 않는 코드를 얼마나 적극적으로 프루닝할지 제어하십시오.
참고: 이 옵션의 기본값은 현재 Scripting Backend 설정에 따라 달라집니다.
프로퍼티 | 기능 |
---|---|
Disabled | 코드가 제거되지 않습니다. Mono 스크립팅 백엔드의 기본 스트리핑 레벨입니다. IL2CPP 스크립팅 백엔드를 선택한 경우 빌드 시간에 미치는 영향 때문에 Disabled 옵션을 사용할 수 없습니다. 관리되는 코드가 많아질수록 IL2CPP가 생성해야 할 C++ 코드도 늘어나므로 더 많은 C++ 코드를 컴파일해야 합니다. 그 결과, 코드를 변경한 후 변경 내용이 실행될 때까지 소요되는 시간이 대폭 증가합니다. |
Low | 도달할 수 없는 코드를 대부분 제거하는 동시에 실제로 사용되는 코드를 스트리핑할 가능성은 최소화하는 보수적인 규칙에 따라 코드를 제거합니다. 낮은 스트리핑 레벨에서는 크기 축소보다 가용성이 우선합니다. IL2CPP의 기본 스트리핑 레벨이며, 다수의 Unity 에디터 릴리스에서 사용되었습니다. |
Medium | 낮은 스트리핑 레벨과 높은 스트리핑 레벨 간의 균형을 추구하는 규칙에 따라 코드를 제거합니다. 중간 스트리핑 레벨은 낮은 스트리핑 레벨보다 덜 보수적이지만 높은 스트리핑 레벨보다 덜 적극적입니다. 따라서 코드 제거로 인한 부작용이 발생할 가능성도 낮은 스트리핑 레벨과 높은 스트리핑 레벨의 중간 수준입니다. 코드 변경과 테스트 사이의 반복 시간을 좀더 단축하려면 IL2CPP 스크립팅 백엔드에 중간 스트리핑 레벨을 적용하십시오. .NET 3.5 스크립팅 런타임 버전을 설정한 경우 중간 스트리핑 레벨을 사용할 수 없습니다. |
High | 도달할 수 없는 코드를 최대한 제거하고 중간 스트리핑 레벨보다 작은 빌드를 생성합니다. 높은 스트리핑 레벨에서는 크기 축소가 가용성보다 우선합니다. link.xml 파일과 Preserve 속성을 추가하거나 문제가 되는 코드 섹션을 재작성해야 할 수 있습니다. 높은 스트리핑 레벨에서는 이와 같은 추가 크기 축소를 위해 분석에 더 많은 시간이 소요되므로, 빌드 시간과 반복 시간이 중간 스트리핑 레벨보다 길 수 있습니다. .NET 3.5 스크립팅 런타임 버전을 설정한 경우 높은 스트리핑 레벨을 사용할 수 없습니다. |
참고: Managed Stripping Level 옵션은 사용되지 않는 Unity 엔진 코드를 제거하는 절차에 영향을 미치지 않습니다. 이 옵션은 IL2CPP Scripting Backend 설정을 이용하는 경우 사용 가능합니다.
이 섹션은 관리되는 코드 스트리핑에 대한 자세한 내용 및 관련 문제를 식별하고 수정하는 방법을 설명합니다.
Unity에서 프로젝트를 빌드하면 빌드 프로세스는 C# 코드를 CIL(Common Intermediate Language)이라는 .NET 바이트코드 형식으로 컴파일합니다. 이 CIL 바이트코드는 어셈블리라고 불리는 파일로 패키징됩니다. 마찬가지로 프로젝트에서 사용되는 플러그인의 .NET 프레임워크 라이브러리와 C# 라이브러리도 CIL 바이트코드 어셈블리로 미리 패키징됩니다. 보통 빌드 프로세스는 프로젝트에서 사용하는 어셈블리 코드의 분량과는 관계없이 어셈블리 파일 전체를 포함합니다.
관리되는 코드 스트리핑 프로세스는 프로젝트의 어셈블리를 분석하여 실제로 사용되지 않는 코드를 찾아 제거합니다. 이 분석은 일련의 규칙을 사용하여 유지할 코드와 폐기할 코드를 결정합니다. 이러한 규칙에서는 코드를 너무 많이 제거할 위험을 감수하고 지나치게 많은 코드로 인해 증가한 빌드 크기를 축소합니다. 관리되는 스트리핑 레벨 설정을 통해 코드를 얼마나 적극적으로 제거할지 제어할 수 있습니다.
Unity 빌드 프로세스는 UnityLinker라는 툴을 사용하여 관리되는 코드를 스트리핑합니다. UnityLinker는 Unity에서 작동하도록 커스터마이즈된 Mono IL Linker 버전입니다. UnityLinker는 프로젝트의 포크에 구축됩니다. 포크는 업스트림 IL Linker 프로젝트를 면밀히 추적합니다. 참고로 Unity 엔진에 한정된 UnityLinker 부분은 포크에 유지되지 않습니다.
UnityLinker는 프로젝트의 모든 어셈블리를 분석합니다. 먼저 상위 레벨, 루트 타입, 메서드, 프로퍼티, 필드 등을 마킹합니다. 예를 들어, 씬의 게임 오브젝트에 추가한 MonoBehaviour 파생 클래스는 루트 타입입니다. 다음으로 UnityLinker는 마킹한 루트를 분석하여 이러한 루트가 의존하는 모든 관리되는 코드를 식별하고 마킹합니다. 이와 같은 정적 분석이 끝나면 마킹되지 않은 모든 잔류 코드는 애플리케이션 코드의 실행 경로를 통해 도달할 수 없는 것으로 파악되어 어셈블리에서 삭제됩니다.
이 프로세스에서 코드는 난독 처리되지 않습니다.
UnityLinker는 일부 경우 프로젝트 코드가 반사를 통해 다른 코드를 참조하는 인스턴스를 감지하지 못할 수 있으며, 실제로 사용 중인 코드를 잘못 삭제할 수 있습니다. Managed Stripping Level 을 Low 에서 High 로 올리면 코드 스트리핑으로 인해 의도하지 않은 게임 내 동작 변경이 발생할 위험도 높아집니다. 약간의 로직 변화부터 사라진 메서드 호출로 인한 크래시 발생까지 다양한 수준의 동작 변경이 발생할 수 있습니다.
UnityLinker는 일부 반사 패턴을 감지하고 처리할 수 있습니다. 처리할 수 있는 최신 패턴의 예시는 Mono IL Linker 반사 테스트 제품군을 참조하십시오. 하지만 반사를 본격적으로 사용하려면 UnityLinker에 변경하지 않을 클래스에 대한 힌트를 제공해야 합니다. 이러한 힌트는 link.xml 파일과 Preserve 속성을 통해 제공할 수 있습니다.
UnityLinker는 어셈블리에서 미사용 코드를 분석할 때 속성이나 link.xml 파일을 통해 보존된 요소를 루트로 취급합니다.
UnityLinker가 코드를 스트리핑하는 일을 방지하려면 소스 코드의 [Preserve] 속성을 이용하십시오. 다음 리스트는 여러 코드 요소에 Preserve를 적용하면 UnityLinker가 어떤 엔티티를 보존하는지 나타냅니다.
Assembly: 모든 타입에 [Preserve] 속성을 적용한 것처럼 어셈블리의 모든 타입을 보존합니다. 어셈블리에 Preserve 속성을 할당하려면 어셈블리에 포함된 임의의 C# 파일의 네임스페이스 선언 밖에 속성 선언을 추가하십시오.
using System;
using UnityEngine.Scripting;
[assembly: Preserve]
namespace Example
{
public class Foo {}
}
Type: 타입과 해당 기본 생성자를 보존합니다.
Method: 선언 타입, 반환 타입, 메서드의 모든 인수 타입을 보존합니다.
Property: 프로퍼티, 프로퍼티의 선언 타입, 값 타입, 게터 메서드와 세터 메서드를 보존합니다.
Field: 필드, 선언 타입과 필드 타입을 보존합니다.
Event: 이벤트, 이벤트의 선언 타입, 반환 타입, 추가 메서드와 제거 메서드를 보존합니다.
Delegate: 델리게이트 타입과 모든 델리게이트 메서드를 보존합니다.
link.xml 파일의 코드 엔티티를 마킹하면 Preserve 속성 사용 시 보다 세부적인 제어가 가능합니다. 예를 들어, 클래스에 Preserve 속성을 추가하면 타입과 기본 생성자 모두가 보존됩니다. 반면 link.xml 파일에서는 기본 생성자는 제외하고 타입만 유지할 수 있습니다.
모든 어셈블리와 네임스페이스에 Preserve 속성을 정의할 수 있습니다. 즉, UnityEngine.Scripting.PreserveAttribute 클래스를 사용하거나, 서브 클래스를 지정하거나, 다음과 같이 직접 PreserveAttribute 클래스를 생성할 수 있습니다.
class Foo
{
[UnityEngine.Scripting.Preserve]
public void UsingUnityPreserve(){}
[CustomPreserve]
public void UsingCustomPreserve(){}
[Preserve]
public void UsingOwnPreserve(){}
}
class CustomPreserveAttribute : UnityEngine.Scripting.PreserveAttribute {}
class PreserveAttribute : System.Attribute {}
[assembly: UnityEngine.Scripting.AlwaysLinkAssembly] 속성을 사용하면 빌드에 포함된 다른 어셈블리의 해당 어셈블리 참조 여부와 관계없이 UnityLinker가 어셈블리를 처리하도록 강제 설정할 수 있습니다. AlwaysLinkAssembly 속성은 어셈블리에만 정의할 수 있습니다.
이 속성은 UnityLinker가 Root Marking Rules 를 어셈블리에 적용하도록 명령할 뿐, 속성 자체가 어셈블리 내 코드의 보존에 직접적인 역할을 하는 것은 아닙니다. 어셈블리에 대한 루트 마킹 규칙에 일치하는 코드 요소가 없으면 UnityLinker는 해당 어셈블리를 빌드에서 제거합니다.
[RuntimeInitializeOnLoadMethod] 속성이 정의된 메서드를 하나 이상 포함하되 프로젝트 씬에 직간접적으로 사용된 타입을 포함하지 않는 패키지나 미리 컴파일된 어셈블리에 이 속성을 사용하십시오.
어셈블리가 [assembly: AlwaysLinkAssembly]를 정의하며 빌드에 포함된 다른 어셈블리에 의해 참조되는 경우 이 속성은 결과에 영향을 미치지 않습니다.
link.xml 파일은 어셈블리 및 어셈블리에 포함된 타입과 기타 코드 엔티티를 보존하는 방식을 선언하는 프로젝트별 목록입니다. link.xml 파일을 사용하려면 이 파일을 만들어(아래 예시 참조) Project Assets 폴더(또는 Assets의 하위 디렉토리)에 추가하십시오. 프로젝트에 원하는 수만큼 link.xml 파일을 추가하여 플러그인의 자체 보존 선언을 제공할 수 있습니다. UnityLinker는 link.xml 파일에 보존된 어셈블리, 타입 또는 멤버를 루트 타입으로 취급합니다.
link.xml 파일은 패키지 내에서는 지원되지 않으나, 비패키지 link.xml 파일의 패키지 어셈블리를 참조할 수 있습니다.
다음 예시는 link.xml 파일을 사용하여 프로젝트 어셈블리의 루트 타입을 선언하는 다양한 방법을 나타냅니다.
<linker>
<!--
Preserve types and members in an assembly
-->
<assembly fullname="Assembly1">
<!--Preserve an entire type-->
<type fullname="Assembly1.A" preserve="all"/>
<!--No "preserve" attribute and no members specified
means preserve all members-->
<type fullname="Assembly1.B"/>
<!--Preserve all fields on a type-->
<type fullname="Assembly1.C" preserve="fields"/>
<!--Preserve all fields on a type-->
<type fullname="Assembly1.D" preserve="methods"/>
<!--Preserve the type only-->
<type fullname="Assembly1.E" preserve="nothing"/>
<!--Preserving only specific members of a type-->
<type fullname="Assembly1.F">
<!--
Fields
-->
<field signature="System.Int32 field1" />
<!--Preserve a field by name rather than signature-->
<field name="field2" />
<!--
Methods
-->
<method signature="System.Void Method1()" />
<!--Preserve a method with parameters-->
<method signature="System.Void Method2(System.Int32,System.String)" />
<!--Preserve a method by name rather than signature-->
<method name="Method3" />
<!--
Properties
-->
<!--Preserve a property, it's backing field (if present),
getter, and setter methods-->
<property signature="System.Int32 Property1" />
<property signature="System.Int32 Property2" accessors="all" />
<!--Preserve a property, it's backing field (if present), and getter method-->
<property signature="System.Int32 Property3" accessors="get" />
<!--Preserve a property, it's backing field (if present), and setter method-->
<property signature="System.Int32 Property4" accessors="set" />
<!--Preserve a property by name rather than signature-->
<property name="Property5" />
<!--
Events
-->
<!--Preserve an event, it's backing field (if present),
add, and remove methods-->
<event signature="System.EventHandler Event1" />
<!--Preserve an event by name rather than signature-->
<event name="Event2" />
</type>
<!--Examples with generics-->
<type fullname="Assembly1.G`1">
<!--Preserve a field with generics in the signature-->
<field signature="System.Collections.Generic.List`1<System.Int32> field1" />
<field signature="System.Collections.Generic.List`1<T> field2" />
<!--Preserve a method with generics in the signature-->
<method signature="System.Void Method1(System.Collections.Generic.List`1<System.Int32>)" />
<!--Preserve an event with generics in the signature-->
<event signature="System.EventHandler`1<System.EventArgs> Event1" />
</type>
<!--Preserve a nested type-->
<type fullname="Assembly1.H/Nested" preserve="all"/>
<!--Preserve all fields of a type if the type is used. If the type is not
used it will be removed-->
<type fullname="Assembly1.I" preserve="fields" required="0"/>
<!--Preserve all methods of a type if the type is used.
If the type is not used it will be removed-->
<type fullname="Assembly1.J" preserve="methods" required="0"/>
<!--Preserve all types in a namespace-->
<type fullname="Assembly1.SomeNamespace*" />
<!--Preserve all types with a common prefix in their name-->
<type fullname="Prefix*" />
</assembly>
<!--Preserve an entire assembly-->
<assembly fullname="Assembly2" preserve="all"/>
<!--No "preserve" attribute and no types specified means preserve all-->
<assembly fullname="Assembly3"/>
<!--Fully qualified assembly name-->
<assembly fullname="Assembly4, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="Assembly4.Foo" preserve="all"/>
</assembly>
<!--Force an assembly to be processed for roots but don’t explicitly preserve
anything in particular. Useful when the assembly is not referenced.-->
<assembly fullname="Assembly5" preserve="nothing"/>
</linker>
link.xml 파일의 <assembly> 요소에는 특수한 목적을 지닌 3가지 속성이 있습니다.
ignoreIfMissing 기본적으로 UnityLinker는 link.xml 파일에 참조된 어셈블리를 찾을 수 없는 경우 빌드를 중단합니다. 모든 플레이어 빌드 동안 존재하지 않는 어셈블리에 대해 보존을 선언해야 하는 경우 다음과 같이 link.xml 파일 내 <assembly> 요소의 ignoreIfMissing 속성을 사용하십시오.
<linker>
<assembly fullname="Foo" ignoreIfMissing="1">
<type name="Type1"/>
</assembly>
</linker>
ignoreIfUnreferenced
어셈블리가 다른 어셈블리에 의해 참조되는 경우에만 해당 어셈블리의 엔티티를 보존하려는 경우, link.xml 파일 내 <assembly> 요소의 ignoreIfUnreferenced 속성을 사용하면 어셈블리에서 하나 이상의 타입이 참조된 경우에만 어셈블리의 엔티티를 보존할 수 있습니다.
<linker>
<assembly fullname="Bar" ignoreIfUnreferenced="1">
<type name="Type2"/>
</assembly>
</linker>
참고: 참조된 어셈블리의 코드의 스트리핑 여부는 중요하지 않으며, 참조된 어셈블리에서 이 속성이 정의된 요소는 계속해서 보존됩니다.
windowsruntime
Windows 런타임 메타데이터(.winmd) 어셈블리에 대한 보존을 정의하는 경우, link.xml 파일의 <assembly> 요소에 windowsruntime=“true” 속성을 추가해야 합니다.
<linker>
<assembly fullname="Windows" windowsruntime="true">
<type name="Type3"/>
</assembly>
</linker>
Unity 에디터는 Unity 프로젝트의 씬에서 사용된 타입을 포함하는 어셈블리의 목록을 추려 UnityLinker에 전달합니다. 그러면 UnityLinker가 해당 어셈블리, 해당 어셈블리의 모든 참조, link.xml 파일에 선언된 어셈블리 및 AlwaysLinkAssembly 속성이 정의된 어셈블리를 처리합니다. 일반적으로 이러한 카테고리에 해당하지 않는 프로젝트 내 어셈블리는 UnityLinker에 의해 처리되지 않으며 플레이어 빌드에 포함되지 않습니다.
UnityLinker는 어셈블리를 처리할 때마다 어셈블리 분류, 어셈블리가 씬에서 사용되는 타입을 포함하는지 여부 및 사용자가 해당 빌드에 대해 선택한 Managed Stripping Level 에 따라 일련의 규칙을 준수합니다.
이러한 규칙에서 어셈블리는 다음과 같이 분류됩니다.
.NET Class Library assemblies — mscorlib.dll과 System.dll과 같은 Mono 클래스 라이브러리 및 netstandard.dll과 같은 .NET 클래스 라이브러리 파사드 어셈블리를 모두 포함합니다.
Platform SDK assemblies — 플랫폼 SDK의 관리되는 어셈블리를 포함합니다(예: 유니버설 Windows 플랫폼 SDK의 일부인 windows.winmd 어셈블리).
Unity Engine Module assemblies — UnityEngine.Core.dll 등 Unity 엔진을 구성하는 관리되는 어셈블리를 포함합니다.
Project assemblies — 프로젝트 어셈블리를 포함합니다. 예:
Assembly-CSharp.dll과 같은 스크립트 어셈블리
사전 컴파일된 어셈블리
패키지 어셈블리
다음 섹션은 UnityLinker가 각 Managed Stripping Level 설정에 대해 어셈블리 코드를 마킹, 보존 및 스트리핑하는 방법을 설명합니다.
Low Managed Stripping Level 을 선택하면 UnityLinker는 도달할 수 없는 코드를 대부분 제거하는 동시에 실제로 사용되는 코드를 스트리핑할 가능성은 최소화하는 보수적인 규칙에 따라 코드를 제거합니다. Low 스트리핑 레벨에서는 가용성이 크기 축소보다 우선합니다.
루트 마킹 규칙은 UnityLinker가 어셈블리의 상위 레벨 타입을 식별하는 방식을 규정합니다.
어셈블리 타입 | 액션 | 루트 마킹 규칙 |
---|---|---|
.NET 클래스 & 플랫폼 SDK | 스트립 | 예방적 보존 적용 |
모든 link.xml에 정의된 보존 | ||
씬에서 사용되는 타입을 포함한 어셈블리 | 복사 | 어셈블리의 모든 타입과 멤버 마킹 |
기타 | 스트립 | 모든 공용 타입 마킹 |
공용 타입의 모든 공용 멤버 마킹 | ||
[RuntimeInitializeOnLoadMethod] 속성이 지정된 메서드 마킹 | ||
[Preserve] 속성이 지정된 타입과 멤버 마킹 | ||
모든 link.xml에 정의된 보존 | ||
다음 어셈블리에서 MonoBehaviour 및 ScriptableObject에서 파생된 모든 타입 마킹 사전 컴파일된 어셈블리 패키지 어셈블리 어셈블리 정의 어셈블리 Unity 스크립트 어셈블리 |
||
테스트 | 스트립 | NUnit.Framework에 정의된 속성이 포함된 메서드 마킹. 예: [Test] |
[UnityTest] 속성이 포함된 메서드 마킹 |
참고: Strip 액션은 UnityLinker가 제거할 수 있는 코드의 어셈블리를 분석함을 나타냅니다. Copy 액션은 UnityLinker가 어셈블리 전체를 최종 빌드로 복사하고 어셈블리 내의 모든 타입을 루트 타입으로 마킹함을 나타냅니다.
UnityLinker는 루트 타입을 마킹한 후 정적 분석을 수행하여 루트가 의존하는 코드를 식별합니다.
규칙 타겟 | 설명 |
---|---|
Unity 타입 | UnityLinker가 MonoBehaviour에서 파생된 타입을 마킹하는 경우 해당 타입의 모든 멤버도 마킹합니다. |
UnityLinker가 ScriptableObject에서 파생된 타입을 마킹하는 경우 해당 타입의 모든 멤버도 마킹합니다. | |
속성 | UnityLinker가 모든 마킹된 어셈블리, 타입, 메서드, 필드, 프로퍼티 등의 모든 속성을 마킹합니다. |
Debugging 속성 | 스크립트 디버깅을 활성화하면 UnityLinker는 멤버를 사용하는 코드 경로가 없어도 [DebuggerDisplay] 속성이 정의된 모든 멤버를 마킹합니다. |
.NET 파사드 클래스 라이브러리 어셈블리 | 파사드 어셈블리는 .NET 클래스 라이브러리의 어셈블리로, 다른 어셈블리에 타입 정의를 전달합니다. 예를 들어 .NET Standard 2.0 API 호환성 레벨의 일부인 netstandard.dll 은 .NET 인터페이스를 정의하는 파사드 어셈블리이지만, .NET 인터페이스의 구현을 다른 .NET 어셈블리로 전달합니다.파사드 어셈블리는 런타임 시 반드시 필요하지는 않으나, 이러한 어셈블리에 의존하는 반사 코드를 작성할 수 있으므로 낮은 스트리핑 레벨을 통해 보존할 수 있습니다. |
다음 예시에서는 코드에서 Foo.UnusedProperty
프로퍼티를 사용하지 않는다고 가정합니다. 그러면 보통 UnityLinker가 해당 프로퍼티를 스트리핑하지만, 스크립트 디버깅을 활성화하면 Foo
의 [DebuggerDisplay] 속성 때문에 Foo.UnusedProperty
를 마킹하고 보존합니다.
[DebuggerDisplay("{UnusedProperty}")]
class Foo
{
public int UnusedProperty { get; set; }
}
Medium Managed Stripping Level 을 선택하면 UnityLinker가 Low 스트리핑 레벨과 High 스트리핑 레벨 간의 균형을 추구하는 규칙에 따라 코드를 제거합니다. Medium 스트리핑 레벨은 Low 스트리핑 레벨보다 덜 보수적이지만 High 스트리핑 레벨보다 덜 적극적입니다. 따라서 코드 제거로 인한 부작용이 발생할 가능성도 Low 스트리핑 레벨과 High 스트리핑 레벨의 중간 수준입니다.
어셈블리 타입 | 액션 | 루트 마킹 규칙 |
---|---|---|
.NET 클래스 & 플랫폼 SDK | 스트립 | 예방적 보존을 적용하지 않는다는 점 외에 낮은 스트리핑 레벨과 동일 |
씬에서 참조된 타입을 포함한 어셈블리 | 스트립 | 어셈블리의 모든 타입과 멤버를 자동으로 마킹하지 않음 |
[RuntimeInitializeOnLoadMethod] 속성이 지정된 메서드 마킹 | ||
[Preserve] 속성이 지정된 타입과 멤버 마킹 | ||
모든 link.xml에 정의된 보존 | ||
다음 어셈블리에서 MonoBehaviour 및 ScriptableObject에서 파생된 모든 타입 마킹 사전 컴파일된 어셈블리 패키지 어셈블리 어셈블리 정의 어셈블리 Unity 스크립트 어셈블리 |
||
기타 | 스트립 | 다음을 제외하면 낮은 스트리핑 레벨과 동일: 공용 타입이 자동으로 마킹되지 않음 공용 타입의 공용 멤버가 자동으로 마킹되지 않음 |
테스트 | 스트립 | 낮은 스트리핑 레벨과 동일 |
규칙 타겟 | 설명 |
---|---|
Unity 타입 | 낮은 스트리핑 레벨과 동일 |
속성 | 낮은 스트리핑 레벨과 동일 |
Debugging 속성 | 낮은 스트리핑 레벨과 동일 |
.NET 파사드 클래스 라이브러리 어셈블리 | 낮은 스트리핑 레벨과 동일 |
High Managed Stripping Level 을 선택하면 UnityLinker가 도달할 수 없는 코드를 최대한 제거하고 Medium 스트리핑 레벨보다 작은 빌드를 생성합니다. High 스트리핑 레벨에서는 크기 축소가 가용성보다 우선합니다. link.xml 파일과 Preserve 속성을 추가하거나 문제가 되는 코드 섹션을 재작성해야 할 수 있습니다.
어셈블리 타입 | 액션 | 루트 마킹 규칙 |
---|---|---|
.NET 클래스 & 플랫폼 SDK | 스트립 | 중간 스트리핑 레벨과 동일 |
씬에서 참조된 타입을 포함한 어셈블리 | 스트립 | 중간 스트리핑 레벨과 동일 |
기타 | 스트립 | 중간 스트리핑 레벨과 동일 |
테스트 | 스트립 | 낮은 스트리핑 레벨과 동일 |
Link.xml 파일은 자주 사용되지 않는 “features” XML 속성을 지원합니다. 예를 들어 mscorlib.dll에 내장된 mscorlib.xml 파일이 이 속성을 사용하지만, 적합한 경우 모든 link.xml 파일에서 사용할 수 있습니다.
High 레벨 스트리핑 중에는 UnityLinker가 현재 빌드의 설정에 기반하여 지원되지 않는 기능의 보존을 제외합니다.
예를 들어 다음의 link.xml 파일은 COM을 지원하는 플랫폼에 포함된 타입의 메서드 1가지 및 모든 플랫폼의 메서드 1가지를 지원합니다.
<linker>
<assembly fullname="Foo">
<type fullname="Type1">
<!--Preserve FeatureOne on platforms that support COM-->
<method signature="System.Void FeatureOne()" feature="com"/>
<!--Preserve FeatureTwo on all platforms-->
<method signature="System.Void FeatureTwo()"/>
</type>
</assembly>
</linker>
규칙 타겟 | 설명 |
---|---|
Unity 타입 | 낮은 스트리핑 레벨과 동일 |
속성 | UnityLinker는 모든 마킹된 어셈블리, 타입과 멤버에 대해 속성 타입이 마킹된 속성을 모두 마킹합니다. UnityLinker는 런타임이 필요로 하는 일부 속성을 항상 보존합니다. UnityLinker는 모든 어셈블리, 타입과 멤버에서 System.Security.Permissions.SecurityPermissionAttribute와 같이 Security에 관련된 모든 속성을 제거합니다. |
Debugging 속성 | UnityLinker는 DebuggerDisplayAttribute 및 DebuggerTypeProxyAttribute와 같은 디버깅 속성을 항상 제거합니다. |
.NET 파사드 클래스 라이브러리 어셈블리 | 모든 .NET 파사드 어셈블리를 보존하는 낮은 스트리핑 레벨과 중간 스트리핑 레벨과는 달리, 높은 스트리핑 레벨에서는 런타임에 반드시 필요하지 않으므로 모든 파사드를 제거합니다. 이로 인해 스트리핑 후에도 파사드 어셈블리가 존재한다고 가정하는 반사 코드가 작동하지 않게 됩니다. |
High 스트리핑 레벨을 설정하면 UnityLinker가 코드 크기를 더욱 축소하기 위해 메서드 바디를 편집합니다. 이 섹션에서는 UnityLinker가 메서드 바디에 수행하는 주요 편집 몇 가지를 요약하여 설명합니다.
현재 UnityLinker는 .NET 클래스 라이브러리 어셈블리의 메서드 바디만 편집합니다. 메서드 바디 편집 후에는 어셈블리의 소스 코드가 더 이상 어셈블리의 컴파일된 코드와 일치하지 않으므로 디버깅이 더 어려워질 수 있습니다.
UnityLinker가 System.Environment.OSVersion.Platform
을 검사하며 현재 타겟팅하는 플랫폼에서 도달 가능하지 않은 If문 블록을 제거합니다.
UnityLinker가 필드에 대한 직접 액세스를 통해 필드를 가져오거나 설정하는 메서드 호출을 대체합니다. 대부분의 경우 이를 통해 메서드 전체를 스트리핑하여 크기를 축소할 수 있습니다.
Mono 백엔드 타겟팅 시 UnityLinker는 메서드의 호출자가 필드에 직접 액세스하도록 승인된 경우에만 필드의 가시성 여부에 따라 이러한 변경을 수행합니다. IL2CPP의 경우 가시성 규칙이 적용되지 않으므로 UnityLinker는 적절한 경우 이러한 변경을 수행합니다.
UnityLinker가 const 값만을 반환하는 메서드 호출을 인라이닝합니다.
UnityLinker가 반환 타입이 void
인 빈 메서드 호출을 제거합니다.
UnityLinker는 Finally 블록이 비어 있는 경우 Try/Finally 블록을 제거합니다. 빈 호출을 제거하면 빈 Finally 블록이 생성될 수 있습니다. 메서드 편집 중 이러한 현상이 발생하면 UnityLinker는 Try/Finally 블록 전체를 제거합니다. 예를 들어 컴파일러가 Dispose()
를 호출하기 위해 Foreach 루프의 일부로 Try/Finally 블록을 생성하는 경우 이러한 현상이 발생할 수 있습니다.