Unity 웹의 메모리 제약으로 인해 실행하는 콘텐츠의 복잡도가 제한될 수 있습니다.
웹 콘텐츠는 브라우저에서 실행됩니다. 브라우저는 애플리케이션에서 콘텐츠를 실행하는 데 필요한 메모리를 메모리 공간에 할당합니다. 사용 가능한 메모리의 양은 다음에 따라 다릅니다.
참고: 웹 메모리와 관련된 보안 위험에 대한 자세한 내용은 보안 및 메모리 리소스를 참조하십시오.
다음 Unity 웹 콘텐츠 영역은 브라우저에 상당한 양의 메모리를 할당해야 합니다.
Unity는 메모리 힙을 사용하여 모든 Unity 엔진 런타임 오브젝트를 저장합니다. 여기에는 관리되는 오브젝트와 네이티브 오브젝트, 로드된 에셋, 씬, 셰이더가 포함됩니다. 이는 Unity 플레이어가 다른 플랫폼에서 사용하는 메모리와 유사합니다.
Unity 힙은 할당된 메모리의 인접한 블록입니다. Unity는 애플리케이션의 요구 사항을 충족할 수 있도록 힙의 자동 크기 조정을 지원합니다. 힙 크기는 애플리케이션이 실행될 때 확장되며 최대 2GB까지 확장할 수 있습니다. Unity는 이 메모리 힙을 메모리 오브젝트로 만듭니다. 메모리 오브젝트의 버퍼 프로퍼티는 WebAssembly 코드에서 액세스하는 메모리의 원시 바이트를 보유하는 크기 조정 가능한 ArrayBuffer입니다.
브라우저가 주소 공간에서 인접한 메모리 블록을 할당하지 못하는 경우 힙의 크기를 자동으로 조절하면 애플리케이션에 크래시가 발생할 수 있습니다. 이러한 이유로 Unity 힙 크기를 최대한 작게 유지하는 것이 중요합니다. 따라서 애플리케이션의 메모리 사용량을 계획할 때 주의해야 합니다. Unity 힙의 크기를 테스트하려면 프로파일러를 사용하여 메모리 블록의 콘텐츠를 프로파일링합니다.
Web Player Settings의 Memory Growth Mode 옵션을 사용하여 힙의 초기 크기와 증가를 제어할 수 있습니다. 기본 옵션은 모든 데스크톱 사용 사례에 적합하도록 설정되어 있습니다. 하지만 모바일 브라우저에서는 고급 튜닝 옵션을 사용해야 합니다. 모바일 브라우저의 경우 애플리케이션의 일반적인 힙 사용에 따라 초기 메모리 크기를 설정하는 것이 좋습니다.
Unity 웹 빌드를 생성하면 Unity가 .data 파일을 생성합니다. 여기에는 애플리케이션을 실행하는 데 필요한 모든 씬과 에셋이 포함되어 있습니다. Unity 웹은 실제 파일 시스템에 액세스할 수 없으므로 가상 메모리 파일 시스템을 생성하고 브라우저가 여기에서 .data 파일을 압축 해제합니다. Emscripten 프레임워크(JavaScript)는 이 메모리 파일 시스템을 브라우저 메모리 공간에 할당합니다. 콘텐츠가 실행되는 동안 브라우저 메모리는 압축되지 않은 데이터를 저장합니다. 다운로드 시간과 메모리 사용량을 모두 적게 유지하려면 압축되지 않은 데이터를 최대한 작게 유지하십시오.
메모리 사용을 줄이기 위해 에셋 데이터를 에셋 번들로 패킹할 수 있습니다. 에셋 번들은 에셋 다운로드를 완벽하게 제어합니다. 애플리케이션이 에셋을 다운로드할 시기와 런타임이 언로드할 시기를 제어할 수 있습니다. 사용되지 않는 에셋을 언로드하면 메모리를 확보할 수 있습니다.
AssetBundles는 Unity 힙에 바로 다운로드되므로 브라우저에서 추가 할당이 발생하지 않습니다.
사용자의 컴퓨터에서 콘텐츠에 있는 에셋 데이터를 자동으로 캐싱하려면 Data Caching을 활성화합니다. 그러면 이후 실행 시 해당 데이터를 다시 다운로드할 필요가 없습니다. Unity 웹 로더는 IndexedDB API를 사용하여 데이터 캐싱을 구현합니다. 이 옵션을 사용하면 브라우저가 기본적으로 캐싱하기에 너무 큰 파일을 캐싱할 수 있습니다.
데이터 캐싱을 사용하면 브라우저가 사용자의 컴퓨터에 애플리케이션 데이터를 저장할 수 있습니다. 브라우저는 캐시에 저장할 수 있는 양과 캐시할 수 있는 최대 파일 크기를 제한하는 경우가 많습니다. 이렇게 되면 애플리케이션이 원활하게 실행되는 데 충분하지 않은 경우가 많습니다. Unity 웹 로더 캐싱은 Unity가 브라우저 캐시 대신 IndexedDB에 데이터를 저장할 수 있도록 하는 IndexedDB API를 사용합니다.
Data Caching 옵션을 활성화하려면 File > Build Settings > Player Settings > Publishing Settings로 이동하십시오.
가비지 컬렉션은 사용되지 않는 메모리를 찾고 확보하는 프로세스입니다. Unity 가비지 컬렉션의 작동 방식에 대한 개요는 자동 메모리 관리를 참조하십시오. 가비지 컬렉션 프로세스를 디버깅하려면 Unity 프로파일러를 사용합니다.
WebAssembly의 보안 제한으로 인해 사용자 프로그램은 가능한 악용을 방지하기 위해 네이티브 실행 스택을 검사할 수 없습니다.
즉, 웹 플랫폼에서는 관리되는 코드가 실행되고 있지 않을 때만 GC를 실행할 수 있습니다(잠재적으로 라이브 C# 오브젝트를 참조할 수 있음). 이는 모든 렌더링된 게임 프레임이 끝날 때 발생합니다.
즉, 웹 플랫폼에서는 C# 코드 실행 중간에 가비지 컬렉터를 실행할 수 없으며, 각 프로그램 프레임이 끝날 때만 실행할 수 있습니다. 이러한 불일치로 인해 웹에서 가비지 컬렉션 동작은 다른 플랫폼과 비교하여 몇 가지 차이가 발생합니다.
이러한 차이로 인해 프레임당 많은 임시 할당을 수행하는 코드에 주의해야 하며, 특히 이러한 할당이 선형 크기 증가의 순서를 나타낼 수 있는 경우에 주의해야 합니다. 이러한 할당으로 인해 가비지 컬렉터에 일시적으로 급격한 메모리 증가 부담이 발생할 수 있습니다.
예를 들어 오래 실행되는 루프가 있는 경우 가비지 컬렉터는 루프의 반복 간에 실행되지 않기 때문에 웹에서 실행할 때 다음 코드가 실패할 수 있습니다. 이 경우 가비지 컬렉터는 중간 문자열 오브젝트가 사용하는 메모리를 확보할 수 없으며 Unity 힙의 메모리가 부족해집니다.
string hugeString = "";
for (int i = 0; i < 100000; i++)
{
hugeString += "foo";
}
위 예시에서 루프 끝의 hugeString 길이는 3 * 100000 = 300000자입니다. 하지만 코드는 최종 문자열을 생성하기 전에 수십만 개의 임시 문자열을 생성합니다. 루프 전체에 걸쳐 할당된 총 메모리는 3 * (1 + 2 + 3 + … + 100000) = 3 * (100000 * 100001 / 2) = 15기가바이트입니다.
네이티브 플랫폼에서는 루프가 실행되는 동안 가비지 컬렉터가 이전의 임시 복사본을 지속적으로 지웁니다. 따라서 위의 코드를 실행하려면 총 15GB의 RAM이 필요하지 않습니다.
웹 플랫폼에서 가비지 컬렉터는 프레임이 끝날 때까지 임시 문자열 복사본을 다시 가져오지 않습니다. 그 결과 위의 코드는 15GB의 RAM을 할당하려고 시도하다가 메모리가 부족해집니다.
다음은 이러한 유형의 일시적으로 급격한 메모리 부담이 발생할 수 있는 두 번째 예시를 보여 주는 코드입니다.
byte[] data;
for (int i = 0; i < 100000; i++)
{
data = new byte[i];
// do something temporary with data[]
}
여기서 이 코드는 마지막 100KB 배열만 보존되더라도 1 + 2 + 3 + … + 100000바이트 = 5GB의 바이트를 일시적으로 할당합니다. 이렇게 하면 최종 출력에 100KB만 필요하더라도 웹 플랫폼에서 프로그램에 메모리가 부족한 것처럼 보일 수 있습니다.
이러한 유형의 문제를 방지하려면 임시 메모리 할당의 양이 제곱씩 증가하는 코드 구성은 피해야 합니다. 대신 최종적으로 필요한 데이터 크기를 미리 할당하거나, 일시적인 메모리 부담을 완화하는 용량 예약을 수행하는 List<T> 또는 유사한 데이터 구조를 사용합니다.
예를 들어, List<T> 컨테이너의 경우 데이터 구조의 최종 크기를 알고 있다면 필요한 용량을 미리 할당할 수 있는 List<T>.ReserveCapacity() 함수를 사용하는 것이 좋습니다. 마찬가지로 이전에 메모리가 몇 메가바이트인 컨테이너의 크기를 줄일 때 List<T>.TrimExcess() 함수를 사용하는 것이 좋습니다.
참고: Delegate, Action, Func 같은 C# 델리게이트 또는 이벤트를 사용할 때 이러한 클래스는 내부적으로 위와 유사한 선형 증가 할당을 활용하십시오. 웹 플랫폼의 가비지 컬렉터에 대한 일시적인 메모리 부담을 최소화하기 위해 이러한 클래스를 사용하여 과도한 양의 프레임당 델리게이트 등록 및 등록 취소를 피하십시오.