Unity 검색

메모리 사용량을 절감하는 에셋 번들 사용법

공유

Is this article helpful for you?

Thank you for your feedback!

애플리케이션에서 콘텐츠 전송 네트워크(CDN)로 에셋을 스트리밍하거나, 하나의 대용량 바이너리로 에셋을 한꺼번에 패킹해봤다면 에셋 번들에 대해 들어보셨을 겁니다. 에셋 번들은 직렬화된 에셋(텍스처, 메시, 오디오 클립, 셰이더 등)을 하나 이상 포함하며 런타임 시 로드할 수 있는 파일입니다.

에셋 번들은 직접 사용하거나 Unity 어드레서블 에셋 시스템(줄여서 어드레서블)과 같은 시스템을 통해 사용할 수 있습니다. 어드레서블 시스템은 프로젝트 내 에셋을 보다 편리하게 관리하는 방법을 지원하는 패키지로, 에셋 번들보다 상위 수준의 추상화를 제공합니다. 어드레서블은 개발자와 에셋 번들 간의 직접적인 상호작용을 최소화해주지만, 에셋 번들을 이용하는 경우 메모리 사용량에 어떤 영향이 있는지 이해할 필요가 있습니다. 어드레서블 시스템에 대해서는 블로그 포스팅유나이트 코펜하겐 2019 세션을 참조하세요.

신규 프로젝트를 진행하는 개발자는 에셋 번들을 직접 사용하는 대신 어드레서블을 사용할 것을 권장합니다. 또한 프로젝트에서 이미 에셋 번들 방식을 사용하고 있다면 이번 포스팅에서 소개해드리는 에셋 번들이 런타임 메모리에 미치는 영향을 참고하여 가장 이상적인 결과를 얻으시기 바랍니다.

메모리 캐시로 인한 높은 메모리 사용

Unity는 WWW 클래스(현재 지원 중단)또는 UnityWebRequestAssetBundle(UWR)을 사용하여 LZMA 에셋 번들을 다운로드할 때 메모리 캐시와 디스크 캐시를 이용하여 에셋 번들 가져오기, 재압축 및 버전화를 최적화합니다.

메모리 캐시로 로드된 에셋 번들은 메모리를 많이 소모합니다. 따라서 에셋 번들의 콘텐츠에 자주, 신속하게 액세스해야 하는 경우 외에는 권장하지 않습니다. 그 대신 디스크 캐시를 사용하세요.

UnityWebRequestAssetBundle API에 버전 또는 해시 인수를 제공하면 Unity가 에셋 번들 데이터를 디스크 캐시에 저장합니다. 인수를 제공하지 않는 경우 Unity는 메모리 캐시를 사용합니다. 어드레서블은 기본적으로 디스크 캐시를 사용합니다. 이 동작은 UseAssetBundleCache 필드로 제어할 수 있습니다.

AssetBundle.LoadFromFile()AssetBundle.LoadFromFileAsync()의 경우 LZMA 에셋 번들을 보관할 때 항상 메모리 캐시를 사용합니다. 따라서 되도록 UnityWebRequestAssetBundle API를 사용하도록 합니다. UnityWebRequestAssetBundle API 사용이 어려운 경우, AssetBundle.RecompressAssetBundleAsync()를 사용하여 디스크에서 LZMA 에셋 번들을 재작성할 수 있습니다.

내부 테스트 결과, 디스크 캐시를 사용하는 경우와 메모리 캐시를 사용하는 경우 RAM 사용량 차이가 최소 10배에 달하는 것으로 나타났습니다. 따라서 메모리 소모량과 추가로 필요한 디스크 공간 및 에셋 인스턴스화 시간 중 어느 것이 더 중요한지 잘 판단해야 합니다.

에셋 번들 메모리 캐시가 애플리케이션의 메모리 사용량에 주는 영향을 알아보려면 네이티브 프로파일러(여기서는 Xcode의 Allocations Instrument 사용)를 사용하여 ArchiveStorageConverter 클래스의 할당을 점검합니다. 이 클래스가 RAM을 10MB 이상 차지한다면 메모리 캐시를 사용하고 있을 가능성이 높습니다.

ArchiveStorageConverter 클래스의 메모리 사용량을 나타내는 Xcode의 Allocations Instrument

에셋 번들이 실제로 로드하는 요소 확인

대규모 프로젝트의 에셋 번들을 구축할 때는 Unity가 기본적으로 중복된 정보를 최소화한다고 가정하면 안 됩니다. 대신 유니티 컨설팅 및 개발팀에서 Python 언어로 작성한 AssetBundle Analyzer를 사용하여 생성된 에셋 번들의 중복 데이터 인스턴스를 편리하게 파악할 수 있습니다. 커맨드 라인을 통해 사용하는 이 툴은 생성된 에셋 번들에서 정보를 추출한 후, SQLite 데이터베이스에 저장하여 다양한 보기 방식을 제공합니다. 데이터베이스는 DB Browser for SQLite와 같은 툴을 사용하여 쿼리할 수 있습니다. 이 툴은 수동으로 또는 어드레서블을 통해 제작한 모든 번들에서 빌드 파이프라인의 비효율성 문제를 파악하고 해결하는 데 사용할 수 있습니다.

DB Browser for SQLite를 사용하여 AssetBundle Analyzer 툴의 결과를 분석하는 모습

또는 프로젝트에 바로 통합할 수 있는 AssetBundle Browser 툴다운로드해서 사용할 수도 있습니다. 이 툴은 어드레서블과 비슷한 기능을 지원하므로, 어드레서블을 사용하는 경우에는 해당되지 않습니다.

AssetBundle Browser 툴을 사용하면 개별 Unity 프로젝트의 에셋 번들 설정을 확인하고 수정할 수 있습니다. 이 툴은 빌드 기능을 비롯하여 종속성으로 인해 가져온 중복된 에셋(예: 텍스처)을 알려주는 등 유용한 기능을 지원합니다.

AssetBundle Browser 툴은 종속성으로 인해 두 개 이상의 번들에서 가져온 중복된 에셋에 대해 알려줍니다.

에셋 중복으로 인한 메모리 손실

에셋 번들에 에셋을 정리할 때는 종속성에 유의해야 합니다. Unity는 에셋 번들 토폴로지와 상관없이 (리소스 폴더에 있거나 리소스 폴더와 관련된) 애플리케이션 바이너리 내에 존재하는 에셋과 에셋 번들에서 로드해야 하는 에셋을 구분합니다. 이 두 에셋 유형은 서로 완전히 다른 세상에 존재한다고 표현할 수 있습니다. 예를 들어, '리소스 폴더 세상’에 존재하는 에셋의 인스턴스에 대해 강한 참조(hard reference)가 있는 에셋 번들을 생성할 수는 없습니다. 대신 Unity는 이러한 에셋을 참조하기 위해 '에셋 번들 세상’에서 사용할 수 있는 에셋 사본을 만듭니다.

두 컴포넌트에 'logo' 이미지에 대한 강한 참조가 있습니다. 이러한 컴포넌트가 플레이어와 함께 하나의 번들로 묶이거나 에셋 번들에 포함되는 등 서로 다른 아카이브로 직렬화되는 경우, 각 컴포넌트에 이미지의 사본이 별도로 포함됩니다.

게임의 로고를 예로 들어보겠습니다. 로고는 게임이 시작되는 동안 로딩 씬의 UI에 표시됩니다. 이러한 로딩 화면은 원격 에셋이 디스크로 스트리밍되기 전에 표시되어야 하므로 로고 에셋을 즉시 사용할 수 있도록 빌드에 포함해야 합니다.

이 로고는 사용자가 언어, 사운드 환경 설정 및 기타 설정을 선택하는 데 사용하는 UI 옵션 패널에도 사용됩니다. 이 UI 패널이 에셋 번들에서 로드되면 해당 에셋 번들이 로고 에셋의 사본을 자체적으로 생성합니다.

로딩 화면과 옵션 패널이 동시에 로드되는 경우, 로고 에셋 사본이 중복으로 로드되므로 불필요한 메모리 소모가 발생합니다.

이 문제는 스크린 간의 하드 링크를 해제하는 방법으로 해결할 수 있습니다. 로고가 에셋 번들에 있는 경우, 일정량 이상의 스트리밍이 발생해야만 에셋에 대한 참조를 확보할 수 있습니다. 로고가 바이너리(예: 리소스 폴더)에 있는 경우, UI 패널이 로고 에셋에 대해 약한 참조(weak reference)를 보유하고 Resources.Load와 같은 API를 통해 로드되어야 합니다.

로고 이미지에 대한 약한 참조로 스트링이 사용되었습니다. 사용자 스크립팅에서 스트링을 사용해야만 런타임 시 이미지를 로드하고 적절한 컴포넌트에 할당할 수 있습니다.

사용자 스크립팅에서 스트링을 사용해야만 런타임 시 이미지를 로드하고 적절한 컴포넌트에 할당할 수 있습니다. 로고 에셋이 있는 에셋 번들을 애플리케이션의 StreamingAssets 디렉토리에 포함하는 방법도 좋은 절충안이 될 수 있습니다. 그러면 계속해서 에셋 번들에서 에셋을 로드하되, 번들을 로컬에서 호스팅하므로 콘텐츠 다운로드에 소요되는 시간을 절약할 수 있습니다.

에셋 참조에 스트링, 경로 또는 GUID를 사용하면 직관적이지 않지만, 약한 참조가 사용된 필드에는 Unity의 기본 드래그 앤 드롭 참조 기능을 활성화하는 커스텀 인스펙터를 만드는 것이 좋습니다. 그리고 반드시 Unity의 메모리 프로파일러 패키지를 사용하여 메모리에서 중복된 에셋을 식별하세요. 어드레서블 시스템은 자체적인 메커니즘에 기반하여 종속 관계에서 중복을 확인합니다. 자세한 내용은 기술 자료를 참조하세요.

마무리

어드레서블 시스템은 에셋 번들보다 높은 수준의 추상화를 제공하지만, 원리를 알아두면 위에서 설명한 것과 같은 심각한 성능 문제를 방지할 수 있습니다.

이 시리즈의 후속 포스팅과 로드맵을 준비하고 있습니다. 혹시 더 자세히 다루었으면 하는 분야가 있다면 댓글로 알려주시기 바랍니다.

Is this article helpful for you?

Thank you for your feedback!