이전 글에서 Addressables에 대한 내용을 다루었었는데, 메모리 관리와 관련해서 추가로 찾은 내용이 있어서 올리고자 한다.
Addressables은 자체적으로 Reference Count를 관리하여 AssetBundle 내의 모든 Asset이 Release되면 AssetBundle을 통째로 자동으로 메모리에서 해제해준다는 내용을 찾아볼 수 있었다.
또, 마찬가지로 AssetBundle끼리도 Reference Count를 가지고 있는 것으로 보였다.
하지만, 각 Asset에 대한 Reference Count는 어떻게 관리를 하는지에 대해서는 찾아보기가 조금 어려웠다.
만약 각각 Asset을 직접 해제해줘야 한다면 작업이 상당히 번거로워질 것이기 때문에, 이점이 크지 않을 것 같았다.
InstantiateAsync와 Release에 대한 개념은 인지하고 있었기 때문에 과연 이 기능이 Asset 내부에서도 Reference Count를 관리해주며 자동으로 메모리를 해제해주는지 알아보았다.
먼저 테스트용 프로젝트를 만들었다. 프로젝트 초기 단계에서는 Addressables를 사용하기는 생각보다 간단했다. Package Manager에 추가하고, Addressables를 체크한 다음, 그룹 설정 등을 해주고 기본 Build를 해주면 로컬 메모리에 잘 생성되고, 잘 로드되는 등 생각보다 사용하기가 어렵지는 않았다. (로컬 기준)
유니티 Addressable #2 - 스프라이트 (tistory.com)
유니티 Addressable #2 - 스프라이트
가. 준비물 가져오기 1. Sprite 2장 가져오기 이번에 사용할 이미지를 두장을 유니티로 가져옵니다. 이미지를 Sprite로 만들고 Inspector창에서 Addressable을 활성화시킨 다음 옆의 빈칸에 원하는 이름을
mrbinggrae.tistory.com
사용 방법은 아직 잘 몰라서 위 블로그의 코드를 참조해서 테스트를 해보았다.
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.UI;
public class TestScript : MonoBehaviour
{
public GameObject parent1;
public GameObject parent2;
private GameObject _image1;
private GameObject _image2;
public void LoadGameObject()
{
Addressables.InstantiateAsync("Assets/TestResources/Image.prefab", parent1.transform).Completed += GameObject1Loaded;
}
public void LoadGameObject2()
{
Addressables.InstantiateAsync("Assets/TestResources/Image2.prefab", parent2.transform).Completed += GameObject2Loaded;
}
public void LoadSprite()
{
Addressables.LoadAssetAsync<Sprite>("Assets/TestResources/TestSprite.png").Completed += SpriteLoaded;
}
public void ReleaseImage1()
{
Addressables.ReleaseInstance(_image1);
}
public void ReleaseImage2()
{
Addressables.ReleaseInstance(_image2);
}
private void SpriteLoaded(AsyncOperationHandle<Sprite> obj)
{
switch (obj.Status)
{
case AsyncOperationStatus.Succeeded:
_image1.GetComponent<Image>().sprite = obj.Result;
_image2.GetComponent<Image>().sprite = obj.Result;
Debug.Log("Sprite 로드 성공");
break;
case AsyncOperationStatus.Failed:
Debug.Log("Sprite 로드 실패");
break;
default:
break;
}
}
private void GameObject1Loaded(AsyncOperationHandle<GameObject> obj)
{
switch (obj.Status)
{
case AsyncOperationStatus.Succeeded:
_image1 = obj.Result;
Debug.Log("GameObject 로드 성공");
break;
case AsyncOperationStatus.Failed:
Debug.Log("GameObject 로드 실패");
break;
default:
break;
}
}
private void GameObject2Loaded(AsyncOperationHandle<GameObject> obj)
{
switch (obj.Status)
{
case AsyncOperationStatus.Succeeded:
_image2 = obj.Result;
Debug.Log("GameObject 로드 성공");
break;
case AsyncOperationStatus.Failed:
Debug.Log("GameObject 로드 실패");
break;
default:
break;
}
}
}
[1번 테스트]
AssetBundle1: Image가 포함된 GameObject Prefab을 보유 (Image의 Sprite는 비어있음)
AssetBundle2: Sprite 1개 보유(TestSprite)
AssetBundle1의 Prefab을 InstantiateAsync로 불러오고, Sprite를 LoadAssetAsync로 불러옴. LoadAssetAsync로 불러올 때 AssetBundle1에서 불러온 Prefab에서 “GetComponent<Image>().sprite = TestSprite 를 하여 직접 참조를 시키고, AssetBundle1의 Prefab을 ReleaseInstance로 Release 해줌.
결과: Prefab은 해제됐지만, Sprite는 메모리에 남아있음.
지금 생각해보니 당연한 것 같은데, Sprite는 따로 로드한 것이고 Addressables을 통해서가 아니라 직접 연결해주었기 떄문에 Addressables에서 참조 카운터가 간섭할 여지가 없음. 사라지지 않는 것이 맞는 것 같다.
[2번 테스트]
AssetBundle1: Image가 포함되어있고, Image의 Sprite는 AssetBundle2의 TestSprite를 참조하는 GameObject Prefab이 있음.
AssetBundle2: Sprite 1개 보유(TestSprite)
AssetBundle1의 Prefab을 InstantiateAsync로 불러오고, AssetBundle1의 Prefab을 ReleaseInstance로 Release 해줌.
결과: AssetBundle1의 Prefab을 로드할 때 AssetBundle2의 TestSprite도 같이 로드되었고, AssetBundle1의 Prefab을 ReleaseInstance로 Release할 때 AssetBundle2의 TestSprite도 같이 Release되었다. (메모리에서 삭제됨)
[3번 테스트]
AssetBundle1: Image가 포함되어있고, Image의 Sprite는 AssetBundle2의 TestSprite를 참조하는 GameObject Prefab이 있음.
AssetBundle2: Image가 포함되어있고, Image의 Sprite는 AssetBundle2의 TestSprite를 참조하는 GameObject Prefab이 있음.
AssetBundle3: Sprite 1개 보유(TestSprite)
AssetBundle1의 Prefab을 InstantiateAsync로 불러오고, AssetBundle2의 Prefab을 InstantiateAsync로 불러옴. 이후 AssetBundle1의 Prefab을 ReleaseInstance로 Release 해주고, AssetBundle2의 Prefab을 ReleaseInstance로 Release 해줌.
결과: AssetBundle1의 Prefab을 로드할 때 AssetBundle3의 TestSprite도 같이 로드되었음.(메모리에 적재) 이후 AssetBundle2의 Prefab을 로드하였을 때 AssetBundle3의 TestSprite는 1개로 유지되었음.
그 다음, AssetBundle1의 Prefab을 ReleaseInstance로 Release하였지만 TestSprite는 남아있었음. AssetBundle2의 Prefab을 Release하자 TestSprite도 같이 Release되었음. (메모리에서 삭제됨)
+메모리 적재/삭제 여부는 Event Viewer와 메모리 프로파일러로 함께 확인하였음.
Prefab을 InstantiateAsync로 생성하고, ReleaseInstance로 해제해준다면, 종속성에 의해 같이 로드된, 사용하지 않는 Asset들은 자동으로 Release된다.
즉, 내부에 Asset에 대한 Reference Count도 있어서 메모리를 자동으로 관리해준다는 말이다. 이 Reference Count만 잘 작동하도록 사용한다면 Asset에 대한 관리도 간편하게 처리할 수 있다.
이것 하나만으로도 Addressables를 사용할 이유는 충분하다고 생각한다.
이외에도 Addressables를 사용하면서 도움이 될만한 내용들을 찾아서 정리해봤다.
1. Addressables의 버전 관리
https://docs.unity3d.com/Packages/com.unity.addressables@1.21/manual/ContentUpdateWorkflow.html
Content update builds | Addressables | 1.21.15
Content update builds When you need to update your application, you can create a content update build to only distribute the updated content of your application.
docs.unity3d.com
Addressables로 번들을 빌드할 때 생성되는 addressables_content_state.bin 파일을 이용하면 버전 관리를 수월하게 할 수 있는 것 같다. 해당 파일을 사용하여 빌드하면 Catalog를 새로 생성하는 것이 아니라 기존의 Catalog에서 수정된 번들에 대해서만 변경을 해줘서 변경이 있는 AssetBundle만 다운로드 받도록 할 수 있는 것으로 보인다. 버전을 관리할 때는 각 버전에 대한 빌드 내용과 빌드 당시의 Catalog 파일, addressables_content_state.bin을 같이 백업하면 될 것 같음.
2. 시작할 때 원하는 Asset만 다운로드
https://docs.unity3d.com/Packages/com.unity.addressables@1.21/manual/DownloadDependenciesAsync.html
Preload dependencies | Addressables | 1.21.15
Preload dependencies When you distribute content remotely, you can improve performance by downloading dependencies in advance of when your application needs them. For example, you can download essential content on start up when your game is launched for th
docs.unity3d.com
각 Asset에는 label을 여러 개 달 수 있는데, 이 label을 key로 하여 Addressables.DownloadDependenciesAsync를 호출하면 설정한 것만 미리 받을 수 있는 것 같음.
3. 다운로드 Override 기능
https://docs.unity3d.com/Packages/com.unity.addressables@1.21/manual/TransformInternalId.html
Change resource URLs | Addressables | 1.21.15
Change resource URLs You can modify the URLs that Addressables uses to load assets at runtime in the following ways: Static Profile variables When you define the RemoteLoadPath Profile variable you can use a static property to specify all or part of the UR
docs.unity3d.com
다운로드할 때 다운로드 URL을 변경할 수 있는 기능을 제공하는 것 같음.
위 내용들을 통해서 우리의 요구사항은 충족할 수 있는 것으로 보이지만... Addressables로 교체하면서 소요될 시간, 발생할 문제에 대처할 시간과 우리에게 남은 시간, 해야할 일 등을 고려해봤을 때 지금 도입하기에는 조금 어려움이 있을 것 같아서 (가능하다면) 다음 프로젝트에서 적용하기로 하였다.
조금 아쉽긴 하지만, 지금도 Shader Varint Stripping을 통해서 상당한 용량의 메모리를 확보했기 때문에 이전에 비해서는 부담이 많이 줄어들어서 도입이 시급하지는 않을 것 같다.
현재는 Shader Variant Stripping을 좀 더 효율적으로 하기 위해서 Shader Variant를 인위적으로 Compile되도록 하여 사용하는 Shader Variant를 수집하는 작업을 하고 있다. 아마 이 작업이 잘 된다면 Shader Variant Stripping에 대한 내용도 포스트 할 것 같다.
'개발 > 공부' 카테고리의 다른 글
[Unity] Time.realtimeSinceStartup 은 정확도가 떨어진다. (1) | 2024.05.09 |
---|---|
JetBrains Rider의 장점을 활용해서 효율적인 개발하기 (0) | 2023.09.16 |
메모리 관리에 대한 탐구 (8) - AssetBundle, Scriptable Build Pipeline, Addressables (0) | 2023.08.15 |
메모리 관리에 대한 탐구 (7) - 에셋의 중복과 Scriptable Build Pipeline (1) | 2023.08.12 |
메모리 관리에 대한 탐구 (6) - 에셋 번들의 이해 (0) | 2023.08.11 |