메모리
글 27개
Content Directories 는 아직 로컬 전용이다
Unity 6.6 의 Content Directories 는 Player 에 넣는 로컬 콘텐츠만 다룹니다. 원격 콘텐츠를 AssetBundle 로 남기면 공유 에셋이 양쪽에 따로 빌드되고, Addressables 4.x 에서 그룹을 바꿀 때는 빌드 스크립트에 따라 경고 없이 빈 빌드가 됩니다.
Content Directories 의 참조 카운트는 핸들만 센다
Content Directories 는 Loadable 핸들과 씬만 세어 마지막 쪽이 놓은 1프레임 뒤 에셋을 내립니다. Instantiate 한 인스턴스는 세지 않아서, 쓰는 중에 핸들을 놓으면 인스턴스의 머티리얼이 경고 없이 사라집니다.
Content Directories 는 등록할 때 실선까지만 올린다
Content Directories 는 디렉터리를 등록할 때 매니페스트를 읽고 Root 와 실선만 올립니다. 나머지는 Loadable.Load 가 매니페스트로 파일을 찾아 실선 순서대로 열고, 씬과 핸들로 각각 올린 같은 에셋은 하나의 객체로 공유됩니다.
Content Directories 의 파일 이름은 내용 해시다
Content Directories 는 에셋 하나를 내용 해시 이름의 파일 하나로 빌드합니다. 바뀌지 않은 파일은 다시 쓰지 않아 재빌드가 65초에서 9초로 줄었고, 대신 Player 빌드에 스트리핑 정보를 넘기지 않으면 콘텐츠 전용 컴포넌트가 에러 없이 빠집니다.
Content Directories 는 Root 에서 참조를 따라 빌드한다
Content Directories 빌드는 Root Asset 에서 GUID 참조를 따라가며 닿는 에셋을 모읍니다. 빌드는 Loadable 같은 점선 참조까지 다 따라가지만 런타임 로드는 점선에서 멈추므로, 수명이 갈리는 자리에 점선을 둡니다.
Content Directories 는 번들을 에셋 단위로 쪼갠다
AssetBundle 은 번들 하나가 배포, 로드, 해제 단위라 에셋 하나를 쓰려 해도 번들을 통째로 엽니다. Unity 6.6 의 Content Directories 는 이 일을 Root, 디렉터리, Loadable 셋으로 나눠 에셋 하나씩 올리고 내립니다.
Addressables 는 번들 단위로 참조를 센다
Addressables 는 에셋마다 참조를 세지만 실제로 내리는 단위는 번들이라, 같은 번들의 다른 에셋이 살아 있으면 Release 한 에셋도 남습니다. Object.Instantiate 인스턴스는 세지 않고, InstantiateAsync 를 Destroy 만 하면 씬이 내려갈 때까지 번들이 열려 있습니다.
Addressables 의 그룹 설정이 번들 모양을 정한다
Addressables 의 그룹은 에디터 개념이고 런타임 단위는 그룹 설정이 만든 번들입니다. 묶음 방식에 따라 같은 20개 에셋이 번들 1개, 20개, 4개가 되고, 그룹끼리 공유하는 에셋은 Analyze 로 찾아 따로 빼야 한 벌이 됩니다.
Addressables 는 AssetBundle 위에 주소를 얹는다
Addressables 는 에셋에 주소를 붙여 그 주소로 로드하게 하고, 어느 번들에 있는지와 의존 번들을 여는 일을 대신합니다. 다만 에디터 기본 모드는 그룹 설정을 보지 않아서, 에디터에서만 되는 로드가 생깁니다.
AssetBundle 의 Unload 는 어느 쪽이든 사고가 난다
AssetBundle.Unload(false) 뒤 같은 번들을 다시 열면 옛 객체와 새 객체가 메모리에 같이 있고, 옛 것은 UnloadUnusedAssets 로만 치워집니다. Unload(true) 를 쓰는 중에 부르면 인스턴스의 머티리얼이 경고 없이 사라집니다.
AssetBundle 은 배정하지 않은 공용 에셋을 번들마다 복사한다
두 번들이 같은 머티리얼을 쓰는데 그 머티리얼을 어느 번들에도 넣지 않으면, AssetBundle 은 번들마다 따로 복사하고 둘을 열면 메모리에도 두 벌이 올라옵니다. 공용 에셋을 공용 번들에 명시하면 한 벌로 줄어듭니다.
AssetBundle 의 의존성은 직접 열어야 따라온다
AssetBundle 은 프리팹의 머티리얼이 다른 번들에 있으면 그 번들을 직접 열기 전까지 머티리얼이 비어 마젠타로 그립니다. 나중에 열면 끊긴 참조가 이어지지만, AssetBundleManifest 로 의존 번들을 먼저 여는 순서를 지킵니다.
AssetBundle 은 압축 방식에 따라 여는 비용이 다르다
LZMA 번들을 LoadFromFile 로 열면 번들 전체를 풀어 메모리에 둡니다. LZ4 와 무압축은 1ms 안에 열리고, LoadFromMemory 는 바이트 배열과 사본을 같이 들며, UnityWebRequest 에 버전을 주면 디스크 캐시를 써서 메모리가 크게 줄어듭니다.
AssetBundle 은 에셋을 Player 밖 파일로 꺼낸다
유니티는 Scene List 에서 참조를 따라 에셋을 Player 에 싣고, Resources 폴더는 참조가 없어도 통째로 싣습니다. AssetBundle 은 에셋 일부를 Player 밖 아카이브 파일로 빼서 따로 받고, 따로 열고, 따로 내리게 합니다.
VitalRouter 의 할당 0 은 기다리지 않는 발행에서만이다
VitalRouter 는 zero-allocation 을 내세우고, 동기 발행은 정말 0 B 입니다. 그런데 인터셉터를 하나 달면 24 B, 구독자가 실제로 기다리면 유니티에서 발행 한 번에 1 KB 가까이 붙습니다. 어디까지 막고 어디서부터 못 막는지 봅니다.
번역도 공짜가 아니다
페이지 테이블은 메모리에 있습니다. 그러니 표를 보는 것도 메모리 접근입니다. CPU 가 TLB 를 두고 표를 네 단계로 쪼갠 이유와, 그 값이 게임에서 어디에 나타나는지 봅니다.
램이 느려서 캐시가 생겼다
CPU 는 한 사이클에 일하는데 램은 수백 사이클 걸립니다. 그 사이를 메우려고 생긴 것이 캐시이고, 캐시가 기대는 것이 지역성입니다. 계층이 몇 단이고 각 단이 얼마인지부터 봅니다.
페이지 폴트는 오류가 아니지만 프레임은 흔든다
가상 주소가 물리 주소로 번역되는 과정과, 그 번역이 빗나갔을 때 OS 가 하는 일. 그 멈춤이 16.6밀리초짜리 프레임 예산에서 얼마나 큰지까지 봅니다.
가상 메모리는 램이 모자라서 나왔지만 남은 이유는 따로 있다
오버레이를 손으로 짜던 시절부터 1962년 아틀라스까지. 메모리가 모자라서 만든 장치가 왜 램이 남아도는 지금까지 모든 OS 의 바닥에 남아 있는지 봅니다.
엔티티는 번호표이고 데이터는 청크에 줄지어 있다
ECS 의 세 조각이 각각 무엇인지, 아키타입과 청크가 왜 필요한지, 쿼리가 엔티티를 하나씩 뒤지지 않는 이유까지.
1만 개를 한 번 도는 것과 1개를 1만 번 부르는 것
적 1만 마리를 움직이는 코드가 느린 이유는 계산이 무거워서가 아니라 데이터가 흩어져 있어서입니다. ECS 는 그걸 붙여 놓는 것에서 시작합니다.
관리 힙은 30MB 인데 게임은 2GB 를 먹는다
텍스처와 메시는 관리 힙에 거의 없습니다. GC 를 아무리 줄여도 여기는 안 줄어듭니다.
박싱은 값을 힙으로 보내는 일이다
object 자리에 int 를 넣으면 값이 상자에 담겨 힙으로 갑니다. 그 상자값이 얼마인지 봅니다.
struct 는 값을 담고 class 는 주소를 담는다
int 가 힙에 안 가는 이유, 그리고 힙에 안 가는 타입을 직접 만드는 법.
대입하면 값이 복사되거나 주소가 복사된다
규칙은 하나입니다. 담긴 것이 복사됩니다. 담긴 것이 값이냐 주소냐에 따라 결과가 완전히 달라집니다.
new 는 힙에 자리를 만들고 주소를 돌려준다
변수 안에 객체가 들어 있는 것이 아닙니다. 객체가 있는 곳의 주소가 들어 있습니다.
변수를 선언하면 무슨 일이 일어나는가
변수는 값을 담는 상자가 아니라 이름이 붙은 메모리 칸입니다. 선언·할당·사용이 각각 언제 무엇을 하는지 그림으로 봅니다.