애플리케이션의 성능을 최적화할 때 고려해야 할 한 가지 중요한 요소는 메모리 할당입니다. 이 문서는 Unity의 기본 메모리 할당자 타입에 대한 정보를 제공하고 할당자를 커스터마이즈하여 성능을 개선할 수 있는 시나리오를 설명합니다. 할당자에 대한 일반적인 이해가 있는 사용자를 대상으로 합니다.
할당자 타입과 그 기본값에 대한 전체 레퍼런스는 할당자 커스터마이징을 참조하십시오.
애플리케이션은 메모리 할당자를 사용하여 성능과 사용 가능한 메모리 공간의 균형을 유지합니다. 여유 메모리가 많은 애플리케이션은 씬과 프레임을 로드할 때 메모리 소모가 많더라도 더 빠른 할당자를 선호합니다. 그러나 애플리케이션에 제한된 메모리가 있는 경우 느린 할당자를 사용하더라도 해당 메모리를 효율적으로 사용해야 합니다. 다양한 프로젝트에서 최고의 성능을 얻을 수 있도록 Unity의 할당자를 각 애플리케이션의 크기와 요구 사항에 맞게 커스터마이즈할 수 있습니다.
Unity에는 다섯 가지 할당자 타입이 있습니다. 각 타입은 메모리 블록에 할당을 맞추기 위한 서로 다른 알고리즘을 가지고 있어서 다양한 할당에 유용합니다. 할당 간의 중요한 차이점은 일반적으로 지속성, 즉 할당을 어느 할당자로 보내야 하는지를 결정하는 할당 수명입니다. 예를 들어, 장기(영구) 할당은 힙 할당자와 버킷 할당자로 이동하는 반면 단기 할당은 스레드세이프 리니어 및 TLS 할당자로 이동합니다.
다음 표에는 각 할당자 타입의 알고리즘과 용도가 나와 있습니다.
할당자 타입 | 알고리즘 | 용도 |
---|---|---|
동적 힙(Dynamic heap) | 2단계 분리 맞춤(TLSF) | • 메인 할당자 • Gfx 할당자 • 타입트리 할당자 • 파일 캐시 할당자 • 프로파일러 할당자 • 에디터 프로파일러 할당자(에디터 전용) |
버킷(Bucket) | 고정된 크기의 무잠금 할당자 | 작은 할당을 위한 공유 할당자로 사용 • 메인 할당자 • Gfx 할당자 • 타입트리 할당자 • 파일 캐시 할당자 |
듀얼 스레드(Dual thread) | 크기와 스레드 ID를 기반으로 할당을 리디렉션합니다. | • 메인 할당자 • Gfx 할당자 • 타입트리 할당자 • 파일 캐시 할당자 |
스레드 로컬 스토리지(TLS) 스택 | LIFO 스택 | 임시 할당 |
스레드세이프 리니어(Threadsafe linear) | 라운드 로빈(Round robin) FIFO | 잡에 데이터를 전달하기 위한 버퍼 |
참고: 이 문서의 예제에서는 플레이어나 편집기를 닫을 때 로그에 기록되는 메모리 사용량 보고서를 사용합니다. 로그 파일을 찾으려면 로그 파일 페이지의 지침을 따르십시오.
이 섹션에서는 동적 힙, 버킷, 듀얼 스레드 할당자에 대한 기능과 커스터마이징 시나리오를 검토합니다.
듀얼 스레드 할당자는 동적 할당자와 버킷 할당자를 결합한 래퍼입니다. 보다 구체적으로 다음을 결합합니다.
두 개의 동적 힙 할당자는 메인 스레드에 대한 무잠금 할당자와 다른 모든 스레드가 공유하지만 할당과 할당 해제 시 잠기는 할당자입니다. Unity는 버킷 할당자에 비해 너무 큰 할당에 이 할당자를 사용합니다. 동적 힙 할당자는 메모리 블록을 사용합니다. 할당 크기가 블록의 절반 이상이면 동적 힙 할당자 대신 가상 메모리 시스템으로 이동합니다.
작은 할당을 위한 버킷 할당자. 버킷 할당자가 가득 차면 동적 힙 할당자로 할당이 넘어갑니다.
메인 힙 할당자는 동적 힙 할당자입니다. 2단계 분리 맞춤(TLSF) 알고리즘을 메모리 블록에 적용합니다.
각 플랫폼에는 커스터마이즈할 수 있는 기본 블록 크기가 있습니다. 할당은 블록의 절반보다 작아야 합니다. 할당 크기가 블록의 절반 이상인 경우 동적 힙 할당자에게 너무 크기 때문에 Unity는 대신 가상 메모리 API를 사용하여 할당합니다.
동적 힙 할당자에 대한 메모리 사용량 보고서의 예는 다음과 같습니다.
[ALLOC_DEFAULT_MAIN]
Peak usage frame count: [16.0 MB-32.0 MB]: 497 frames, [32.0 MB-64.0 MB]: 1 frames
Requested Block Size 16.0 MB
Peak Block count 2
Peak Allocated memory 54.2 MB
Peak Large allocation bytes 40.2 MB
이 예에서 TLSF 블록 크기는 16MB로 설정되고 Unity는 두 개의 블록을 할당했습니다. 할당자의 최대 사용량은 54.2MB였습니다. 52.4MB 중 40.2MB는 TLSF 블록에 할당되지 않고 가상 메모리로 대체되었습니다. 대부분의 프레임에는 16–32MB의 메모리가 할당된 반면 한 프레임(로딩 프레임으로 추정)은 최대 32–64MB의 메모리를 사용했습니다.
블록 크기를 늘리면 큰 할당이 가상 메모리로 대체되지 않고 동적 힙에 유지됩니다. 그러나 블록 크기가 크면 블록이 완전히 사용되지 않을 수 있으므로 메모리 낭비로 이어질 수 있습니다.
타입트리 할당자와 캐시 할당자를 사용하지 않으려면 해당 크기를 0으로 설정합니다. 타입트리와 캐시를 사용하려고 했던 할당은 대신 메인 할당자로 폴백됩니다. 이로 인해 더 많은 단편화가 발생할 수 있지만 해당 할당자 메모리 블록의 메모리를 절약할 수 있습니다.
버킷 할당자는 작은 할당을 수행하는 빠른 무잠금 할당자입니다. 일반적으로 버킷 할당자는 힙 할당자로 이동하기 전에 작은 할당의 속도를 높이는 첫 번째 단계로 사용됩니다.
할당자는 할당을 위해 메모리 블록을 예약합니다. 각 블록은 16KB의 하위 섹션으로 나뉩니다. 이것은 설정할 수 없으며 사용자 인터페이스에 표시되지 않습니다. 각 하위 섹션은 할당으로 나뉩니다. 할당 크기는 세분화(granularity)라고 하는 설정된 고정 크기의 배수입니다.
다음 예제 설정은 할당을 위해 블록을 예약하는 프로세스를 보여줍니다.
이 설정에서 총 블록 크기(Bucket Allocator Block Size)는 4MB이고 할당 세분화(Bucket Allocator Granularity)는 16B입니다. 첫 번째 할당은 16B, 두 번째 할당은 32B(2*16), 다음 48B, 64B, 80B, 96B, 112B, 128B로 총 8개(Bucket Allocator BucketCount)의 버킷입니다.
각 하위 섹션에는 서로 다른 수의 버킷이 포함되어 있습니다. 하위 섹션의 버킷 수를 계산하려면 하위 섹션 크기(16KB)를 세분화로 나눕니다. 예를 들면 다음과 같습니다.
버킷 할당자는 개발 빌드와 릴리스 빌드에 대해 서로 다른 사용량 보고서를 생성합니다. 개발 빌드에는 각 할당에 추가 40B의 헤더가 있기 때문입니다. 다음 다이어그램은 16B 할당과 64B 할당에 대한 개발 빌드와 릴리스 빌드의 차이점을 보여줍니다.
다음과 같이 할당자가 4MB 중 2MB만 할당한 후 가득 찼다고 보고하는 이유 또한 헤더때문입니다.
[ALLOC_BUCKET]
Large Block size 4.0 MB
Used Block count 1
Peak Allocated bytes 2.0 MB
Failed Allocations. Bucket layout:
16B: 64 Subsections = 18724 buckets. Failed count: 3889
32B: 17 Subsections = 3868 buckets. Failed count: 169583
48B: 31 Subsections = 5771 buckets. Failed count: 39674
64B: 28 Subsections = 4411 buckets. Failed count: 9981
80B: 17 Subsections = 2321 buckets. Failed count: 14299
96B: 6 Subsections = 722 buckets. Failed count: 9384
112B: 44 Subsections = 4742 buckets. Failed count: 5909
128B: 49 Subsections = 4778 buckets. Failed count: 8715
동일한 프로젝트의 릴리스 빌드에서는 할당자 블록 크기로 충분함을 보여줍니다.
[ALLOC_BUCKET]
Large Block size 4.0 MB
Used Block count 1
Peak Allocated bytes 3.3 MB
버킷 할당자가 가득 차면 할당이 다른 할당자로 폴백됩니다. 사용량 보고서는 실패한 할당 수를 포함하여 사용 통계를 표시합니다. 보고서에서 실패 수가 선형적으로 증가하면 로드가 아닌 프레임을 계산할 때 할당이 실패했을 가능성이 있습니다. 폴백 할당은 씬 로드에 문제가 되지 않지만 프레임을 계산할 때 발생하면 성능에 영향을 미칠 수 있습니다.
이러한 폴백 할당을 방지하려면 블록 크기를 늘립니다. 그리고 씬 로드 최대 사용량이 아닌 프레임의 최대 사용량과 일치하도록 새 블록 크기를 제한합니다. 이렇게 하면 블록이 너무 커져서 런타임 시 사용하지도 않을 많은 메모리 예약을 방지할 수 있습니다.
팁: 프로파일러 할당자는 버킷 할당자의 인스턴스를 공유합니다. 공유 프로파일러 버킷 할당자에서 이 공유 인스턴스를 커스터마이즈할 수 있습니다.
듀얼 스레드 할당자는 작은 할당을 위한 공유 버킷 할당자와 동적 힙 할당자의 두 가지 인스턴스(메인 스레드에 대한 무잠금 할당자와 다른 모든 스레드가 공유하지만 할당 및 해제 시 잠기는 할당자)를 래핑합니다.
다음과 같이 두 가지 동적 힙 할당자의 블록 크기를 커스터마이즈할 수 있습니다.
사용량 보고서는 할당자의 세 부분 모두에 대한 정보를 포함합니다. 예를 들면 다음과 같습니다.
[ALLOC_DEFAULT] Dual Thread Allocator
Peak main deferred allocation count 135
[ALLOC_BUCKET]
Large Block size 4.0 MB
Used Block count 1
Peak Allocated bytes 3.3 MB
[ALLOC_DEFAULT_MAIN]
Peak usage frame count: [16.0 MB-32.0 MB]: 8283 frames, [32.0 MB-64.0 MB]: 1 frames
Requested Block Size 16.0 MB
Peak Block count 2
Peak Allocated memory 53.3 MB
Peak Large allocation bytes 40.2 MB
[ALLOC_DEFAULT_THREAD]
Peak usage frame count: [64.0 MB-128.0 MB]: 8284 frames
Requested Block Size 16.0 MB
Peak Block count 2
Peak Allocated memory 78.3 MB
Peak Large allocation bytes 47.3 MB
참고: Peak main deferred allocation count는 삭제 대기열의 항목 수입니다. 메인 스레드는 모든 할당을 삭제해야 합니다. 다른 스레드에서 할당을 삭제하면 해당 할당은 대기열에 추가되고 메인 스레드가 삭제할 때까지 대기열에서 기다립니다. 그런 다음 지연된 할당으로 계산됩니다.
이 섹션에서는 스레드 로컬 스토리지(TLS) 할당자와 스레드세이프 리니어 할당자의 기능 및 커스터마이징 시나리오에 대해 설명합니다.
Unity에는 듀얼 스레드 할당자 외부에서 작동하는 두 가지 할당자가 있습니다.
스레드 로컬 스토리지(TLS): 빠른 임시 할당을 위한 스택 기반 할당자입니다. 오버헤드가 거의 없기 때문에 가장 빠른 할당자이며 또한 단편화를 방지합니다. 후입선출(LIFO) 기반입니다.
스레드세이프 리니어: 선입선출(FIFO) 라운드 로빈 할당자로, 임시 잡 할당이 워커 스레드 간에 단기 메모리를 전달하는 데 사용됩니다.
각 스레드는 임시 할당을 위해 고유한 고속 스택 할당자를 사용합니다. 이 할당은 프레임보다 수명이 짧고 매우 빠릅니다.
임시 할당자의 기본 블록 크기는 플랫폼의 경우 4MB이고 Unity 에디터의 경우 16MB입니다. 이 값은 커스터마이즈할 수 있습니다.
참고: 할당자가 설정된 블록 크기를 초과하여 사용하는 경우 Unity는 블록 크기를 증가시킵니다. 최대 블록 크기는 원래 크기의 두 배입니다.
스레드의 스택 할당자가 가득 차면 할당은 스레드세이프 리니어 잡 할당자로 폴백됩니다. 몇 개(프레임에서 1–10개 또는 로드 중에 수백 개)의 오버플로 할당은 가능하지만 모든 프레임에서 개수가 증가하면 블록 크기를 늘릴 수 있습니다.
사용량 보고서의 정보는 애플리케이션에 적합한 블록 크기를 선택하는 데 도움이 될 수 있습니다. 예를 들어 다음 메인 스레드 사용량 보고서에서 로드는 2.7MB에서 최대이지만 나머지 프레임은 64KB 미만입니다. 블록 크기를 4MB에서 64KB로 줄이고 로딩 프레임이 할당을 초과하도록 허용할 수 있습니다.
[ALLOC_TEMP_TLS] TLS Allocator
StackAllocators :
[ALLOC_TEMP_MAIN]
Peak usage frame count: [16.0 KB-32.0 KB]: 802 frames, [32.0 KB-64.0 KB]: 424 frames, [2.0 MB-4.0 MB]: 1 frames
Initial Block Size 4.0 MB
Current Block Size 4.0 MB
Peak Allocated Bytes 2.7 MB
Overflow Count 0
[ALLOC_TEMP_Job.Worker 18]
이 두 번째 예에서 워커 스레드는 대량의 임시 할당에 사용되지 않습니다. 메모리를 절약하기 위해 워커의 블록 크기를 32KB로 줄일 수 있습니다. 이것은 각 워커 스레드에 자체 스택이 있는 멀티 코어 시스템에서 특히 유용합니다.
[ALLOC_TEMP_Job.Worker 14]
Initial Block Size 256.0 KB
Current Block Size 256.0 KB
Peak Allocated Bytes 18.6 KB
Overflow Count 0
Unity의 워커 스레드는 잡에 대한 워커 버퍼를 잠금 없이 빠르게 할당하기 위해 라운드 로빈 선입선출(FIFO) 알고리즘을 사용합니다. 잡이 완료되면 버퍼를 삭제합니다.
이 할당자는 메모리 블록을 할당한 다음 해당 블록 내에서 선형으로 메모리를 할당합니다. 사용 가능한 블록은 풀에 보관됩니다. 한 블록이 가득 차면 할당자는 풀에서 새 블록을 페치합니다. 할당자가 블록의 메모리를 더 이상 필요로 하지 않으면 블록을 비우고 사용 가능한 블록 풀로 반환합니다. 블록을 다시 사용할 수 있도록 빠르게 할당을 지우는 것이 중요하므로 잡은 몇 프레임 이상 할당된 상태를 유지해서는 안 됩니다.
블록 크기를 커스터마이즈할 수 있습니다. 할당자는 필요에 따라 최대 64개의 블록을 할당합니다.
모든 블록이 사용 중이거나 할당이 블록에 비해 너무 큰 경우 해당 할당은 잡 할당자보다 훨씬 느린 메인 힙 할당자로 폴백됩니다. 몇 개(프레임에서 1–10개 또는 특히 로드 중에 수백 개)의 오버플로 할당은 가능하지만 모든 프레임에서 오버플로 수가 증가하면 블록 크기를 늘려 폴백 할당을 방지할 수 있습니다. 그러나 씬 로드와 같은 이벤트의 최대 사용량에 맞추기 위해 블록 크기를 너무 많이 늘리면 플레이 동안에 많은 메모리를 사용할 수 없게 될 수 있습니다.
예제:
[ALLOC_TEMP_JOB_4_FRAMES (JobTemp)]
Initial Block Size 0.5 MB
Used Block Count 64
Overflow Count (too large) 0
Overflow Count (full) 50408
이 예제 사용량 보고서에서 0.5MB의 블록 크기는 너무 작아서 애플리케이션에 필요한 잡 메모리를 수용할 수 없으며 전체 할당자로 인해 대부분의 할당이 오버플로되었습니다.
빌드의 프레임 오버플로가 충분한지 확인하려면 잠시 실행해 본 다음 더 오래 실행하십시오. 오버플로 카운트가 일정하게 유지되면 해당 오버플로는 로드 중에 발생하는 최고점입니다. 실행 시간이 길어질수록 오버플로 카운트가 증가하면 빌드는 프레임당 오버플로를 처리합니다. 두 경우 모두 블록 크기를 늘려 오버플로를 줄일 수 있지만, 로드 중에는 프레임당 오버플로가 오버플로보다 더 중요합니다.
할당자 설정을 커스터마이즈하려면 다음 중 하나를 수행하십시오.
할당자 파라미터 이름과 기본값:
할당자 | 설명 | 파라미터 이름 | 기본값 | ||
---|---|---|---|---|---|
Main Allocators | Unity에서 대부분의 할당에 사용하는 할당자입니다. | ||||
Main Allocator | Unity에서 대부분의 할당에 사용하는 기본 할당자입니다. | ||||
Main Thread Block Size | 전용 메인 스레드 할당자의 블록 크기입니다. | memorysetup-main-allocator-block-size |
16777216 |
||
Shared Thread Block Size | 공유 스레드 할당자의 블록 크기입니다. | memorysetup-thread-allocator-block-size |
16777216 |
||
Gfx Allocator | Unity에서 Gfx 시스템과 관련된 CPU 할당에 사용하는 할당자입니다. | ||||
Main Thread Block Size | 전용 메인 스레드 Gfx 할당자의 블록 크기입니다. | memorysetup-gfx-main-allocator-block-size |
16777216 |
||
Shared Thread Block Size | 공유 스레드 Gfx 할당자의 블록 크기입니다. | memorysetup-gfx-thread-allocator-block-size |
16777216 |
||
Other Allocators | |||||
File Cache Block Size | 파일 캐시에는 단편화를 방지하기 위한 자체 할당자가 있습니다. 그 할당자의 블록 크기입니다. | memorysetup-cache-allocator-block-size |
4194304 |
||
Type Tree Block Size | 타입 트리에는 많은 작은 할당으로 인한 단편화를 피하기 위해 자체 할당자가 있습니다. 그 할당자의 블록 크기입니다. | memorysetup-typetree-allocator-block-size |
2097152 |
||
Shared Bucket Allocator | 메인 할당자 간에 공유되는 버킷 할당자입니다. | ||||
Bucket Allocator Granularity | 공유 할당자에 있는 버킷의 스텝 크기입니다. | memorysetup-bucket-allocator-granularity |
16 |
||
Bucket Allocator BucketCount | 버킷 크기의 수입니다. | memorysetup-bucket-allocator-bucket-count |
8 |
||
Bucket Allocator Block Size | 버킷에 사용되는 메모리 블록의 크기입니다. | memorysetup-bucket-allocator-block-size |
Editor: 8388608 Player: 4194304
|
||
Bucket Allocator Block Count | 할당할 최대 블록 수입니다. | memorysetup-bucket-allocator-block-count |
Editor: 8 Player: 1
|
||
Fast Per Thread Temporary Allocators | 매우 짧은 할당을 처리하는 스레드 로컬 스토리지(TLS) 할당자입니다. | ||||
Main Thread Block Size | 메인 스레드 스택 크기의 초기값입니다. | memorysetup-temp-allocator-size-main |
Editor: 16777216 Player: 4194304
|
||
Job Worker Block Size | Unity 잡 시스템에 있는 각 잡 워커의 크기입니다. | memorysetup-temp-allocator-size-job-worker |
E262144 |
||
Background Job Worker Block Size | 각 백그라운드 워커의 크기입니다. | memorysetup-temp-allocator-size-background-worker |
32768 |
||
Preload Block Size | 사전 로드 관리자 스택 크기입니다. | memorysetup-temp-allocator-size-preload-manager |
Editor: 33554432 Player: 262144
|
||
Audio Worker Block Size | 각 오디오 워커 스레드의 스택 크기입니다. | memorysetup-temp-allocator-size-audio-worker |
65536 |
||
Cloud Worker Block Size | 클라우드 워커 스레드 스택 크기입니다. | memorysetup-temp-allocator-size-cloud-worker |
32768 |
||
Gfx Thread Blocksize | 메인 렌더 스레드 스택 크기입니다. | memorysetup-temp-allocator-size-gfx |
262144 |
||
GI 베이킹 블록 크기입니다. | 각 GI 워커 스레드의 스택 크기입니다. | memorysetup-temp-allocator-size-gi-baking-worker |
262144 |
||
NavMesh Worker Block Size | 내비 메시 워커 스레드 스택 크기입니다. | memorysetup-temp-allocator-size-nav-mesh-worker |
65536 |
||
Fast Thread Shared Temporary Allocators | 스레드 간에 공유되는 단기 할당을 위한 빠른 리니어 할당자입니다. | ||||
Job Allocator Block Size | Unity에서 주로 잡 워커 스레드에 사용하는 라운드 로빈 리니어 스레드 할당자입니다. | memorysetup-job-temp-allocator-block-size |
2097152 |
||
Background Job Allocator Block Size | 수명이 긴 할당을 허용하는 백그라운드 워커용 리니어 할당자입니다. | memorysetup-job-temp-allocator-block-size-background |
21048576 |
||
낮은 메모리 플랫폼의 잡 할당자 블록 크기입니다. | 메모리가 2GB 미만인 플랫폼은 잡 워커와 백그라운드 잡 모두에 사용하는 크기입니다. | memorysetup-job-temp-allocator-reduction-small-platforms |
262144 |
||
Profiler Allocators | Unity에서 애플리케이션의 할당 패턴을 방해하지 않도록 프로파일러 전용으로 사용하는 할당자입니다. | ||||
Profiler Block Size | 프로파일러의 주요 부분에 대한 블록 크기입니다. | memorysetup-profiler-allocator-block-size |
16777216 |
||
Editor Profiler Block Size | 프로파일러의 에디터 부분에 대한 블록 크기입니다. 이것은 플레이어에 존재하지 않습니다. | memorysetup-profiler-editor-allocator-block-size |
1048576 |
||
Shared Profiler Bucket Allocator | 프로파일러와 에디터 프로파일러 할당자를 위한 공유 버킷 할당자입니다. 메모리가 부족한 플랫폼에는 없습니다. |
||||
Bucket Allocator Granularity | 공유 할당자에 있는 버킷의 스텝 크기입니다. | memorysetup-profiler-bucket-allocator-granularity |
16 |
||
Bucket Allocator BucketCount | 버킷 크기 수입니다. 예를 들어 값이 4인 경우 크기는 16, 32, 48, 64입니다. | memorysetup-profiler-bucket-allocator-bucket-count |
8 |
||
Bucket Allocator Block Size | 버킷에 사용되는 메모리 블록의 크기입니다. | memorysetup-profiler-bucket-allocator-block-size |
Editor: 33554432 Player: 4194304
|
||
Bucket Allocator Block Count | 할당할 최대 블록 수입니다. | memorysetup-profiler-bucket-allocator-block-count |
Editor: 8 Player: 1
|
팁: 설정이 성능을 향상시키려면 변경 전후에 애플리케이션을 프로파일링하십시오. 자세한 내용은 프로파일러 개요 페이지를 참조하십시오. 메모리 사용량 보고서를 확인할 수도 있습니다. 플레이어 또는 에디터를 닫을 때 로그에서 볼 수 있습니다. 로그 파일을 찾으려면 로그 파일 페이지의 지침을 따르십시오.
Unity는 MemorySettings.asset
에 할당자 설정을 저장하고 빌드 시 수정된 설정으로 boot.config
파일을 채웁니다. 이는 모든 빌드마다 새 설정이 적용됨을 의미합니다.
에디터에서 boot.config
는 ProjectSettings
폴더에 있습니다. Unity에서 MemorySettings.asset
을 가져오거나 변경할 때마다 업데이트됩니다. 에디터의 새 값은 다음 에디터 시작 시에만 적용됩니다.