Unity 검색

Extended Q&A: Optimizing memory and build size with Addressables | Hero image
Extended Q&A: Optimizing memory and build size with Addressables | Hero image
공유

Is this article helpful for you?

Thank you for your feedback!

저는 2월에 Unity Accelerate Solutions 시니어 소프트웨어 개발 컨설턴트로서 어드레서블 에셋 시스템에 관한 기술 웨비나를 진행했습니다. 라이브 세션을 진행하며 프로젝트의 런타임 메모리와 빌드 크기를 최적화하는 데 사용할 수 있는 다양한 프로파일링 툴을 시연했습니다. Q&A로 웨비나를 마무리했는데, 답변에 사용할 수 있는 시간보다 많은 질문이 들어왔습니다.

더 많은 질문에 답변해 드릴 수 있도록 다음과 같은 Q&A를 추가로 준비했습니다.


Q: 메모리 문제가 없는 상황에서 캐주얼이나 아케이드 또는 퍼즐과 같은 가벼운 게임에 어드레서블 시스템이 필요할까요?
A: 아마 필요하지 않을 수도 있지만, 어드레서블 시스템으로 메모리 성능만 개선하는 것이 아니라는 점을 염두에 두시기 바랍니다. 콘텐츠를 로드할 시점을 선택할 수 있으면 로딩 시간을 개선할 수 있습니다. 어드레서블에서 콘텐츠를 빌드하면 비교적 시간이 오래 걸리지 않는 반복 작업용 빌드를 확보할 수 있습니다. 이를테면 스크립트를 약간 변경한 경우에는 번들 전체를 다시 빌드할 필요가 없습니다.

Q: 씬이 전환되면 로드된 에셋이 릴리스되나요?
A: 그럴 수 있습니다. 어드레서블에서 로드되었으며 ref 카운트가 0이라 릴리스 준비가 된 에셋은 씬 전환 시 메모리에서 언로드될 수 있습니다. 비가산적으로 씬에서 전환되는 경우 Resources.UnloadUnusedAssets()를 호출하세요. 이렇게 하면 CPU에 부담이 되지만 AssetBundle을 부분적으로 언로드할 수 있습니다.

Q: 오브젝트 풀링과 어드레서블이 서로 문제없이 연동되나요?
A: 네. 어드레서블에서 오브젝트를 로드한 다음 오브젝트의 여러 사본을 인스턴스화하여 풀을 생성할 수 있습니다. 풀 작업이 끝나면 모든 오브젝트를 파괴하고 에셋을 로드하는 데 사용된 AsyncOperationHandle을 릴리스하세요.

Q: 그룹과 번들이 한꺼번에 메모리로 로드되나요?
A: 어드레서블 그룹은 에디터 전용 개념이며 런타임 시에는 번들만 처리하게 됩니다. 번들은 필요할 때에만 메모리로 로드되며, 필요한 콘텐츠만 로드됩니다.

예를 들어, 10개의 문자가 포함된 하나의 번들이 있을 때 문자 3개를 로드하라고 어드레서블에 요청하면 번들의 메타데이터와 해당 문자 3개가 로드됩니다.

Q: 에셋을 릴리스하려 할 때 AsyncOperationHandle 또는 AssetReference를 유지해야 하나요?
A: 사용이 끝난 콘텐츠를 릴리스해야 하므로 핸들을 유지하고 사용하는 것이 좋습니다.

예를 들어 저희 팀원들은 AssetReference에서 바로 Instantiate/Release가 호출되지 않도록 핸들 경로로 자주 이동할 수 있습니다.

Q: 작은 번들이 많으면 어떤 단점이 있나요?
A: 이 기술 자료에서 번들이 너무 많을 때의 단점을 확인할 수 있습니다.

Q: 번들에서 에셋 하나가 필요할 때 같은 번들에 있는 다른 에셋에서 어떤 오버헤드가 발생하나요? 원격 번들이라면 반드시 다운로드해야 하겠지만, 번들의 미사용 에셋에서 실제로 메모리 오버헤드가 발생하지 않나요?
A: 맞습니다. 원격 번들을 사용하려면 전체를 다운로드해야 합니다.

로드된 에셋 번들의 언로드된 에셋에서는 런타임 시 아주 적은 오버헤드가 발생합니다. 번들에서 에셋을 로드할 때마다 번들의 메타데이터를 로드해야 합니다. 이 메타데이터의 일부에는 번들의 모든 에셋이 나열된 목차가 포함되어 있습니다. 번들에 포함된 에셋이 많을수록 메타데이터도 커집니다.

Unity 메모리 프로파일러로 캡처하면 이러한 메모리 오버헤드를 확인할 수 있습니다. 'All Of Memory' 탭에는 메모리의 모든 'SerializedFile' 오브젝트 목록이 번들마다 하나씩 있습니다. 이러한 오브젝트는 번들의 메타데이터입니다.

유니티 기술 자료에서 이 메타데이터에 대해 자세히 알아보세요.

Q: 오픈 월드 설정에서 작업할 때, 자체 번들에 모든 에셋을 두어 오버헤드의 발생을 방지하면서 번들의 절반을 언로드한 후 Resources.UnloadUnusedAssets()로 클린업하지 않고 개별 에셋을 언로드하려면 어떤 번들 전략을 사용할 수 있을까요?
A: 무엇보다도, 콘텐츠가 동시에 언로드되기를 바란다면 해당 콘텐츠를 번들링해야 한다는 점을 기억하세요. 게임 월드에 플레이어가 옮길 일이 없는 특정 생물군의 나무나 바위처럼 정적인 콘텐츠가 있다면, 해당 콘텐츠를 함께 번들링해야 합니다. 플레이어가 집을 수 있는 아이템과 같이 동적인 콘텐츠는 별도로 번들링해야 합니다.

이 블로그 포스팅과 링크된 GitHub 리포지토리에서 오픈 월드 게임을 위한 번들 분할에 대해 알아볼 수 있습니다. 중복된 번들을 없애 각 번들의 메모리 오버헤드를 줄이는 방법도 소개합니다. 특히 4단계와 5단계에서 오픈 월드와 관련된 내용을 살펴볼 수 있습니다.

Q: 언제 'AssetBundle CRC'를 활성화해야 하나요? 
A: 원격 그룹의 경우 캐시된 AssetBundle을 제외하면 활성화하고, 로컬 그룹의 경우 비활성화하는 방법이 권장됩니다. 이 검사의 목적은 다운로드 시 데이터가 손상되지 않았음을 확인하는 것뿐이며, 로컬 AssetBundle을 검사할 이유는 거의 없습니다.

Q: 에셋을 로드하고 언로드할 때 CPU 성능 문제로 인해 어드레서블을 사용하지 않아도 되는 경우는 언제인가요?
A: 어드레서블 시스템은 사전에 모든 콘텐츠를 로드할 필요가 없으므로 CPU 로드 성능에 긍정적 영향을 미칩니다.

씬을 로드할 때 어드레서블을 사용하지 않는다면 모든 콘텐츠와 레퍼런스를 로드해야 합니다. 콘텐츠를 어드레서블로 옮기면 콘텐츠를 로드할 시점을 선택할 수 있습니다.

한 예로, 씬의 인벤토리 관리자에 1,000개의 인벤토리 아이템에 대한 레퍼런스가 있다고 가정하겠습니다. 어드레서블을 사용하지 않는다면 이 모든 인벤토리 항목에 대해 메시와 텍스처, 오디오 클립 등을 모두 로드해야 합니다. 이 콘텐츠가 모두 로드되고 나면 씬을 로드하는 속도가 더 빨라집니다.

Q: 어드레서블 에셋의 종속성도 모두 어드레서블이어야 하나요, 아니면 공유되는 경우에만 필요한가요?
A: 종속성을 어드레서블로 표시할 필요는 없습니다. 종속성은 빌드 프로세스 도중에 필요하다면 어드레서블에 추가됩니다.

예를 들어 플레이어 프리팹을 어드레서블로 만드는 경우, 플레이어의 메시나 텍스처 또는 오디오를 직접 어드레서블로 표시할 필요가 없습니다. 번들이 빌드되면 아직 어드레서블에 있지 않은 모든 종속성이 플레이어 프리팹 번들에 자동으로 포함됩니다.

Q: 실수로 어떤 에셋을 릴리스하지 않고 씬을 변경했다면 이 에셋은 어떻게 되나요?
A: 씬 변경은 근본적으로 핸들과의 상호 작용이 잘 되는 편입니다. 하지만 에셋을 로드하고 에셋의 핸들을 릴리스하지 않았다면 해당 에셋은 메모리에 유지됩니다.

어드레서블에는 내부 레퍼런스 카운팅 시스템이 있으며 핸들은 이 시스템과 상호 작용하는 방식을 나타냅니다. 에셋을 로드하면 레퍼런스 카운트가 증가하며, 릴리스하면 레퍼런스 카운트가 감소합니다.

크리에이터는 이 레퍼런스 카운트를 최신 상태로 유지해야 합니다. 레퍼런스 카운트가 1보다 크기만 하면 에셋은 메모리에 유지됩니다.

Q: 웨비나 예시와 관련해서, 제가 오픈 월드 게임을 제작하고 있다고 가정하겠습니다. 오픈 월드 어딘가에 보스가 있다고 하면 플레이어가 보스를 향해 가는 상황에서 어드레서블을 어떻게 사용하나요? 적으로부터 일정 거리 떨어진 지점에서 트리거를 통해 검 async를 로드하는 커맨드를 전송하나요, 아니면 다른 방법을 사용하나요? 
A: 콘텐츠를 로드하고 언로드하는 시점을 선택하기가 애매할 수 있습니다. 플레이어의 시야에 보스가 제대로 보이도록 하고 싶지만, 플레이어가 아직 몸을 돌려 보스를 피할 수 있는 상황에서 보스가 너무 일찍 로드되는 것은 바람직한 상황이 아닐 수 있습니다.

다행히도 콘텐츠를 로드하고 언로드하는 시점에 관해 반복 작업을 수행할 수 있으므로, 단번에 완벽하게 최적화할 필요가 없습니다.

먼저, 플레이어가 보스에게 근접하면 특정 '영역'의 모든 콘텐츠를 로드하는 방식이 바람직합니다(예: 플레이어가 던전 입구에 접근하면 던전 내부의 모든 요소를 로드). 이렇게 했을 때 메모리에 불필요한 부담이 발생한다면 더 세분화된 로딩과 언로딩 설정을 적용할 수 있습니다.

검이 충분히 빠르게 로드되지 않는 경우, 로드 트리거를 이동해 시작 시점을 앞당겨 보거나, Unity 프로파일러의 CPU 모듈로 무엇이 로드되는지 확인해 검 에셋의 로드 타임을 개선하거나, 어드레서블을 동기식으로 사용해 로드를 완료해 볼 수 있습니다.

이 기술 자료에서 자세한 내용과 동기식 어드레서블의 코드 스니핏에 대한 정보를 확인해 보세요.

Q: 씬이 시작될 때 어드레서블을 로드하는 경우 로딩 화면이 필요한가요?
A: 어드레서블에서의 로딩은 일반적으로 Addressables.LoadAssetAsync()를 사용할 때처럼 비동기식으로 수행됩니다.

로딩 화면을 벗어나기 전까지 로드할 필요가 없는 콘텐츠가 있을 수 있습니다. 이러한 AsyncOperationHandle을 수집하고 로딩 화면을 벗어나기 전에 필요한 항목이 모두 완료될 때까지 대기할 수 있습니다.

Q: 런타임 시 어드레서블 메타데이터의 메모리 사용량은 얼마나 되나요(해당 데이터를 로드하기 전)?
A: 어드레서블 초기화 진행 중에, 어드레서블이 디스크 또는 원격 위치에서 레이블과 주소를 에셋에 매핑하는 방법을 알 수 있도록 카탈로그 파일이 로드됩니다. 카탈로그 규모가 클수록 런타임 시 메모리 오버헤드도 커집니다.

레이블 또는 GUID를 필요 없는 그룹에 포함하지 않거나, 기존 데이터의 크기를 줄이는 등 불필요한 데이터를 스트리핑하는 등의 방법으로 카탈로그의 크기를 줄일 수 있습니다. 이를테면 길이가 더 길 수 있는 파일 이름이나 전체 경로 대신 그룹의 Internal Asset Naming Mode를 GUID로 설정하면 됩니다. Unity 메모리 프로파일러에서 카탈로그의 런타임 메모리 크기를 확인할 수 있습니다.

Q: 어드레서블을 빌드하느라 소모되는 시간 동안 Unity 에디터는 어떤 작업을 하나요?
A: 빌드 보고서 로그가 /Library 폴더에 출력됩니다. 이 로그에서 빌드 프로세스의 각 단계를 볼 수 있습니다. 추가 세부 사항을 로그에 추가하려면 Edit > Preferences > Scriptable Build Pipeline > Use Detailed Build Log에서 'Use Detailed Build Log' 경로를 선택합니다.

로그를 확인하는 방법에 대한 시각 자료와 기술 자료를 확인하세요.

Q: Resources.Load()에도 중복 문제가 있나요?
A: 네. 어드레서블 콘텐츠와 리소스 콘텐츠를 서로 다른 세상이라고 생각하면 더 쉽게 이해할 수 있습니다. /Resources에 텍스처가 있다면 이 텍스처의 사본 하나가 Resources 파일에 포함되어 있습니다. 어드레서블에 있는 번들이 해당 텍스처를 사용한다면 각 번들에 이 텍스처의 암묵적인 사본 하나가 포함되는 셈입니다. 따라서 디스크에 해당 텍스처의 사본이 여러 개 존재하게 되며 메모리에도 여러 개의 사본이 있을 수 있습니다.

이러한 중복을 방지하려면 /Resources에서 텍스처를 꺼내 어드레서블 그룹에 추가해야 합니다.

Q: 어드레서블을 사용하지 않을 때 중복된 번들을 제거하여 해결할 수 있는 디스크상의 유사한 크기 문제가 있나요?
A: 네. 웨비나와 슬라이드를 통해 수중 경주 씬 두 개를 복제해 빌드 크기를 대폭 줄인 사례를 보여 드렸습니다.

Q: 셰이더 배리언트 중복을 막으려면 어떻게 해야 하나요?
A: 다른 에셋과 동일한 프로세스(그룹에서 명시적으로 선언)로 셰이더를 복제할 수 있습니다.

빌드에 배치되는 어드레서블 그룹에서 에셋을 명시적으로 선언하면 여러 번들로 복제되지 않습니다.

특히 셰이더의 경우에는 프로젝트에서 '공유 셰이더' 그룹을 지정하여, 앱의 수명 연장을 위해 메모리에 필요하고 여러 에셋에서 공유되는 셰이더를 그룹에 포함하는 방식이 일반적입니다.

Q: 두 개의 Unity 씬이 동일한 프리팹 중복 빌드 크기를 공유하나요?
A: 씬이 종속된 프리팹이 어드레서블에 명시적으로 포함되었는지, 씬이 동일한 빌드 또는 다른 빌드에 있는지에 따라 다릅니다.

중복이 발생하는 과정에 대한 시각적 설명은 웨비나 슬라이드와 이 블로그 포스팅의 4단계에서 확인하세요.

번들에 포함되는 모든 콘텐츠는 번들의 종속성 전체에 액세스할 수 있어야 한다는 사실을 기억해 두세요. 씬 하나를 번들에 배치하는 경우 그 종속성 전체가 다음 중 하나에 해당해야 합니다.

  • 어드레서블 내 특정 위치에 명시적으로 포함되어 있음
  • 동일한 번들에 암묵적으로 포함되어 있음

Q: 모든 게임 에셋이 격리된 그룹으로 함께 패킹되지 않도록 특정 그룹에서 중복을 비교할 수 있나요?
A: 네. 빌트인 중복 제거 규칙을 실행한 다음 Addressables Groups 창의 에셋을 더 효과적으로 그룹화하여 정렬할 수 있습니다.

또는 보다 스케일링 가능한 방식으로 자체 Addressables AnalyzeRules를 작성할 수 있으며, 이 내용은 Analyze 창에 표시됩니다. 이러한 빌트인 규칙은 Addressables 패키지에서 C# 형태로 전달되며 하나의 기준과 같은 역할을 수행합니다.

이를테면 모든 그룹에서 'Character-'로 시작하는 모든 중복 항목을 찾아 볼 수 있습니다. 암묵적 중복이 있는 경우 'Shared-Character' 그룹에 배치할 수도 있습니다.

Q: 원격 빌드와 로컬 경로를 다룰 예정이신가요?
A: 원격 빌드와 로컬 경로는 다루지 않았는데, 웨비나에서는 이를 '어드레서블 프로파일'이라 했습니다. 어드레서블 프로파일의 정의와 사용 방법은 이 기술 자료에서 확인할 수 있습니다.

Q: 어드레서블과 CCD(Cloud Content Delivery)는 어떻게 연동되나요?
A: CCD 통합에 대한 내용은 이 기술 자료에서 볼 수 있습니다.

Q: 저해상도 및 고해상도 어드레서블 배리에이션을 구현하는 베스트 프랙티스에 관해 조언을 주실 수 있나요?
A: GitHub의 어드레서블 샘플에서 관련 예시를 확인할 수 있습니다.

Q: 번들 콘텐츠가 암호화되어 있는 경우 어떻게 하나요? UnityDataTool로 콘텐츠 암호화를 해제할 수도 있나요?
A: 아니요. UnityDataTool로 콘텐츠를 분석하려면 먼저 데이터 암호화를 해제해야 합니다.

Q: 하나의 Unity 프로젝트에서 번들을 빌드한 후 다른 프로젝트에서 빌드된 앱으로부터 런타임 시 번들을 로드하는 방식이 지원되는 사용 사례인가요?
A: 네. 동시에 여러 카탈로그를 사용하면 됩니다.

Q: InstantiateAsync를 사용할 때의 단점이 있나요? 아니면 LoadAsync와 수동 인스턴스화를 사용하면 더 나은 경우가 있나요?
A: Addressables.LoadAssetAsync()를 사용하고 Object.Instantiate()를 호출하는 방식이 좋습니다. Addressables.InstantiateAsync()는 많은 성능 비용이 소모됩니다.

Q: 스크립터블 오브젝트가 많이 있으며, 최소 1~2개의 스프라이트가 변수로 참조됩니다. 스프라이트를 어드레서블로 변경하려는 경우 어드레서블에 대한 레퍼런스를 일일이 변경해야 하나요? 아니면 다른 방법이 있나요?
A: 에디터 스크립트를 사용하여 이러한 레퍼런스를 전환할 수 있습니다.

AssetReference 필드를 ScriptableObject에 추가하고 Sprite 필드를 임시로 유지하면 됩니다. 그런 다음 ScriptableObject 전체에서 반복되는 에디터 스크립트를 작성하고 어드레서블에서 스프라이트 에셋을 검색하여 관련 AddressableAssetEntry를 찾은 다음, 주소를 저장하거나 AssetReference를 생성하여 ScriptableObject에 저장하면 됩니다.

끝으로, 다이렉트 스프라이트 레퍼런스 및 스왑 그리고 관련된 모든 코드를 제거하여 AssetReference를 사용할 수 있습니다.

Q: WebGL 게임에 어드레서블을 사용할 수 있나요? 그렇다면 특별히 알아 두어야 할 사항이 있나요?
A: 사용 가능하며, 두 가지 참고 사항이 있습니다. 우선, WebGL에서는 스레딩이 지원되지 않으므로 태스크를 사용하면 안 됩니다. 아울러 WebGL에서는 캐싱이 다르게 작동합니다. 앞서 원격 AssetBundle을 캐시할 때의 문제를 살펴본 바 있으니 참고해 주세요.

Q: Shader.Find('ShaderName")를 사용하는 경우 이는 빌드와 어드레서블 중 무엇을 기반으로 하나요?
A: 어드레서블이 아닌 Unity 플레이어의 빌드에 기반합니다. Shader.Find()는 AssetBundle의 결과를 반환하지 않습니다.

Q: 이름이 유사한 그룹이 많은 경우 Addressables Groups 창을 구성하려면 어떻게 해야 하나요?
A: Addressables Groups UI를 구성하려면 Group Hierarchy with Dashes를 활성화하면 됩니다. 그러면 이름이 유사한 그룹이 그룹화됩니다. 이를테면 'Character-person' 및 'Character-person2'는 'Character' 그룹화 아래의 UI에 표시됩니다.

이는 번들이 생성되는 방식에는 영향을 미치지 않으며 UI 구성이 변경된 것뿐입니다.

Addressables 포럼을 방문해 피드백을 공유해 주세요. 연재 중인 Tech from the Trenches 시리즈에서 다른 유니티 개발자들의 새로운 기술 블로그도 확인해 보실 수 있습니다.

2023년 4월 28일 엔진 & 플랫폼 | 17 분 소요

Is this article helpful for you?

Thank you for your feedback!

관련 게시물