본문 바로가기

Unity/Articles

Unity: 2022.1 IL2CPP 제네릭 공유 개선점이 가지는 의미

Feature preview: IL2CPP Full Generic Sharing in Unity 2022.1 beta | Unity Blog

 

Feature preview: IL2CPP Full Generic Sharing in Unity 2022.1 beta | Unity Blog

This code demonstrates the expressiveness of generic virtual methods. In other words, we can send data of any type (the “message”) from any class that implements the IManager interface to any class that implements the IReceiver interface. With IL2CPP i

blog.unity.com

 

IL2CPP는 C#으로 작성된 유니티 스크립트를 C++ 코드로 변환해주는 스크립팅 백엔드 라이브러리입니다. 

사용자가 작성한 클래스와 구조체들을 C++ 코드로 자동으로 변환해주며 유니티엔진 게임오브젝트와 연결시켜주는 기능도 함께 포함되어있습니다.

 

사용자는 C# 으로 코드를 작성하면 정해진 규칙에 따라 코드가 자동으로 생성되고 연결되는데, 이 자동화 과정에서 제네릭 클래스들은 적절한 코드를 생성하기가 꽤 까다롭습니다. 예를 들어, List<T>와 같은 자료형이 사용되면 기존에는 다음과 같은 과정을 거쳤습니다.

 

1. 컴파일 타임에서 T에 사용된 모든 타입을 찾습니다.

2. 타입의 구조, 패딩이 동일한 것끼리 묶습니다.

3. 찾은 데이터 구조 개수만큼 타입이 지정된 일반 클래스 메서드 코드가 생성됩니다.


1. 

간혹, 컴파일 에러가 발생하지 않았음에도 런타임에서 IL2CPP Execution 예외를 마주칠 때가 있습니다. 이것은 1번 컴파일 타임의  타입 탐지 과정에서 제네릭 타입에 사용될 것이라고 간주되지 않았던 타입이 사용된 경우, 해당 타입에 대해 미리 생성된 코드가 없는 경우 발생합니다. 기술적으로 이 문제는 해결가능하지만, 핵심 개발자 Josh는 유니티 IL2CPP 포럼에서 많은 시간이 걸리고 사용되지 않을 코드까지 생성하게 될 수도 있는 한계가 있어 기본 옵션으로 설정되어 있지 않다고 언급한 바 있습니다.

 

2.

클래스, 구조체의 padding을 떠올리시면 비교적 쉽게 이해할 수 있습니다.

class Character {
  int health;
  int level;
  string name;
  int heroType;
}

class Monster {
  int uniqueId;
  int health;
  string displayName;
  int monsterType;
}

위 두 가지 클래스는 내부 필드 구성요소의 종류와 순서가 일치합니다. 동일한 클래스를 상속받지 않았더라도, int-int-string-int로 메모리 구성이 동일합니다. 이 두 클래스는 List<T>에서 T로 들어갈 때, 하나의 구조로 취급되어 코드가 한 번만 생성됩니다.

 

확신을 위해 한 가지 예시를 더 확인해보겠습니다.

struct Vector3Int {
  int x;
  int y;
  int z;
}

struct UserInfo {
  int uid;
  int token;
  int sessionId;
}

Vector3Int는 유니티에서 정의한 구조체이고, UserInfo는 유저 인증을 위해 사용자가 정의한 데이터 오브젝트 구조체입니다. 마찬가지로 3개 int로 구성이 같으며, 위 두 구조체를 T로 사용하는 제네릭 코드들은 한 번만 생성됩니다.

 

 

기존의 IL2CPP 빌드 파이프라인에서는 이런 방식으로 제네릭에 사용될 수 있다고 간주되는 모든 타입의 구조에 따라 코드를 생성했습니다. 이번에 개선된 버전의 IL2CPP 빌드 파이프라인에서는 제네릭에 활용되는 클래스를 서로 공유하여 사용할 수 있도록 코드를 생성하도록 변경하였습니다. 다양한 타입에 따라 여러벌의 코드뭉치를 생성하지 않아도 되므로, 개발자 레이어 및 사용자 레이어에서도 몇 가지의 이점을 볼 수 있게 됩니다.


1. 램에 상주하는 메모리 감소

2. 컴파일된 코드 바이너리 용량 감소

 

1.

유니티는 IL2CPP로 빌드되면 global-metadata.dat 파일에 심볼들과 메소드 이름을 저장해두고 매핑하는데 사용하는데 이 용량이 줄어들 수 있습니다. 생성된 코드들과 타입이 미리 지정된 주소에 있음을 연결해주는 파일인데, 모든 타입으로부터의 코드를 생성할 필요가 없어 코드가 줄어듦에 따라 인식용 연결 데이터도 함께 줄어드는 개념입니다.

앱이 부팅하면서 파일시스템에 존재하는 global-metadata.dat를 로드하고 읽어들이며 필요한 심볼 정보를 메모리에 적재하고, 파일을 언로드합니다. 소량이나마 메모리에 올라와있어야하는 데이터가 줄어들어 제네릭을 다수 사용하면 부담이 되던 심볼크기가 줄어듭니다.

 

2. 

공유해서 사용할 수 있게 되어 타입만 다르고 같은 코드를 여러 벌 다수 생성해낼 필요가 없게되었습니다. 그만큼 필요없는 메소드 블럭들이 사라지므로, 배포되어야 할 바이너리 크기도 줄어듭니다. 압축된 아트 리소스에 비하면 미미하겠지만, 큰 손해 없이 조금이나마 줄어든다는 점에서는 긍정적인 부분입니다. 1. 에서 언급된 global-metadata.dat의 파일 크기도 줄어드므로 그 만큼 초기 앱 배포 용량도 줄어듭니다.