이 글에서는 Wwise 2024.1의 새로운 시스템 중 하나인 AkMemoryArena라는 Wwise의 새로운 기본 메모리 에 대해 소개하고 자세히 살펴보려고 합니다. 이전 버전의 Wwise와 달리 이 새로운 메모리 는 Wwise의 메모리 리소스 활용을 크게 향상시킬 수 있지만, 메모리 시스템을 적절히 연구하고 구성하면 많은 프로젝트를 훨씬 더 큰 수준으로 최적화할 수 있습니다.
메모리 예약 및 파편화 검토
Wwise 2019.2에서는 새로운 기본 메모리 지원이 포함되었습니다. 이 할당자의 주요 기능은 다음과 같습니다.
- 내부 메모리 파편화를 완화하기 위해 모든 크기의 클래스에서 블록 기반 할당
- 개별 스레드가 메모리를 할당 및 해제할 때 경합이 거의 또는 전혀 발생하지 않도록 해주는 스레드 캐시 작동 방식
- 필요에 따라 메모리 리소스를 확장하여 사전 크기의 메모리 풀을 보유할 필요가 없음
이 새로운 메모리 할당자는 성능이 뛰어나며 필요에 따라 메모리를 매핑하는 기능으로 Wwise의 유연성을 상당히 향상시켜 줍니다.
일부 개선 사항이 있었지만 더 많은 개발자들이 사용하면서 전반적인 메모리 활용에 관한 몇 가지 결함이 드러났습니다. 세 가지 주요 문제가 발견되었죠.
- 블록 기반 할당 체계는 필요한 것보다 각 할당이 더 큰 경향이 있으며, 평균적으로 많은 작품에서 사용되는 총 'Memory Used(사용된 메모리)'가 약 10-15% 증가했습니다.
- 스레드 캐시 작동 방식으로 인해 더 많은 스레드에서 메모리 할당이 발생하기 때문에 'Memory Reserved(예약된 메모리)'가 증가합니다. 심각한 경우 독특한 힙(heap)이 발생하는 애플리케이션에서 관리하는 수십 개의 스레드가 거의 사용되지 않고 해제되는 경우도 있었습니다.
- 할당자는 한 번에 큰 범위의 새 메모리 페이지를 요청하지만, 또한 이러한 범위의 하위 집합이 매핑 해제되어 해당 부분이 사용되지 않은 채로 남아 모두 해제될 수 있을 것으로 예상합니다.
이러한 문제 중 일부는 다른 스레드 캐시 메모리 할당자와 공유되지만 일부 문제는 특히 Wwise의 일부 작동 조건, 즉 Wwise 사운드 엔진이 미들웨어 라이브러리라는 사실로 인해 발생했습니다. Wwise가 미들웨어라는 것은 소프트웨어 스택의 중간에 있음을 의미하기 때문에 플랫폼의 가장 낮은 수준 측면과 상호작용할 수 없습니다. 여기에는 실행에 사용되는 모든 스레드를 직접 제어하거나 메모리 매핑/매핑 해제를 위해 가상 메모리 시스템과 직접 상호 작용하는 등이 포함됩니다. 예를 들어 대부분의 게임 엔진과 Wwise의 통합은 메모리의 부분 매핑 해제라는 개념을 지원할 수 없기 때문에 많은 메모리가 예약되어 있지만 사용되지 않는 경우가 많습니다.
수년에 걸쳐 예약된 메모리 양을 제어하기 위해 다양한 조치가 도입되었지만 이로 인해 할당자를 사용하는 데 드는 CPU 비용을 증가시키는 경향이 있어 처음에 인식된 많은 이점들이 상쇄되어 버렸습니다. 그러한 노력에도 불구하고 Wwise 사운드 엔진의 리소스를 최대한 활용하기 위해 합리적으로 해결할 수 없는 메모리 할당자와 관련된 몇 가지 핵심 설계를 결정해야 했습니다.
계속하기 전에, 대용량의 메모리를 사용할 수 있는 데스크톱이나 서버 애플리케이션과 같이 스레드 캐시 할당자가 탁월한 결과를 제공할 수 있는 사례가 확실히 많다는 점에 주목할 필요가 있습니다. 예를 들어 LLVM 프로젝트는 최근 Windows의 툴체인에 대한 기본 메모리 할당자를 rpmalloc으로 이동하고 처리에서 성능이 상당히 향상된 것을 볼 수 있었습니다. 다른 애플리케이션에서 보편적으로 공유하지 않는 저희 소프트웨어의 고유한 요구 사항이 많기 때문에 이 전략을 사용하는 것이 Wwise에 가장 적합하지 않다는 결론을 내린 경우지요.
그렇다면 이제 Wwise의 요구 사항을 더 잘 충족시켜 주는 새로운 메모리 할당 시스템인 AkMemoryArena를 살펴보겠습니다.
AkMemoryArena 개요
2024.1에서는 Wwise의 새로운 메모리 할당자로 AkMemoryArena가 도입되었습니다. 수년간 Wwise가 제공한 다른 메모리 할당자는 저희 사용에 맞게 조정된 기성 솔루션이었던 반면, 처음부터 완전히 전체 메모리 할당자를 구축한 것은 이번이 처음입니다. 이는 처음부터 Wwise의 모든 요구 사항을 목표로 삼기 위한 것이었습니다.
AkMemoryArena에는 다음 기능이 포함되어 있습니다.
메모리 범위를 동적으로 매핑/매핑 해제하는 기능
필요한 경우 새로운 메모리 범위를 매핑하고 모든 구성 요소 할당이 해제되면 해당 범위의 매핑을 해제하는 기능은 여전히 유지됩니다. 즉, 메모리 풀의 크기를 엄격한 제한으로 사전에 정하도록 강요하지 않습니다. 저희 경험상 미리 정해진 메모리 제한이 없을 경우 대부분의 개발자들에게 훨씬 더 큰 유연성과 안정성이 제공됩니다.
우수한 CPU 및 메모리 파편화 성능
AkMemoryArena는 다양한 상황에서 전체 CPU 성능과 메모리 사용률 간의 균형을 유지하기 위해 다양한 할당 알고리즘을 혼합해서 사용합니다.
- 블록 기반 할당자는 크기가 256 바이트 미만인 작은 할당에 사용됩니다.
- 예: 아레나의 'Small-Block Allocator' (혹은 SBA) 섹션
- 중간 규모 할당에 대해 '적합한' 할당 정책을 사용하는 자유 목록 기반 할당자
- 예: 아레나의 'Two-level segregated fit' (혹은 TLSF) 섹션
- 대규모 할당을 위한 독립형 범위
이를 통해 개발자는 시간이 지남에 따라 전체 메모리 파편화를 제어할 수 있습니다. 또한 할당 알고리즘은 모두 일정한 시간 성능 특성을 가지고 있어 대부분의 상황에서 CPU 오버헤드를 낮게 유지하는 데 도움이 됩니다.
또한 AkMemoryArena에는 스레드 로컬 캐시도 없으며, 대신 가벼운 잠금 방식을 사용하여 스레드 간 동기화를 처리합니다. 이는 사용 가능한 모든 스레드에서 메모리 할당자의 상태를 공유함으로써 다중 스레드 상황에서도 메모리 예약을 예측 가능하게 유지하도록 해줍니다.
스레드 로컬 캐시가 부족할 경우 여러 스레드에 걸쳐 메모리를 자주 할당하면 성능이 저하될 수 있다는 단점이 있습니다. 특히 여러 코어에 걸쳐 오디오를 렌더링하는 동안 메모리 할당의 빈도를 줄이기 위해 Wwise는 이제 TempAlloc 및 BookmarkAlloc과 같은 단기 메모리 할당을 처리하기 위한 특수 메모리 할당자를 사용합니다. 이는 자체 스레드-로컬 상태를 활용하여 스레드 간 동기화의 필요성을 대부분 제거해줍니다.
메모리 할당 자체의 CPU 오버헤드 외에도 메모리 매핑을 위해 약 2MiB 크기의 Huge Pages를 활용하는 것이 더 실용적이라는 것도 발견했습니다. Huge Pages를 활용하면 CPU에서 변환 색인 버퍼가 누락되는 빈도를 줄일 수 있습니다. 이는 더 작은 4KiB나 16KiB 크기의 페이지를 사용할 때보다 전반적인 CPU 성능을 약 10% 향상시킵니다.
간단한 게임 엔진 통합
게임 엔진 통합을 간소화하고 메모리 할당 시 오류나 불확실성을 제거하기 위해 AkMemoryArena에서 메모리 범위를 획득 및 해제하는 데 사용하는 콜백을 최대한 단순하게 만들었습니다.
각 AkMemoryArena에는 메모리 관리를 위해 사용자가 제공한 한 쌍의 콜백이 필요합니다. 하나는 메모리 범위를 할당하기 위한 것이고, 다른 하나는 메모리 범위를 해제하기 위한 것입니다.
이러한 콜백을 구현하는 데는 요구 사항이 거의 없습니다.
- 메모리가 어떻게 매핑/매핑 해제되는지에 대해 특수 요구 사항이 없음
- 메모리 정렬에 대한 요구 사항이 없음
- 범위를 해제할 때 제공되는 유일한 데이터는 범위를 할당하기 위한 이전 호출에서 반환된 데이터(사용자가 제공한 할당 주소 및 임의 userData에 대한 포인터 모두)와 완전히 동일함
콜백이 얼마나 간단할 수 있는지에 대해 예로, 이러한 함수의 유효한 구현은 함수의 몇몇 매개 변수를 어떠한 추가 논리 없이 std::malloc()과 std::free()로 전달할 수 있습니다. 사실 Windows와 POSIX 기반 플랫폼에서 Wwise 사운드 엔진에서의 이러한 후크의 기본 버전은 이와 완전히 같습니다.
프로젝트 요구에 따른 맞춤 구성
많은 프로젝트 팀들은 메모리 활용 접근 방식에 대해 다양한 생각을 갖고 있습니다. 혹은 게임용으로 개발되는 콘텐츠의 특성으로 인해 메모리 활용에 대해 특정한 결정이 필요하다는 것을 알게될 수도 있죠. 예를 들자면,
- 어떤 팀은 메모리 사용을 극도로 예측 가능하게 하기 위해 애플리케이션 전체의 모든 메모리가 시작 시 모든 시스템에 할당되고 예산이 완전히 책정되는 것을 선호할 수 있습니다.
- 또 다른 팀은 대신 메모리를 아주 세분화된 수준으로 매핑/매핑 해제하여 필요한 때만 다른 시스템이 메모리를 사용할 수 있도록 하는 것을 선호할 수 있습니다.
저희는 필요에 따라 다양한 요구 사항과 사용 사례를 충족할 수 있는 풍부한 구성 옵션을 확보하려고 노력했습니다.
예를 들어 AkMemoryArena는 초기화 시 모든 메모리를 사전 예약할 필요는 없지만 초기 범위를 아주 큰 크기로 설정하여 효과적으로 작동하도록 구성할 수 있습니다. 이 초기 메모리 범위는 첫 번째 호출 이후 호출될 때마다 경고를 발행하여 메모리 할당을 위한 콜백에 소프트 제한으로 적용될 수도 있습니다.
메모리 파편화 모니터링용 프로파일러
AkMemoryArena를 Wwise에 구축하는 과정의 일환으로 이 기회를 활용하여 시간이 지남에 따라 아레나를 자세히 모니터링하고 프로파일링할수 있는 시스템을 설정하고 싶었습니다. 이는 여러분의 작품에 대한 AkMemoryArena 구성을 안내하는 데 도움이 될 뿐만 아니라 필요한 경우 문제를 식별하거나 최적화를 하는 데 보다 자세한 지원과 도움을 제공할 수 있을 것으로 기대됩니다.
메모리 사용량 감소
이러한 기능 외에도 AkMemoryArena에서 예약된 총 메모리는 물론 사용된 총 메모리도 이전에 달성할 수 있었던 것보다 훨씬 낮은 경향이 있다는 것을 발견했습니다.
AkMemoryArena 프로파일링
AkMemoryArena의 새로운 Profiler는 각 Arena의 각 범위에 대해 다음의 통계 및 데이터를 제공합니다.
- 할당 콜백 fnMemAllocSpan에 대해 각 호출에서 반환한 Address 및 UserData
- 메인 뷰의 모든 행은 fnMemAllocSpan에 대한 한 번의 호출을 나타냅니다.
- 범위의 크기
- 할당된 범위의 양과 해제된 범위의 양
- 범위의 어떤 영역이 할당되는지 보여주는 파편화 맵
각 AkMemoryArena에 대한 일부 통계도 집계되어 제공됩니다. 사용 및 예약된 총 메모리와 같은 단순한 누적 외에도 Profiler는 아레나에서 ''Largest Free(가장 큰 여유)' 공간이 무엇인지 열거해줍니다. 이는 아레나가 새로운 범위를 요청하기 전에 지원할 수 있는 가장 큰 할당을 보여주는 통계입니다. 이 값을 해제된 총 메모리 양과 비교하면 Arena가 얼마나 파편화되어 있는지를 나타내는 일반적인 지표로 사용할 수 있습니다.
이 모든 작업은 아주 적은 런타임 비용으로 수행되며, 이 데이터를 계산하기 위해 전체 애플리케이션 기록이 필요하지 않습니다. 다시 말해 게임이 여러 시간 동안 실행되더라도 Authoring 애플리케이션이 게임에 연결하여 아주 적은 노력으로 메모리 레이아웃 상태를 평가할 수 있습니다.
그런데 이 데이터를 추적하여 CPU 오버헤드를 줄일 경우 AkMemoryArena 상태에 대한 대략적인 세부 수준만 제공된다는 단점이 있습니다. 메모리 분석을 위한 많은 전문 도구처럼 할당별 세부 수준을 제공하지 못할 수도 있지만, 사용자가 메모리 활용에 문제가 있는지 쉽게 평가하기에는 충분한 데이터라고 생각합니다. 문제가 발견되면 이를 사용하여 추가 검사를 지시하고 아레나 및 메모리 전략의 추가 구성을 위한 결정을 안내할 수 있습니다.
게임 엔진으로의 통합
사전 제작된 Unity/Unreal Wwise 통합을 사용하는 대신 커스텀 게임 엔진을 관리하는 사용자의 경우 메모리 시스템을 통합하고 구성하는 방법에 대한 몇 가지 측면을 다시 고려해 볼 필요가 있습니다.
우선 개별 메모리 할당을 처리하기 위한 기존의 모든 콜백을 계속 사용할 수 있으며 이들의 작동 방식은 변경되지 않았습니다. Wwise에서의 모든 할당에 대해 자체 메모리 할당을 사용하려는 경우 해당 옵션을 활용할 수 있습니다.
하지만 이러한 콜백을 사용하면 AkMemoryArena는 물론 위에서 설명한 새로운 Profiler를 사용할 수 없게 됩니다. 모든 메모리 할당이 고려되었는지 확인해야 한다고 생각하더라도 사용 가능한 도구를 볼 때 AkMemoryArena를 대신 사용하는 것을 고려해볼 수 있습니다. AkMemoryArena를 사용하여 대부분의 메모리 할당을 처리하면 다른 전역적 메모리 시스템에 대한 부담을 완화하거나 Wwise에서 메모리 사용량을 추적하기 위한 다른 도구 개발을 단순화하는 데 도움이 될 수 있습니다.
또한 AkMemoryArena를 사용 중이더라도 각 개별적인 메모리 할당에 대한 일부 메타데이터를 기록할 수 있습니다. 이를 위해 AkMemSettings의 'Debug' 메모리 후크가 제공됩니다.
이러한 메모리 후크는 Wwise가 기본 메모리 할당 시스템을 사용하는지, 아니면 개별 메모리 할당 콜백을 대신 사용하는지에 관계없이 활성화됩니다. 이러한 콜백은 메모리 파편화에 대한 심층 진단을 수행해야 하는 경우 유용하게 사용됩니다.
위에서 언급한 것처럼 저희는 AkMemoryArena의 초기 설정 및 통합을 최대한 간단하게 유지하려고 노력했습니다. 다음은 메모리 할당 및 할당 해제를 위한 콜백 구현의 예시입니다.
AkMemoryArena는 별도로 구성되기 때문에 심지어 각 AkMemoryArena에 대해 서로 다른 콜백을 사용할 수도 있습니다. 이는 해당 시스템에서 사용하는 할당 크기와 수명의 상대적인 차이로 인해 Primary 혹은 Media 아레나에 대해 다른 하위 레벨 메모리 할당자를 사용하려는 경우 적용할 수 있습니다.
또는 특정 메모리 아레나의 비활성화하려는 경우 콜백을 nullptr로 설정하면 됩니다.
이는 기본적으로 Release 구성에서 Profiler 아레나(AkMemoryMgrArena_Profiler)를 비활성화하거나 오디오 처리에 장치별 메모리를 사용하지 않는 플랫폼에서 Device 메모리 아레나(AkMemoryMgrArena_Device)를 비활성화하는 데 사용됩니다.
마찬가지로 Wwise를 게임 엔진에 통합하는 나머지 부분이 어떻게 구성되어 있는지에 따라 Wwise가 일반 게임플레이에서 Media 아레나에 메모리를 전혀 할당하지 않을 수도 있습니다. 프로젝트가 AK::SoundEngine::LoadBank나 AK::SoundEngine::LoadBankMemoryCopy 대신 AK::SoundEngine::SetMedia 및 AK::SoundEngine::LoadBankMemoryView와 같은 API를 독점적으로 사용하는 경우 이에 해당할 수 있습니다. Authoring 도구가 새로운 미디어를 사운드 엔진으로 전송할 때 일부 Media 할당이 계속 수행되기 때문에 이 옵션은 Wwise의 Release 구성을 대상으로 하는 경우에만 고려해야 합니다.
또한 게임 엔진 통합에 따라 사운드 엔진 외부에도 Memory Arena를 사용하는 것을 고려해볼 필요가 있습니다. 예를 들어 AK::MemoryMgr::Malloc을 사용하여 자체적으로 메모리 할당을 생성하고 SoundBank 데이터를 해당 할당에 로드한 다음 이 메모리를 AK::SoundEngine::LoadBankMemoryView에 제공할 수 있습니다. 이 경우 메모리가 여전히 AkMemoryArena에 의해 관리되므로 Profiler와 기타 시스템의 이점을 얻으면서 여전히 메모리 할당을 코드에서 소유하고 수명은 사용자가 제어할 수 있습니다.
추가 구성 및 조정
AkMemSettings의 memoryArenaSettings 배열은 Memory Arena의 다른 매개 변수를 구성하는 데 사용됩니다. 이 배열은 사운드 엔진에서 합리적인 기본값으로 구성되어 있지만, 각 게임마다 콘텐츠와 메모리 요구 사항이 다르기 때문에 가장 일반적인 상황에서 시스템이 제대로 작동하도록 하려면 게임 콘텐츠에 대한 적절한 최적화가 메모리 활용에 상당한 도움이 될 수 있습니다. 예를 들어 시뮬레이션된 게임 콘텐츠에 대한 시험에서 각 시험마다 AkMemoryArena 설정을 간단하게 조정하면 전체 Memory Reserved를 5-10%까지 줄일 수 있다는 사실을 발견했습니다.
다음은 저희가 권장해드리는 몇 가지 간단한 제안 사항입니다.
- 일반적인 메모리 사용량이나 목표 메모리 예산과 일치하도록 AkMemoryArenaSettings::uTlsfInitSize를 설정하세요. 보통 'Base' 혹은 초기 범위가 더 클수록 개별 메모리 범위를 갖는 대신 최고의 메모리 파편화 성능을 제공하는 경향이 있다는 것을 발견했습니다. 또한 보다 큰 초기 크기는 시작 시 전체 시스템의 전체 메모리 예약을 명확하게 하고 다른 영역의 메모리 예산 책정에도 도움이 될 수 있습니다.
- AkMemoryArenaSettings::uSbaInitSize를 Profiler에서 식별된 워터마크로 설정하세요. SBA에는 자체적인 'Base' 범위가 있는데, 이는 각 구성 요소 할당에 대한 메모리 공간을 줄여 (할당당 약 16 바이트) 추가적인 이점을 제공합니다. 이를 더 높은 값으로 설정하여 더 '작은' 할당이 SBA Base Span 안에 들어가도록 할 경우 Memory Reserved뿐만 아니라 Memory Used를 낮추는 데 직접적인 영향을 미칠 수 있습니다..
- TLSF 범위의 파편화를 줄이려면 AkMemoryArenaSettings::uAllocSizeHuge를 더 낮은 값으로 설정하세요. 여기서 값이 낮을수록 더 많은 할당이 TLSF 범위에 포함되지 않고 독립형 'Huge' 범위가 됩니다. 이는 fnMemAllocSpan에 대한 Memory Arena 후크 통합이 어떻게 설정되었는지에 따라 크게 달라집니다. 왜냐하면 이것이 fnMemAllocSpan로의 호출이 더 많이 발생하도록 하며 범위의 외부 파편화가 문제가 되지 않는다고 가정해야 하기 때문입니다.
이와 같은 다른 제안 사항은 Wwise SDK 문서의 AkMemoryArena 구성 및 조정하기에서 확인할 수 있습니다.
댓글