[Unity] Unity Performance Tuning Bible 훑어보기 (6)

ChangBeom·2024년 7월 30일

[3.6 Xcode]

Xcode는 애플이 제공하는 통합 개발 환경 도구이다. Unity에서 타깃 플랫폼을 iOS로 설정하면, 그 빌드 결과물이 Xcode 프로젝트가 된다. Unity에서 측정하는 것보다 더 정확한 수치를 얻을 수 있기 때문에 엄격한 검증을 할 때는 Xcode를 사용하는 것을 권장한다. 이번 절에서는 Debug Navigator, GPU Frame Capture, Memory Graph의 세가지 프로파일링 도구에 대해 알아보자.

[3.6.1 프로파일링 방법]

 Xcode에서 프로파일링하는 방법은 두 가지가 있다. 첫 번째는 Xcode에서 직접 단말기에 빌드하여 애플리케이션을 실행하는 방법이다. 아래 그림과 같이 실행 버튼을 누르기만 하면 프로파일링이 시작된다.

두 번째는 실행 중인 애플리케이션을 Xcode의 디버거에 첨부하는 방법이다. 이는 앱을 실행한 후 Xcode 메뉴의 'Debug -> Attach to Process'에서 실행 중인 프로세스를 선택하면 프로파일을 생성할 수 있다. 단, 빌드 시 인증서가 개발자용 (Apple Development) 이어야 한다. Ad Hoc이나 Enterprise 인증서로는 첨부할 수 없으니 주의하자.

[3.6.2 Debug Navigator]

Debug Navigator는 Xcode에서 애플리케이션을 실행하기만 하면 CPU, Memory등의 디버그 게이지를 확인할 수 있다. 애플리케이션 실행 후 아래 그림의 스프레이 마크를 누르면 6가지 항목이 표시된다. 또는 Xcode 메뉴의 'View -> Navigators -> Debug'에서 열 수도 있다. 아래에서는 각 항목에 대해 설명한다.

<CPU 게이지>
 CPU를 얼마나 사용하고 있는지 확인할 수 있다. 또한 각 스레드별 사용률도 확인할 수 있다.

<Memory 게이지>
 메모리 사용량의 개요를 볼 수 있다. 내역 등 세부적인 분석은 불가능하다.

<Energy 게이지>
 전력 소비에 대한 개요를 볼 수 있다. CPU, GPU, Network 등의 사용량 현황을 파악할 수 있다.

<Disk 게이지>
  File I/O의 개요를 확인할 수 있다. 예기치 않은 타이밍에 파일 읽기/쓰기가 이루어지고 있지 않은지 확인하는데 도움이 된다.

<Network 게이지>
 네트워크 통신의 개요를 확인할 수 있다. Disk와 마찬가지로 예기치 않은 통신을 하고 있지는 않은지 등을 체크하는 데 도움이 될 것이다.

<FPS 게이지>
 이 게이지는 기본적으로 표시되지 않는다. <3.6.3 GPU Frame Capture>에서 설명하는 GPU Frame Capture를 활성화하면 표시된다. FPS는 물론 셰이더 스테이지의 사용률, 각 CPU와 GPU의 처리 시간을 확인할 수 있다.

[3.6.3 GPU Frame Capture]

GPU Frame Capture는 Xcode에서 프레임 디버깅을 할 수 있는 툴이다. Unity의 Frame Debugger와 마찬가지로 렌더링이 완료되기까지의 과정을 확인할 수 있다. 유니티에 비해 셰이더의 각 단계별 정보량이 많기 때문에 병목현상 분석 및 개선에 도움이 된다. 아래에서는 사용법을 설명한다.

<준비>
Xcode에서 GPU Frame Capture를 활성화하기 위해서는 스키마 편집이 필요하다. 먼저 'Product -> Scheme -> Edit Scheme'에서 스키마 편집 화면을 연다.

 다음으로 'Options'탭에서 GPU Frame Capture를 'Metal'로 변경한다.

 마지막으로 'Diagnostics'탭에서 Metal의 'Api Validation'을 활성화한다.

<캡처>
실행 중 디버그 바에서 카메라 마크를 누르면 캡처가 이루어진다. 장면의 복잡도에 따라 처음 캡처하는 데 시간이 걸리므로 조금만 기다리자. 참고로 Xcode13 버전 이후에서는 Metal 아이콘으로 변경되었다.

 캡처가 완료되면 다음과 같은 요약 화면이 표시된다.

 이 요약 화면에서는 드로잉의 종속성, 메모리 등의 세부 정보를 확인할 수 있는 화면으로 전환할 수 있다. 또한 Navigator 영역에는 드로잉에 관한 명령어 표시가 되어있다. 이 표시 방법은 'View Frame By Call'과 'View Frame By Pipeline State'가 있다.

By Call 표시에서는 모든 드로잉 명령이 호출된 순서대로 정렬된다. 이는 드로잉의 사전 준비가 되는 버퍼의 설정 등도 포함되기 때문에 매우 많은 명령이 나열된다. 반면 By Pipeline State는 각 셰이더에서 그려진 지오메트리에 대한 드로잉 명령만 나열된다. 어떤 것을 조사할 것인지에 따라 표시를 전환하는 것이 좋다.

Navigator 영역에 나열된 드로잉 명령어를 누르면 해당 명령어에 사용된 프로퍼티 정보를 확인할 수 있다. 프로퍼티 정보에는 텍스쳐, 버퍼, 샘플러, 셰이더 함수, 지오메트리 등이 있다. 각 프로퍼티는 더블클릭을 통해 세부 정보를 확인할 수 있다. 예를 들어, 셰이더 코드 자체나 샘플러가 Repeat인지 Clamp인지까지 파악할 수 있다.

 지오메트리 프로퍼티는 버텍스 정보가 테이블 형태로 표시될 뿐만 아니라, 카메라를 움직여 형상을 확인할 수도 있다.

 다음으로 요약 화면의 Performance란에 있는 'Profile'에 대해 설명한다. 이 버튼을 누르면보다 세밀한 분석을 시작한다. 분석이 끝나면 도면에 소요된 시간이 Navigator 영역에 표시된다.

뿐만 아니라 분석 결과를 Counters라는 화면에서 보다 상세하게 확인할 수 있다. 이 화면에서는 각 도면의 Vertex, Rasterized, Fragment 등의 처리 시간을 그래픽으로 확인할 수 있다.

다음으로 요약 화면의 Memory 항목에 있는 'Show Memory'에 대해 설명한다. 이 버튼을 누르면 GPU에서 사용하고 있는 리소스를 확인할 수 있는 화면으로 전환된다. 표시되는 정보는 주로 텍스처와 버퍼이다. 불필요한 것이 없는지 확인하는 것이 좋다.

 마지막으로 요약 화면의 Overview에 있는 'Show Dependencies'에 대해 설명한다. 이 버튼을 누르면 각 렌더패스의 종속성을 확인할 수 있다. Dependency를 살펴볼 때 '화살표가 바깥쪽을 향하고 있는 버튼을 누르면 해당 계층 이하의 종속성을 더 열어볼 수 있다.

 이 화면은 어떤 드로잉이 무엇에 의존하고 있는지 확인하고 싶을 때 사용한다.

[3.6.4 Memory Graph]

이 도구는 캡처 타이밍의 메모리 상황을 분석할 수 있다. 왼쪽의 Navigator영역에는 인스턴스가 표시되며, 해당 인스턴스를 선택하면 참조 관계가 그래프로 표시된다. 오른쪽의 Inspector영역에는 해당 인스턴스의 상세 정보가 표시된다.

 이 도구는 플러그인 등 Unity에서 측정할 수 없는 오브젝트의 메모리 사용량을 조사할 때 사용하면 좋다. 아래에서는 사용 방법을 설명한다.

<사전 준비>
Memory Graph에서 유용한 정보를 얻기 위해서는 Scheme를 편집해야 한다. 'Product -> Scheme -> Edit Scheme'에서 Scheme 편집 화면을 연다. 그리고 'Diagnostics'탭에서 'Malloc Stack Logging'을 활성화한다.

 이를 활성화하면 Inspector에 BackTrace가 표시되어 어떤 흐름으로 할당되었는지 알 수 있다.

<캡처하기>
애플리케이션 실행 중 디버그 바에서 branch모양의 아이콘을 누르면 캡처가 이루어진다.

또한 Memory Graph는 'File -> Export MemoryGraph'에서 파일로 저장할 수 있다. 이 파일에 대해 vmmap 명령이나 heap 명령, malloc_history 명령어를 활용하여 더 깊이 있는 조사를 할 수 있다. vmmap명령의 Summary표시를 예로 들어보면, MemoryGraph에서는 파악하기 어려웠던 전체 그림을 파악할 수 있다.

<vmmap summary 명령어>
vmmap --summary hoge.memgraph

[3.7 Instruments]

Xcode에는 Instruments라는 세부적인 측정 및 분석에 특화된 도구가 있다. Instruments는 'Product -> Analyze'를 선택하면 빌드가 시작된다. 완료되면 다음과 같은 계측 항목의 템플릿을 선택할 수 있는 화면이 열린다.

 많은 템플릿에서 알 수 있듯이 Instruments는 다양한 내용을 분석할 수 있다. 이번 절에서는 이 중에서 특히 활용 빈도가 높은 'Time Profiler'와 'Allocations'에 대해 알아보자.

[3.7.1 Time Profiler]

 Time Profiler는 코드의 실행 시간을 측정하는 도구이다. Unity Profiler에 있던 CPU모듈과 마찬가지로 처리 시간을 개선할 때 사용한다.

 측정을 시작하려면 Time Profiler의 툴바에 있는 빨간색 동그라미 모양의 레코드 버튼을 눌러야한다.

 계측을 시작하면 아래 그림과 같은 화면이 나타난다.

Unity Profiler와 달리 프레임 단위가 아닌 구간 단위로 분석을 진행한다. 하단의 Tree View에는 구간 내 처리 시간이 표시된다. 게임 로직의 처리 시간을 최적화할 경우, Tree View의 PlayerLoop이하의 처리를 분석하는 것이 좋다.

Tree View의 표시를 보기 쉽게 하기 위해 Xcode
하단에 있는 Call Trees 설정을 아래 그림과 같이 설정해두면 좋다.
특히 Hide System Libraries에 체크하면, 손이 닿지 않는 시스템 코드가 숨겨져 조사를 쉽게 할 수 있다.

 이렇게 처리 시간을 분석하고 최적화할 수 있다.

Time Profiler에서는 유니티 프로파일러와 기호 이름이 다르다. 크게 다르지는 않지만 '클래스명함수명임의의 문자열'이라는 표기가 된다.

[3.7.2 Allocations]

Allocations는 메모리 사용량을 측정하기 위한 도구이다. 메모리 누수나 사용량을 개선할 때 사용한다.

 측정을 하기 전에 'File -> Recording Options'를 열고 'Discard events for freed mem-ory'를 체크한다.

 이 옵션을 활성화하면 메모리가 해제되었을 때 기록을 파기한다.

 위 그림을 보면 알 수 있듯이 옵션 유무에 따라 외형이 크게 달라진다. 옵션이 있는 경우, 메모리가 할당된 시점에서만 선이 기록된다. 또한 기록된 라인은 그 확보된 영역이 해제되면 파기된다. 즉, 이 옵션을 설정하면 선이 계속 남아 있으면 메모리에서 해제되지 않는다는 뜻이다. 예를 들어, 장면 전환을 통해 메모리를 해제하는 설계의 경우, 전환 전 장면 구간에 선이 많이 남아있다면 메모리 누수를 의심해 볼 수 있다. 이 경우 Tree View로 세부 사항을 추적해 보자.

 화면 하단의 Tree View는 Time Profiler와 마찬가지로 지정한 범위의 세부 정보가 표시된다. 이 Tree View의 표시 방법은 4가지가 있다.

가장 추천하는 표시 방법은 Call Trees이다. 이 표시 방법을 사용하면 어떤 코드에 의해 할당이 발생했는지 추적할 수 있다. 화면 하단에 Call Trees표시 옵션이 있으므로, Time Profiler에서 소개한 Call Trees설정 처럼 Hide System Libraries 등의 옵션을 설정하면 된다. 아래 그림은 Call Tree의 캡처이다. 12.05MB의 할당이 SampleScript의 OnClicked에서 발생하고 있음을 알 수 있다.

 마지막으로 Generations라는 기능을 소개한다. Xcode 하단에 'Mark Generations'라는 버튼이 있다.

 이 버튼을 누르면 해당 타이밍의 메모리가 기억된다. 이후 다시 Mark Generations를 누르면 이전 데이터와 비교하여 새롭게 확보된 메모리 양이 기록된다.

 위 그림의 각 Generations는 세부 내용을 보면 Call Tree형태로 표시되므로, 메모리 증가가 무엇에 의해 발생했는지 추적할 수 있다.

[3.8 안드로이드 스튜디오]

 안드로이드 스튜디오는 안드로이드의 통합 개발 환경 도구이다. 이 도구를 이용해 애플리케이션의 상태를 측정할 수 있다. 프로파일링 가능한 항목은 CPU, Memory, Network, Energy의 4가지이다. 이번 절에서는 먼저 프로파일링 방법을 소개하고, 이후 CPU와 Memory의 측정 항목에 대해 설명한다.

[3.8.1 프로파일 방법]

 프로파일링 방법은 두 가지가 있습니다. 첫 번째는 Android Studio를 통해 빌드하고 프로파일링하는 방법이다. 이 방법은 먼저 Android Studio의 프로젝트를 Unity에서 Export해야한다. Build Settings에서 'Export Project'를 체크하여 빌드하자.

 다음으로 내보낸 프로젝트를 Android Studio에서 연다. 그리고 안드로이드 단말기를 연결한 상태에서 오른쪽 상단에 있는 게이지 모양의 아이콘을 누르면 빌드가 시작된다. 빌드가 완료되면 앱이 실행되고 프로파일이 시작된다.

두 번째는 실행 중인 프로세스를 디버거에 연결하여 측정하는 방법이다. 먼저 Android Studio 메뉴의 'View -> Tool Windows -> Profiler'에서 Android Profiler를 연다.

 다음으로 열린 Profiler의 SESSIONS에서 측정할 세션을 선택한다. 세션을 연결하기 위해 측정하고자 하는 애플리케이션이 실행되어 있어야 한다. 또한 Development Build를 적용한 바이너리여야 한다. 세션 연결이 완료되면 프로파일이 시작된다.

 두 번째 디버거에 연결하는 방법은 프로젝트를 내보낼 필요 없이 간편하게 이용할 수 있으므로 기억해두면 좋다.

엄밀히 말하면 Unity의 Development Build가 아닌 AndroidManifest.xml에서 debuggable과 profileable을 설정해야한다. Unity에서는 Development Build를 하면 자동으로 debuggable이 true로 설정된다.

[3.8.2 CPU 측정]

 CPU 계측 화면은 아래 그림과 같다. 이 화면만으로는 무엇이 얼마나 많은 처리 시간을 소비하고 있는지 알 수 없다. 좀 더 자세히 보기 위해서는 자세히 보고 싶은 스레드를 선택해야 한다.

 스레드 선택 후 Record 버튼을 누르면 해당 스레드의 콜스택이 측정된다. 아래 그림과 같이 몇 가지 측정 유형이 있지만 'Calltack Sample Recording'으로도 충분하다.

 Stop버튼을 누르면 측정이 종료되고 결과가 표시된다. 결과 화면은 Unity Profiler의 CPU 모듈과 같은 형태로 표시된다.

[3.8.3 Memory 측정]

 Memory 계측 화면은 아래 그림과 같다. 이 화면에서는 메모리 내역을 확인할 수 없다.

 메모리 내역을 확인하려면 추가로 계측을 해야한다. 측정 방법은 3가지가 있다. 'Capture heap dump'는 누른 타이밍의 메모리 정보를 얻을 수 있다. 그 외의 버튼은 측정 구간 중 할당량을 분석하기 위한 버튼이다.

 예시로 아래 그림은 Heap Dump의 측정 결과 캡처이다. 세부적으로 분석하기에는 조금 입도가 거칠기 때문에 난이도가 높은 편이다.

[3.9 RenderDoc]

RenderDoc는 오픈소스로 개발되어 무료로 사용할 수 있는 고품질 그래픽 디버거 도구이다. 이 도구는 현재 Windows와 Linux만 지원하고 Mac은 지원하지 않는다. 또한 지원하는 Graphics API는 Vulkan, OpenGL(ES), D3D11, D3D12 등이다. 따라서 안드로이드에서는 사용할 수 있지만 iOS에서는 사용할 수 없다.

 이번 절에서는 안드로이드 애플리케이션을 실제로 프로파일링해본다. 단, 안드로이드 프로파일링에는 몇 가지 제약이 있으니 주의해야한다. 우선 안드로이드 OS버전은 6.0 이상이 필수이다. 그리고 측정 대상 애플리케이션은 Debuggable을 활성화해야 한다. 이는 빌드 시 Development Build를 선택하면 문제가 없다. 참고로 프로파일에 사용한 RenderDoc은 v1.18 버전이다.

[3.9.1 측정 방법]

 먼저 RenderDoc를 준비한다. 공식 사이트(https://renderdoc.org/)에서 설치 프로그램을 다운로드하여 툴을 설치한다. 설치 후 RenderDoc 툴을 연다.

 다음으로 안드로이드 기기를 RenderDoc와 연결한다. 왼쪽 하단에 있는 집 표시를 누르면 PC와 연결된 기기 목록이 표시된다. 목록에서 측정하고자 하는 단말기를 선택해야한다.

 다음으로 연결된 기기에서 실행할 애플리케이션을 선택한다. 오른쪽에 있는 탭에서 Launch Application을 선택하고, Executable Path에서 실행할 앱을 선택한다.

 File Browser창이 열리면, 이번에 측정할 Package Name을 찾아 Activity를 선택한다.

 마지막으로 Launch Application에서 Launch버튼을 누르면 디바이스에서 애플리케이션이 실행된다. 또한 RenderDoc상에서 계측을 위한 탭이 새롭게 추가된다.

 테스트로 Capture Frame(s) Immediately를 누르면 프레임 데이터가 캡처되고, Capture Collected에 나온다. 이 데이터를 더블 클릭하면 캡처 데이터를 열 수 있다.

[3.9.2 캡처 데이터 보는 방법]

 RenderDoc에는 다양한 기능이 있지만, 이번 절에서는 중요한 부분만 추려서 기능을 설명한다. 먼저 화면 상단에는 캡처한 프레임의 타임라인이 표시된다. 각각의 렌더링 명령이 어떤 순서로 이루어졌는지 시각적으로 파악할 수 있다.

 다음으로 Event Browser이다. 여기에는 각 명령이 위에서부터 순서대로 나열되어 있다.

Event Browser내 상단에 있는 '시계 표시'를 누르면 각 명령의 처리 시간이 'Duration'에 표시된다. 측정 타이밍에 따라 처리 시간이 달라지므로, 참고만 하면 된다. 또한 DrawOpaqueObjects의 명령어 내역을 보면 3개가 Batch로 처리되고 있고, 1개만 Batch에서 벗어난 드로잉을 하고 있는 것을 확인할 수 있다.

 다음으로 오른쪽에 탭으로 나열된 각 창에 대해 설명한다. 이 탭 안에는 Event Browser에서 선택한 명령의 상세 정보를 확인할 수 있는 창이 있다. 특히 중요한 것은 Mesh Viewer, Texture Viewer, Pipeline State 세 가지이다.

 먼저 Pipeline State에 대해 알아보자. Pipeline State는 해당 오브젝트가 화면에 그려지기까지 각 셰이더 단계에서 어떤 파라미터를 사용했는지 확인할 수 있다. 또한 사용된 셰이더와 그 내용도 확인할 수 있다.

 파이프라인 스테이트에 표시되는 스테이지 이름은 생략되어 있으므로 정식 명칭을 아래 표로 정리한다.

스테이지 이름정식명
VTXVertex Input
VSVertex Shader
TCSTessellation Control Shader
TES테셀레이션 평가 셰이더
GS지오메트리 셰이더
RSRasterizer
FSFragment Shader
FB프레임 버퍼
CSCompute Shader

 위 그림에서는 VTX스테이지가 선택되어 있으며, 토폴로지 및 버텍스 입력 데이터를 확인할 수 있다. 이 외에도 아래 그림의 FB스테이지에서는 출력되는 텍스쳐의 상태, Blend State 등의 세부 정보를 확인할 수 있다.

 또한 아래 그림의 FS스테이지에서는 프래그먼트 셰이더 내에서 사용된 텍스처와 파라미터를 확인할 수 있다.

 FS 스테이지 중앙의 Resources에는 사용된 텍스처와 샘플러가 표기되어 있다. 또한 FS 스테이지 하단에 있는 Uniform Buffers에는 CBuffer가 표시되어 있다. 이 CButter에는 float, color 등 수치 정보 속성이 저장되어 있다. 각 항목의 오른쪽에는 'Go'라는 화살표 아이콘이 있으며, 이를 누르면 데이터 세부 정보를 확인할 수 있다.

 FS스테이지 상단에는 사용한 셰이더가 표시되며, View를 누르면 해당 셰이더 코드를 확인할 수 있다. 표시를 쉽게 하기 위해 Disassembly type은 GLSL을 추천한다.

다음으로 Mesh Viewer이다. 이것은 Mesh의 정보를 시각적으로 볼 수 있어 최적화나 디버깅에 유용한 기능이다.

 Mesh Viewer의 상단에는 메시의 버텍스 정보가 테이블 형태로 표기되어 있다. 하단에는 카메라를 움직여 형상을 확인할 수 있는 미리보기 화면이 있다. 둘 다 In과 Out으로 탭이 나뉘어져 있어 변환 전후의 값과 모양이 어떻게 달라졌는지 파악할 수 있다.

 마지막으로 Texture Viewer이다. 이 화면에서는 Event Browser에서 선택한 명령의 '입력에 사용한 텍스쳐'와 '출력 결과'를 확인할 수 있다.

화면 오른쪽 영역에서는 입출력의 텍스쳐를 확인할 수 있다. 표시된 텍스쳐를 누르면 화면 왼쪽 영역에 반영된다. 왼쪽의 화면은 단순히 표시만 하는 것이 아니라, 컬러 채널을 필터링하거나 툴바의 설정을 적용할 수 있다.

 위로 2번째 그림에서는 Overlay에 'Wireframe Mesh'를 선택했기 때문에 이 명령으로 그려진 오브젝트에 노란색 와이어프레임 표시가 되어 있어 시각적으로 알기 쉽게 되어있는 것이다.

 또한 Texture Viewer에는 Pixel Context라는 기능이 있다. 이것은 선택한 픽셀의 그리기 히스토리를 볼 수 있는 기능이다. 히스토리를 알 수 있다는 것은 어떤 픽셀에 대해 얼마나 자주 채우기가 이루어졌는지 파악할 수 있다. 이는 오버드로우 조사나 최적화를 할 때 유용한 기능이다. 단, 픽셀 단위이기 때문에 전체적인 오버드로우를 조사하기에는 적합하지 않는다. 조사 방법은 위로 2번째 그림의 왼쪽 화면에서 조사하고 싶은 부분을 마우스 오른쪽 버튼으로 클릭하면 Pixel Context에 그 위치가 반영된다.

 다음으로 Pixel Context의 History 버튼을 누르면 해당 픽셀의 그리기 이력을 확인할 수 있다.

 위 그림에는 4개의 히스토리가 존재한다. 초록색 라인은 심도 테스트 등 파이프라인의 테스트를 모두 통과하여 렌더링되었음을 나타낸다. 일부 테스트에 실패하여 그려지지 않은 경우 빨간색으로 표시된다. 캡처한 이미지에서는 화면 지우기 처리와 캡슐 그리기가 성공했고, Plane과 Skybox가 깊이 테스트에 실패한 것을 확인할 수 있다.

0개의 댓글