스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
게임 메모리 프로파일링 및 최적화
Apple 플랫폼에서 게임의 메모리를 계산 및 할당하는 방법을 알아보세요. Instruments 및 Game Memory 템플릿을 사용하여 게임을 프로파일링하고, 메모리 그래프를 사용하여 현재 메모리 사용을 모니터링하며, Xcode 메모리 디버거 및 명령줄 도구를 사용하여 분석하는 방법을 보여드립니다. 또한 Metal 디버거의 Metal 리소스를 살펴보고 메모리 사용을 최적화하는 데 더욱 도움이 될 팁과 요령을 제공합니다.
리소스
관련 비디오
WWDC23
WWDC22
WWDC21
-
다운로드
♪ ♪
게임 메모리 프로파일링 및 최적화에 오신 것을 환영합니다 저는 Apple GPU 소프트웨어 팀의 Jack XU입니다 제 동료인 Seth Lù와 함께 합니다 지난 몇 년 동안 우리 팀은 여러분과 같은 게임 개발자와 협업하면서 함께 게임 메모리를 이해하고 개선하고자 했습니다 오늘 저희는 배운 것을 공유하고자 합니다 이를 통해 게임 메모리를 디버그하고 최고의 플레이어 경험으로 멋진 게임을 만들 때 앞서나갈 수 있습니다
CPU와 GPU 개체 모두의 게임 내 메모리 사용량을 분석합니다 그리고 게임 내 할당 물리적 메모리의 실제 사용량 개체 간의 참조를 분석합니다 메모리에는 많은 측면이 있어 개발자 도구를 통해 다른 각도에서 메모리를 살펴봅니다 가이드 투어를 통해 사용 방법을 직접 경험하며 Terminal의 Xcode, Instruments 명령 줄 도구를 살펴보려고 합니다 오늘 가이드 투어에서는 게임 메모리의 이해부터 시작합니다 메모리와 메모리의 증가의 프로파일링으로 시작하면서 Seth가 Instruments를 설명하겠습니다 Instruments를 시간적으로 살펴보고 Xcode와 Terminal의 도구를 사용해 게임의 메모리 그래프를 분석합니다 이런 워크플로는 현재 메모리 사용 상태와 전체 게임 메모리 분석에 중점을 두고 진행됩니다 마지막으로, Seth가 말씀드릴 내용은 Metal Debugger를 사용해 Metal 리소스를 최적화하는 방법으로 독립적이기는 하지만 게임 메모리의 핵심 영역입니다 게임 메모리에 대한 이해부터 시작하겠습니다 Metal 샘플 코드를 사용한 Modern Rendering과 같은 Xcode에서 게임을 실행하는 경우 Xcode의 디버그 탐색기에서 이 메모리 보고서를 열 수 있습니다 처음으로 보게 되는 게임의 현재와 최근 메모리 사용량과 시스템에 미치는 영향 수준입니다
게이지의 숫자는 게임의 현재 메모리 사용량입니다 메모리 디버깅에서 중요한 첫 번째 단계는 숫자의 의미를 이해하는 것입니다
간단히 말씀드리면 게임의 실제 메모리 사용량은 할당과 같지 않습니다 실제 메모리 사용량은 물리적 메모리입니다 할당은 가상 메모리 주소 공간에서 게임이 요청한 메모리입니다 다른 유형의 할당은 별도로 자연적으로 계산됩니다
게임이 메모리를 할당하면 새로운 할당이 물리적 메모리 공간을 즉시 또는 직접 차지하지 않습니다 반대로 가상 메모리 주소 공간에 공간을 예약하는데 이는 시스템이 각 프로세스에 제공합니다 프로그램이 나중에 실제로 이 할당량을 사용할 때 시스템이 물리적 메모리의 공간을 준비합니다
동일한 유형의 할당은 범주로 그룹화되고 가상 주소 공간을 거의 차지하지 않습니다 이러한 범주에는 프로그램의 실행 가능한 바이너리 모든 라이브러리와 프레임워크 스택, 로컬, 임시 변수 일부 함수 인수의 저장소 힙이라고도 하는 동적 메모리 영역을 포함하며 이는 프로그램이 수동으로 할당하는 클래스 인스턴스 스토리지와 메모리 게임 자산 파일과 같은 읽기 전용 리소스에서 매핑된 영역 버퍼, 텍스처 파이프라인 상태 개체와 같은 Metal 개체 등이 있습니다 범주는 영역으로 되어 있습니다 후드 아래에는 메모리 작업이 메모리 페이지의 세분화에서 작동하는데 최신 Apple 기기에 각 16KiB입니다 각 영역이 한 장 이상의 페이지를 차지한다는 뜻으로 16KiB 이상입니다
게임을 진행하면 메모리 상태가 계속 진화하고 새 개체가 할당되고 오래된 아이템은 파괴되고 영역이 계속 변합니다 하지만 영역에서 사용된 페이지만 물리적 메모리에 존재하고 시스템이 다른 앱과 마찬가지로 계속 게임에 청구합니다
게임의 메모리 페이지는 세 가지로 더티, 압축, 클린 중 하나입니다 한 번 살펴보겠습니다 더티 메모리 페이지에는 게임에서 쓴 메모리가 포함됩니다 게임에서 변수나 기호를 수정하는 경우 힙과 프레임워크의 메모리 할당을 포함합니다 Apple 실리콘이 있는 장치에서는 액세스된 Metal 리소스도 이 범주에 속합니다 CPU와 GPU가 같은 고속 통합 메모리 풀을 공유하기 때문이죠
하지만 더티 페이지가 오랫동안 사용되지 않으면 시스템이 페이지를 압축하거나 플래시 또는 디스크에 저장하여 물리적 메모리에서 페이지를 축소할 수 있는데 이를 스와핑이라고 합니다 따라서 장치가 더 많은 앱과 서비스를 실행할 수 있죠 나중에 게임에서 이 페이지를 다시 요청하면 시스템에서 압축을 해제하거나 디스크에서 이 페이지를 엽니다 게임은 압축되지 않은 크기로 계속 청구됩니다 클린 메모리 페이지의 경우 디스크에서 매핑된 읽기 전용 파일을 포함하는데 텍스처, 오디오 애셋 프로세스에 로드된 프레임워크 등 다양한 기능을 제공합니다 시스템이 언제든지 디스크를 비우거나 디스크에서 다시 로드할 수 있기 때문에 게임의 메모리 풋프린트에 포함되지 않습니다 하지만 메모리가 남아 있을 수 있고 과도한 사용은 시스템과 게임 속도를 저하시킬 수 있습니다 보통 처음 두 부분이 가장 흥미로운데 메모리 풋프린트라고 합니다 시스템이 메모리 제한을 위해 이를 사용합니다
어떤 용어에서는 ‘더티 메모리’가 더러움이 깨끗함의 반대인 것처럼 메모리 풋프린트를 의미하기도 합니다 하지만 걱정 마세요 헷갈리는 경우에는 의미를 확실히 구분하겠습니다 메모리의 작동 방식과 시스템의 청구 방식을 살펴봤습니다 Xcode 메모리 게이지 외에도 Mac의 Activity Monitor 앱 등 시스템의 여러 곳에서 메모리 공간을 찾을 수 있습니다 일부 Apple 플랫폼에서는 앱 메모리 제한을 위해 사용합니다 게임은 이 메트릭을 사용해 메모리 사용량을 안내할 수 있습니다 현재 풋프린트와 사용 가능한 메모리를 쿼리할 수 있는 유용한 API가 있습니다 간단히 살펴보겠습니다 iOS, iPadOS 또는 tvOS 게임에 사용할 수 있는 시스템 메모리를 얻으려면 os/proc.h 헤더 파일인 os_proc_available_memory를 호출합니다
Apple 플랫폼의 메모리 풋프린트도 마찬가지로 proc_pid_rusage를 통해 ‘get pid’와 ‘rusage_info_current’라는 프로세스 ID를 사용하는데 이는 현재 버전 6과 데이터 저장소입니다 그리고 물리적 풋프린트와 수명 주기 최대 물리적 풋프린트를 검색합니다
이 첫 번째 섹션에서는 메모리의 개념을 살펴봤습니다 게임의 할당은 가상 메모리 주소 공간에서 발생하고 게임을 통해 액세스하면 물리적 메모리 공간을 16키비바이트로 차지하게 됩니다 메모리 풋프린트는 게임의 실제 메모리 사용을 결정하는 Apple 플랫폼의 기본적이고 보편적인 메트릭입니다 메모리 공간에는 더티, 압축, 스와핑된 페이지가 있습니다 Apple 실리콘의 CPU와 GPU 개체를 모두 포함합니다 그리고 메모리 제한 적용에 사용됩니다 게임은 시스템 API를 호출해 풋프린트와 사용 가능한 메모리를 얻습니다 이제 메모리의 작동 방식을 살펴봤으니 게임에서 어떻게 보이는지 알아보겠습니다 자세한 내용은 Seth에게 차례를 넘기겠습니다 감사합니다, Jack 게임의 메모리 증가를 포착하는 것부터 시작하겠습니다 그리고 계속 Modern Renderer 샘플 프로젝트를 사용하겠습니다 Xcode에서 게임을 실행할 때 Memory Gauge가 시간 경과에 따른 메모리 공간을 보여줍니다 하지만 Instruments에서 게임을 프로파일링하면 메모리 사용을 더 자세히 알 수 있습니다 게임이 출시될 때 대부분 많은 메모리를 할당할 수 있기 때문이죠 기존 실행에 추가하는 대신 새로운 게임 출시부터 프로파일링을 시작할 수 있죠 Xcode에서 게임 프로파일링을 빠르게 시작하려면 실행 버튼을 누른 상태에서 ‘프로파일링’을 선택합니다 자동으로 Instruments로 이동할 수 있습니다 Instruments 앱에는 시스템의 다양한 측면을 기록하고 기록된 데이터를 타임라인에 시각화하는 프로파일링 도구 모음이 포함되어 있습니다 올해의 새로운 기능은 Metal 게임의 메모리 증가를 더 잘 이해할 수 있도록 지원하는 Game Memory 템플릿입니다
이 템플릿에는 메모리 할당을 기록하기 위한 Allocations와 Metal Resource Events 메모리 풋프린트를 기록하는 VM Tracker 가상 메모리 활동을 기록하는 Virtual Memory Trace Metal 관련 이벤트를 기록하는 Metal Application과 GPU가 제공됩니다
이번 데모에서는 처음 세 가지 서비스 Allocations, Metal Resource Events VM Tracker를 중점적으로 살펴봅니다 하지만 먼저 게임 추적을 기록하겠습니다 이 기록 버튼을 누르면 기록을 시작할 수 있습니다 나중에 기록을 멈추려면 같은 버튼을 누르거나 게임을 끝내면 됩니다 Instruments가 Modern Renderer를 기록하는 동안 추적을 기록하는 다른 방법을 보여드리겠습니다 xctrace 명령을 사용하면 프로그래밍 방식으로 기록할 수 있어 자동화 워크플로에 유용합니다
또한 기기 이름을 지정해 iPhone, iPad 또는 Apple TV를 대상으로 선택할 수도 있죠
Instruments 추적을 포착했기 때문에 Allocations를 먼저 살펴보겠습니다 Allocations는 메모리 할당 크기, 개체 참조 수에 대한 상세한 보기를 제공합니다 하지만 비공개 Metal 리소스는 포함하지 않습니다 Statistics 보기에는 모든 힙 할당과 익명 VM이 표시됩니다
All Heap Allocations에는 개체를 포함할 수 있는 malloc된 버퍼가 있고 모든 익명 VM에는 더티일 수 있는 VM 영역이 포함됩니다 잠시 후에 Metal 리소스가 이 범주에 속한다는 것을 알 수 있습니다
All Heap Allocations를 자세히 살펴보겠습니다 일반적으로 할당량이 클수록 최적화에 더 유용합니다 가장 큰 단일 할당을 찾으려면 Size 테이블 열을 클릭해 크기별로 할당을 정렬할 수 있습니다
할당의 경우 이 화살표를 클릭하면 Swift와 Objective-C 개체에 대한 참조 수 변경 사항을 볼 수 있죠
목록에서 선택된 이 대규모 할당의 경우 검사기에 할당 기록에 대한 스택 추적이 존재합니다 버튼을 클릭하면 시스템 라이브러리나 프레임워크를 숨길 수 있습니다 여기 스택 추적에 따르면 Modern Renderer가 애셋을 로드했을 때 할당이 발생했습니다
프레임을 더블 클릭하면 소스 코드가 표시됩니다 이제 다시 돌아가서 ‘All Anonymous VM’ 범주를 살펴보겠습니다
Metal 게임에서는 IOAccelerator와 IOSurface 범주에서 많은 할당량을 찾을 수 있습니다 IOAccelerator의 할당은 Metal 리소스에 해당합니다
스택 추적을 보면 애셋을 로드할 때 할당이 발생했음을 알 수 있죠
IOSurface의 할당은 드로어블에 해당합니다 스택 추적에서 MetalKit 보기가 드로어블을 요청했음을 볼 수 있습니다
Allocations는 기본적으로 할당 크기를 시각화합니다 하지만 다른 방식으로 볼 수도 있습니다 Allocations 트랙의 화살표 버튼을 사용하면 표시 모드를 사용자 정의하여 Allocation Density를 시각화할 수 있습니다 이렇게 하면 시간 경과에 따른 할당량을 표시하도록 그래프가 업데이트되고 메모리 할당에 대한 스파이크가 표시됩니다 스파이크가 메모리 증가의 원인일 수 있습니다 할당에 표시된 데이터는 상당히 낮은 수준입니다 할당된 Metal 리소스를 더 잘 파악하기 위해 Metal Resource Events로 넘어가겠습니다 Metal Resource Events는 Metal 리소스를 중심으로 합니다 Resource Events 보기에서는 Metal 리소스 할당과 할당 해제 기록을 볼 수 있습니다 Metal 리소스를 라벨로 식별할 수도 있는데 Metal API를 통해 프로그래밍 방식으로 지정할 수 있죠 Allocations와 마찬가지로 검사기에서 할당 기록에 대한 스택 추적을 찾을 수 있습니다
이 도구는 Allocation과 Deallocations 트랙을 Metal 장치에 추가합니다 이벤트의 밀도를 시각화하는 데 도움이 되죠 지금까지 살펴본 결과 Allocations와 Metal Resource Events는 메모리 할당을 이해하는 데 도움이 됩니다 하지만 할당이 항상 메모리 풋프린트를 의미하지는 않습니다 이제 VM Tracker로 이동해 실제 메모리 사용량을 살펴보겠습니다
VM Tracker에는 압축되지 않은 더티 메모리나 압축 또는 스와핑된 메모리가 표시됩니다 Dirty Size는 압축되지 않은 더티 메모리를 나타냅니다 Swapped Size는 압축 또는 스와핑된 메모리를 나타냅니다 이 기록을 보면 Modern Renderer의 압축 또는 스와핑된 메모리가 사용되지 않습니다 세부적인 Summary 보기에는 VM 영역이 표시됩니다 ‘매핑된 파일’ 영역을 보면 게임 애셋과 같은 메모리 매핑 리소스를 찾을 수 있습니다 Modern Renderer는 비스트로 애셋 파일을 메모리에 매핑합니다 여기까지 간단하게 Allocations와 Metal Resource Events VM Tracker를 살펴봤습니다 메모리 증가를 프로파일링하는 방법을 간단히 요약해 보겠습니다 먼저 Game Memory 템플릿을 선택해서 추적을 기록하고 분석합니다 메모리 증가 패턴을 재생하거나 확인할 때 이 프로세스를 여러 번 반복할 수도 있습니다 새로운 Game Memory 템플릿이 게임 내 메모리 할당과 풋프린트 증가를 이해하는 데 도움이 되기를 바랍니다 Instruments 사용 방법을 자세히 알아보려면 다른 영상을 확인해 보세요 다시 Jack에게 차례를 넘기겠습니다
게임 메모리 템플릿은 정말 멋진 것 같습니다 시간 경과에 따른 메모리 사용의 변화를 이해하는 데 도움이 될 것입니다 또한 주어진 시간에 대한 게임 메모리 상태를 포착하여 메모리 상태를 다른 측면에서 자세히 검사할 수 있습니다 이를 위한 메모리 그래프와 도구 모음이 있습니다
메모리 그래프라는 파일은 게임 메모리 상태의 전체 스냅샷을 효율적으로 저장하며 개체 생성 기록 참조, 압축 또는 스와핑이 여기에 포함됩니다
문제가 발생하기 전이나 발생한 후에 언제든지 스냅샷을 생성하여 비교할 수 있습니다 더 자세히 살펴보기 위해 메모리 그래프로 메모리를 분석하는 방법을 쿡북 유추로 살펴보겠습니다 재료와 준비 부분을 포함합니다
재료의 경우, 필요한 것은 게임과 Malloc Stack Logging 캡처된 메모리 그래프입니다 Malloc Stack Logging 구성과 메모리 그래프 캡처는 빠르게 할 수 있습니다
Malloc Stack Logging은 게임 프로세스의 할당 정보를 기록합니다 Run 작업을 선택하고 Diagnostics로 가서 Malloc Stack Logging 확인 상자를 선택합니다
두 가지 옵션은 이렇습니다 All Allocation과 Free History로 할당이 해제된 후에도 모든 개체를 추적합니다 로깅 데이터는 메모리를 더 많이 사용할 수 있지만 조각화와 같은 문제를 디버깅하는 데 유용합니다 반면 Live Allocation Only는 할당 해제된 개체를 기록에서 삭제해 더 가볍습니다 이 경우에는 라이브 개체에 대한 참조만 조사하기 때문에 이 옵션을 선택할 수 있습니다 사실 대부분의 경우 Live Allocation Only가 권장되는 옵션입니다
Xcode에서 시작하지 않는다면 환경 변수를 설정할 수 있습니다 malloc 매뉴얼 페이지에서 다른 기록 모드도 확인해 보세요 그리고 메모리 그래프를 준비합니다 디버그 영역의 디버그 메모리 버튼을 클릭하기만 하면 됩니다 Xcode가 메모리 스냅샷을 캡처하고 처리하고 메모리 디버거를 입력합니다 Xcode Memory Debugger는 게임 메모리 사용량을 직관적으로 보여줍니다 잠시 살펴보겠습니다 왼쪽을 보면 Debug Navigator가 개체 인스턴스의 계층 목록을 제공합니다
오른쪽을 보면 File Inspector가 메모리 풋프린트 가동 시간, 캡처 날짜 같은 유용한 정보를 제공합니다
가운데 영역을 보면 왼쪽에서 선택한 개체가 있는 메모리 그래프 보기와 참조가 이 개체에 연결되는 방식이 표시됩니다 잠시 후 그래프를 살펴보겠습니다
그리고 파일 메뉴에서는 향후 분석을 위해 메모리 그래프를 저장하거나 팀과 쉽게 공유할 수 있는 옵션을 제공합니다
Mac 게임의 경우 프로세스 ID나 이름을 사용해 누출 명령줄 프로그램으로 메모리 그래프를 캡처할 수도 있죠 보안 쉘에서 원격으로도 가능하기 때문에 게임을 전체 화면으로 실행하고 집중해야 하는 경우에도 커서가 게임에 남아 있습니다 메모리 그래프 분석에 필요합니다
이제 Xcode Memory Debugger와 Terminal의 다양한 명령 줄 도구로 이 메모리 그래프를 살펴보고 할당, 풋프린트 등을 알아보겠습니다 첫 번째 단계는 메모리 사용을 범주별로 분류하는 것입니다 바로 풋프린트 프로그램이 수행하는 역할이죠
풋프린트가 메모리 그래프 정보로 간단한 요약을 재생성합니다 일반적으로 더 큰 범주에 집중해야 합니다 Modern Rendering 샘플 코드처럼 게임 메모리 그래프의 경우 IOAccelerator가 가장 큽니다 Seth가 말씀드린 것처럼 Metal 리소스가 포함됩니다 힙 할당은 여러 MALLOC-underscore-prefixed 범주에 속하는데 시스템이 크기 풀에 힙 할당을 그룹핑하여 성능을 개선하기 때문이죠 이러한 개체는 타사 플러그인이나 라이브러리와 같이 게임에서 효과음이나 물리 시뮬레이션을 수행하는 여러 위치에서 가져올 수 있습니다
여기 멋진 Apple Arcade 게임 Manifold Garden의 메모리 그래프가 있는데 William Cheer 스튜디오에서 제작된 게임입니다 다행히 게임의 메모리 사용량을 보여드릴 수 있게 되었습니다 게임에서 Unity를 사용하는 Manifold Garden과 같은 게임 엔진이나 메모리 맵 위에 있는 사용자 지정 할당기를 사용하는 경우 메모리가 태그가 지정되지 않은 VM_ALLOCATE로 표시됩니다 팁을 드리면 Apple 플랫폼에서는 게임이 최대 16개의 앱별 태그를 사용하여 메모리 사용을 드릴 다운보다 더 명확하게 알 수 있습니다 매우 간단합니다
먼저 16가지 옵션 중 하나로 태그를 만듭니다 그런 다음 ‘em map’을 호출할 때 마이너스 태그를 새로운 ‘file descriptor’로 바꿉니다 태그와 범주의 정의 방식을 보려면 ‘em map’ 매뉴얼 페이지를 확인하세요
‘mach VM allocate’를 사용하면 할당할 때 플래그 인수에 동일한 플래그가 포함됩니다
풋프린트 프로그램에서는 더티 크기에 스와핑과 압축도 포함되기 때문에 각 범주별로 전체 청구된다고 생각하면 됩니다
현재 메모리 사용의 구성과 공간 구성 방식을 간단히 살펴봤습니다 이 메모리 중 일부는 덜 사용되고 압축되거나 스와핑됩니다 따라서 메모리를 절약할 수 있죠 다음 단계는 게임에서 사용되는 압축이나 스와핑 메모리 양을 파악하고 최적화하는 것입니다
이를 위해 메모리 그래프를 vmmap으로 실행할 수 있죠 두 개를 결합하는 대신 더티와 스와핑된 크기를 제공합니다 이 더티 열에는 현재 스와핑이나 압축되지 않은 일반 더티 메모리가 포함되는 반면 스와핑 열에는 압축이나 스와핑된 메모리의 원래 크기가 포함됩니다 시스템은 이 두 열을 추가하여 풋프린트를 결정합니다 하지만 스와핑 크기 열의 내용은 자주 사용되지 않기 때문에 게임 메모리를 최적화하기 위해 찾아야 할 항목을 보여주는 좋은 예입니다 다음은 가상 크기 열이 있는 할당 크기입니다 그리고 상주 크기에는 실행 파일과 메모리 매핑 파일 같은 클린 페이지가 포함됩니다
vmmap은 힙 할당을 별도의 테이블과 함께 표시합니다 출력의 맨 아래에서는 vmmap이 영역별로 힙 메모리를 그룹핑합니다 이 영역은 게임의 사용량이나 라이프사이클을 반영합니다 MallocStackLogging을 실행했기 때문에 힙의 할당이 도구 영역에 있습니다 그렇지 않으면 할당 크기를 기준으로 MallocHelperZone과 DefaultMallocZone 두 기본 영역에 있습니다 일반적으로 QuartzCore 영역과 같은 더 작은 시스템 유틸리티 영역을 건너뛸 수 있죠
그리고 조각화가 의심되는 경우 수십 또는 수백 메가바이트와 같은 높은 조각화 크기나 백분율이 표시되는데 WWDC 2021 세션에서 조각화 문제를 자세히 다룹니다
대시 요약 없이 vmmap을 실행하거나 표준 모드에서 vmmap을 실행할 경우 이 범주 내의 각 VM 영역이 한 줄씩 표시됩니다 앞서 설명드린 것처럼 가상 주소 공간을 볼 수 있죠 vmmap을 사용하면 사용 빈도가 낮은 더티 메모리를 추출할 수 있습니다 기본적으로 다양한 크기의 동적 할당이나 malloc된 힙 메모리 사용량도 많이 존재합니다 주의 깊게 살펴봐야 하죠 힙 도구는 클래스별로 작은 리소스를 그룹핑하고 인스턴스 수를 기준으로 정렬합니다 클래스는 VTable, Objective-C 또는 Swift를 통해 C++로 결정됩니다
일부 메타데이터에 대한 헤더를 건너뛰기 위해 --quiet 인수를 사용합니다 올해의 새로운 힙은 개체 유형을 더 지능적으로 식별합니다 Malloc Stack Logging에 의해 기록된 정보를 사용해 호출자나 담당 라이브러리를 표시하기 때문에 거대한 비객체는 더 이상 존재하지 않죠 Manifold Garden의 메모리 그래프를 다시 보겠습니다 이 예에서는 FMOD Studio와 같은 플러그인과 GameAssembly.dylib같은 게임 구성요소가 힙 사용량을 얼마나 차지하는지 처음으로 알 수 있습니다 이제 메모리의 분산 방식을 자세히 알 수 있죠 또 이런 개체의 자세한 정보를 얻을 수 있는 방법도 알려줍니다 이 예의 경우 개발자가 FMOD Studio를 열어 게임의 사운드 트랙과 사운드 효과를 미세 조정하거나 Unity에서 게임 코드 최적화를 찾을 수 있습니다
가끔은 클래스 인스턴스 수보다 클래스 전체 크기를 기준으로 정렬하는 것이 더 유용합니다 Modern Rendering 샘플 프로젝트의 메모리 그래프에서 가장 큰 클래스는 2억 5천 8백만 바이트 이상입니다 Modern Rendering 샘플에서 더 큰 개체를 계속 찾으려면 힙을 사용해 클래스 전체 크기를 기준으로 개체를 정렬하고 각 클래스를 요약하는 대신 --show size로 모든 개체를 정렬합니다 또한 2억 5천 5백만 바이트 크기의 NSConcreteMutableData의 스토리지 개체를 살펴보겠습니다 무엇인지 알아내기 위해 주소를 먼저 알아야 합니다 --address를 추가하고 NSConcreteMutableData 패턴과 wildcard–*를 입력하면 괄호의 크기 필터가 10MB 이상의 개체만 나열할 수 있습니다 개체의 주소입니다 자세한 분석을 위해 다음 단계에서 사용하겠습니다 인스턴스에 대한 개체 식별 기능이 향상된 힙 도구입니다 지금까지 게임에서 메모리를 사용하는 개체를 파악하기 위한 세 가지 도구를 알아봤는데 모두 다른 측면을 보여줍니다 제가 보여드린 건 하나의 워크플로일 뿐입니다 특정 메모리 패턴이나 게임에 사용되는 기술에 따라 원하는 방식으로 사용할 수 있습니다
확실히 알지 못하는 개체를 파악한 다음 원본, 즉 할당 호출 스택을 가져옵니다
Modern Rendering에 있는 2억 바이트 개체의 경우 --call tree 모드를 사용해 주소를 malloc_history에 전달합니다 추가적인 반환 인수가 있으면 할당에 가장 가까운 기능에 집중할 수 있죠 그리고 할당의 역추적입니다 마찬가지로 Xcode Memory Debugger도 검사기에 개체의 할당 기록을 표시합니다 개체를 선택하고 메모리 검사기만 클릭하면 이렇게 나옵니다 또 다른 예로 VM_ALLOCATE를 주소 대신 클래스 패턴으로 전달하면 사용자 지정 할당기 디버깅과 같은 게임이나 플러그인의 익명 VM 사용량을 확인할 수 있죠 Xcode든 malloc_history든 할당 역추적을 파악하고 더 자세히 살펴보기 위해 브레이크포인트를 설정할 수 있습니다
마지막으로, 개체 참조 조사도 도움이 됩니다 메모리 그래프는 항상 개체 참조를 기록합니다 MallocStackLogging을 사용하지 않을 때도 마찬가지입니다 이전에 누출을 사용해서 Xcode 외부의 메모리 그래프를 캡처한 적이 있습니다 누출은 그 이상의 역할을 합니다 메모리 그래프의 모든 참조를 확인하기 때문에 누출과 순환 참조는 중요합니다 누출은 힙에서 추적 트리 인수와 개체 주소를 사용해 개체에 대한 참조 트리를 이렇게 가져옵니다 그러나 이 예에서는 트리가 상당히 크기 때문에 Terminal에서 보는 것보다 더 나은 방법이 있습니다
Xcode 14에서는 선택한 개체의 인고잉 엣지와 아웃고잉 엣지를 모두 표시하도록 그래프 보기를 다시 설계했습니다
Xcode가 엣지를 선택할 수 있는 새로운 인접 선택 팝업도 포함되어 있습니다 복잡한 게임에서 개체 참조를 파악할 때 생산성을 크게 개선할 수 있습니다
주변을 둘러보니 텍스처 관리자가 이 개체에 액세스하는 것 같습니다 여러분의 게임에서 누출 도구와 메모리 그래프 보기를 사용해 중요한 개체 참조 관계를 찾고 게임에서 이 개체가 어떻게 액세스되는지 살펴보세요 그렇게 하면 누출이나 Xcode를 통해 개체의 중요한 참조를 찾을 수 있습니다 누출 매뉴얼 페이지와 Xcode 도움말에서 도구 사용 방법을 확인하세요
메모리 그래프 분석 쿡북에서는 각 단계에서 특정 도구를 사용합니다 모든 것은 메모리 그래프 분석을 위해 함께 사용됩니다
요약하면, 첫 번째 방법은 MallocStackLogging을 사용해 메모리 그래프로 메모리를 캡처하고 분석하는 것입니다 그리고 Xcode로 메모리 그래프를 캡처하거나 Mac 게임용 누출 도구를 사용합니다 그 다음 크기가 크고 문제가 있는 개체를 찾습니다 풋프린트, vmmap, 힙 도구가 메모리를 간단하게 분석하거나 세부적으로 분석합니다 malloc_history를 사용하면 개체가 할당된 위치를 확인할 수 있고 누출이 개체 사용이나 참조를 분석할 수 있습니다 이전 세션에 이런 도구의 사용에 대한 데모와 더 자세한 설명이 포함되어 있습니다 지금까지 Metal 리소스 조사를 계속 미뤘는데 드디어 차례가 왔습니다 Seth에게 넘기겠습니다 안녕하세요 게임에서 Metal 리소스는 큰 메모리 덩어리를 사용합니다 하지만 메모리 사용을 최적화하는 방법이 있습니다
게임에서 Metal 리소스를 최적화할 때 사용할 수 있는 메모리 절약에 대한 목록을 요약했습니다 Metal Debugger를 이용해 리소스를 검사하고 게임 메모리를 줄일 수 있는 고급 기술을 알아보겠습니다
Metal Debugger는 원스톱 숍으로 Metal 게임을 디버깅할 수 있습니다 GPU 프레임을 캡처한 후에 요약 페이지를 찾을 수 있습니다 캡처된 워크로드에 대한 일반적인 통계가 제공됩니다
페이지 하단을 보면 4개의 범주로 나누어진 인사이트 목록이 있습니다 ‘메모리’ 범주의 인사이트는 게임 메모리 절약을 제안합니다 특별히 추적에 대한 메모리 인사이트는 많지 않습니다 이런 인사이트를 살펴봐도 몇 메가바이트의 메모리만 절약할 수 있죠
하지만 게임에 따라 더 많은 절약 효과가 있을 수 있습니다 Metal 리소스에서 사용되는 메모리를 더 완벽하게 파악하려면 Show Memory 버튼을 클릭해 Memory Viewer를 사용하세요
Memory Viewer는 게임에서 캡처한 전체 리소스 목록을 제공합니다 상단 절반에는 필터링의 여러 범주가 표시됩니다 텍스트와 같은 리소스를 빠르게 검색할 수 있습니다 하단 절반에는 테이블에 텍스처만 표시됩니다 필터는 다음에 보겠습니다 리소스 테이블에는 게임 최적화를 위한 열 모음이 있습니다 중요한 리소스를 식별하는 데 도움이 될 수 있는 열을 강조해 보겠습니다
인사이트 열은 요약 페이지에서 방금 본 내용과 유사합니다 테이블을 이 열로 정렬하면 인사이트가 있는 모든 리소스를 빠르게 확인할 수 있습니다 인사이트 아이콘을 클릭하면 발견 내용을 설명하고 가능한 조치를 제공하는 팝업이 표시됩니다 이 열 바로 옆에는 할당된 크기가 있습니다 이 열을 기준으로 정렬하면 가장 큰 리소스를 볼 수 있죠 일부 리소스가 실제로 메모리 크기를 많이 사용하면 유용할 수 있습니다 예를 들어, 일부 텍스처를 더 작은 해상도로 조정하고 버퍼에 로드된 일부 모델은 게임의 시각적 품질에 영향을 미치지 않기 때문에 더 낮은 폴리카운트를 사용할 수 있습니다 텍스처 메모리를 저장하는 방법은 여러 가지입니다 또 다른 중요한 열은 Time Since Last Bound입니다 이 열로 리소스를 정렬하면 최근 사용하지 않은 리소스를 찾을 수 있습니다 리소스가 사용되지 않으면 애셋을 로드할 가치가 있는지 다시 확인하는 것이 좋습니다 한동한 바인딩되지 않은 리소스가 있다면 나중에 다시 사용하지 않을 경우 릴리스를 고려할 수 있습니다 아니면 삭제 가능한 상태를 휘발성으로 설정할 수 있습니다 Metal 리소스가 속하는 세 가지 퍼지 가능 상태는 비휘발성, 휘발성, 비어 있음입니다 기본적으로 리소스는 비휘발성입니다 퍼지 가능 상태를 휘발성으로 설정하면 시스템에서 메모리 압력이 높을 경우 Metal이 메모리에서 리소스를 제거할 수 있습니다 리소스가 비어 있으면 시스템이 더 이상 게임 풋프린트에 청구하지 않습니다 게임에 리소스가 다시 필요하면 콘텐츠가 있는지 확인하고 필요할 때 다시 로드합니다 자주 사용하지 않는 리소스만 휘발성 사용을 고려해서 퍼지 가능 상태를 잘못 사용하지 않도록 하세요
모든 리소스에 대한 일반적인 참고 사항입니다 이제 텍스처를 더 자세히 살펴보겠습니다 모든 열이 기본적으로 Memory Viewer에 표시되지는 않습니다 테이블 헤더를 오른쪽 클릭하면 텍스처의 Pixel Format 같은 열을 표시하거나 숨길 수 있습니다 텍스처의 픽셀 형식을 최적화하면 다양한 텍스처 효과를 얻을 수 있죠 메모리 사용과 대역폭을 줄이기 위해 게임의 다양한 텍스처를 16비트 반정밀 픽셀 형식으로 사용할 수 있습니다 단일 알파 구성요소가 있는 텍스처가 필요한 경우 다중 색상 채널을 사용하지 않을 수 있습니다 마지막으로 일부 읽기 전용 텍스처는 메모리 사용을 줄이기 위해 블록 압축을 사용할 수 있습니다 블록 압축 픽셀 형식에는 ASTC와 BC 등의 옵션이 있습니다 A15 Bionic부터는 텍스처와 렌더링 대상에 손실 압축을 사용해 최대한 품질을 유지하면서 메모리를 절약할 수 있습니다 자세한 내용은 이전 영상을 참고해 주세요
Memory Viewer를 사용하면 이런 메모리 용량 절약 효과를 빠르게 확인할 수 있습니다 게임의 최적화를 위해 사용할 수 있는 추가 기술이 몇 가지 더 있습니다 텍스처가 단일 패스로만 사용되면 저장 모드를 메모리리스로 설정해서 메모리와 대역폭을 절약할 수 있습니다 메모리리스 텍스처는 깊이, 스텐실, 다중 샘플 텍스처 같은 임시 렌더링 대상에 적합합니다 그렇지 않고 텍스처가 GPU에서만 사용되는 경우 저장 모드를 비공개 공유 또는 관리로 설정할 수 있습니다 iPhone과 iPad처럼 Apple silicon Macs에서는 관리 모드가 필요하지 않습니다 예를 보여드리겠습니다 게임의 텍스처가 Depth32Float_Stencil8입니다 깊이 텍스처가 패스에서 사용되고 스텐실 텍스처의 콘텐츠는 폐기되어 프레임에서 사용되지 않습니다 대신 게임은 메모리와 대역폭을 절약하기 위해 두 개의 텍스처를 사용하고 스텐실 텍스처 메모리리스를 만듭니다
마지막으로 게임에서 메모리를 최대한 활용할 수 있는 또 다른 기술을 알려드리겠습니다 게임에서 앨리어스 리소스를 동시에 사용하지 않으면 힙 리소스를 사용할 수 있습니다 동일한 할당으로 지원되는 메모리를 공유할 수 있죠 하지만 이런 리소스에 대한 액세스를 동기화할 때는 특히 주의해야 합니다 ‘Go bindless with Metal 3’ 강연을 통해 힙에서 할당된 리소스의 사용 방법을 자세히 알아보세요 이것으로 메모리 절약에 대한 체크리스트를 마치겠습니다 게임의 Metal 리소스를 조사하는 데 도움이 되기를 바랍니다
게임 메모리 최적화를 위한 Metal Debugger 사용 방법은 다음 WWDC 강연에서 확인해 주세요 Jack, 다시 나와 주세요
감사합니다, Seth 오늘 가이드 투어를 통해 게임의 메모리 사용을 이해하고 개선하기 위해 할 수 있는 여러 흥미로운 활동을 살펴봤습니다 우선 메모리 풋프린트는 게임의 메모리 사용을 이해하는 데 필요한 주요 지표로 더티, 압축, 스와핑 메모리를 포함합니다 그리고 강력한 메모리 디버깅 도구가 있습니다 Seth가 Instruments를 이용해 원격 측정 트랙으로 메모리 프로파일링을 강화하는 방법을 보여줬죠 새로운 Game Memory 템플릿은 이 작업을 위해 만들어졌습니다 그리고 메모리 그래프로 게임 메모리 상태 스냅샷을 저장할 수 있습니다 유연하고 강력한 명령줄 프로그램으로 개체, 참조, 할당 기록에 대한 메모리 그래프를 분석할 수도 있죠 새로워진 힙 도구와 Xcode Memory Debugger가 강력한 게임 메모리 분석을 지원합니다 마지막으로 Seth가 Metal 리소스에 대한 메모리 절약 체크리스트와 Metal Debugger를 사용해 게임의 Metal 리소스를 해결하는 방법에 대해 설명했습니다 더 자세한 내용은 또 다른 WWDC 세션과 설명서, 매뉴얼 페이지에서 확인할 수 있습니다
저희는 가장 훌륭하고 유연한 도구를 계속해서 발전시키고 있습니다 어서 사용해 보세요 여러분이 찾고 있는 바로 그 도구일 수 있습니다
피드백이 있으신 분은 언제든지 Feedback Assistant 같은 채널로 공유해 주세요 즐거운 메모리 여정이 되셨기를 바라며 시청해 주셔서 감사합니다
-
-
6:53 - Available memory for the process
#import <os/proc.h> API_UNAVAILABLE(macos) API_AVAILABLE(ios(13.0), tvos(13.0), watchos(6.0)) size_t os_proc_available_memory(void);
-
7:07 - Current and peak footprint
#if __has_include(<libproc.h>) #include <libproc.h> // On macOS. #else #include <sys/resource.h> // On iOS, iPadOS and tvOS. int proc_pid_rusage(int pid, int flavor, rusage_info_t *buffer) __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); #endif rusage_info_current rusage_payload; int ret = proc_pid_rusage(getpid(), RUSAGE_INFO_CURRENT, // I.e., new RUSAGE_INFO_V6 this year. (rusage_info_t *)&rusage_payload); NSCAssert(ret == 0, @"Could not get rusage: %i.", errno); // Look up in `man errno`. uint64_t footprint = rusage_payload.ri_phys_footprint; uint64_t footprint_peak = rusage_payload.ri_lifetime_max_phys_footprint;
-
10:04 - Record an Instruments trace
xctrace record --template "Game Memory" \ --attach ModernRenderer \ --output ModernRenderer.trace \ --time-limit 30s
-
10:14 - Record an Instruments trace, on a selected device
xctrace record --device-name "Seth's iPhone" \ --template "Game Memory" \ --attach ModernRenderer \ --output ModernRenderer.trace \ --time-limit 30s
-
16:52 - MallocStackLogging
# See `man malloc`. MallocStackLogging=lite # Live allocations only. MallocStackLogging=1 # All allocation and free history.
-
18:07 - Capture a memory graph
leaks $PID --outputGraph foo.memgraph # or leaks GameName --outputGraph foo.memgraph
-
20:12 - Tag mapped anonymous memory
size_t length; int tag = VM_MAKE_TAG(VM_MEMORY_APPLICATION_SPECIFIC_1); // Check out `man mmap`. void * reservation = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, tag, // Instead of using default `-1`. 0); if (reservation == MAP_FAILED) { @throw [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; } return reservation;
-
20:30 - Tag anonymous memory
size_t page_count; mach_vm_size_t allocation_size = page_count * PAGE_SIZE; mach_vm_address_t vm_address; kern_return_t kr; kr = mach_vm_allocate(mach_task_self(), &vm_address, allocation_size, VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_APPLICATION_SPECIFIC_1)); if (kr != KERN_SUCCESS) { // Refer to mach/kern_return.h. @throw [[NSError alloc] initWithDomain:NSMachErrorDomain code:kr userInfo:nil]; } return vm_address;
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.