본문 바로가기
학교/정리

GPUView

by ehei 2024. 5. 11.

GPU와 CPU 간의 상호 작용을 프로파일링할 수 있는 좋은 툴이 있다. 오래 전에 개발되었지만 여전히 필드에서 사용되는 바로 GPUView이다. 이 도구를 꽤 예전에 써보고 사용할 기회가 없어서 잊고 있었지만, 그걸 일깨워주는 기회가 있었다. 최근에 내가 맡은 일은 특정 게임이 왜 특정한 인텔 노트북에서 셔터링을 유발하는지에 대한 것이다. 셔터링은 보통 랙이라 불리는 현상으로 프레임이 단속적으로 끊어져 게임의 자연스러운 진행을 망치는 현상이다. 내부에서는 VerySleepy라는 도구로 분석했는데, 사실 이것은 유저 레벨의 프로파일링 도구로서 다른 프로세스 특히 GPU와의 상호 작용 같은 걸 볼 수는 없다. 오로지 대상 프로세스이 호출하는 함수 실행 시간을 측정할 뿐이다. 반면 인텔 측 내부 개발자는 다른 도구를 사용해서 병목 지점을 지적했는데, 이 때 사용한 것이 GPUView였다. 나도 담당이 된 만큼 그가 분석한 로그 파일을 살펴보고 곧 답신을 할 예정이다. 그와 관계 없이 이 도구에 대한 정리를 해볼 필요를 느꼈다. 이미 MSDN에 훌륭히 정리가 되어 있지만, 이제 와 내가 느낀 건 나 만의 노트가 필요하다는 사실이다. 그게 진짜 공책이든 블로그든 상관없이, 내 두뇌가 쉽게 받아들일 수 있는 형태면 상관없다. 이전에는 웹에 이미 레퍼런스가 있다는 이유 만으로 정리를 등한시 했다면, 이제는 나 만의 노트를 만들고 싶은 생각이 강해졌다. 서두가 길었다.

설치

일단 GPUView를 설치해보자. 터미널이나 명령 프롬프트를 열고 쉽게 설치할 수 있다. 최신 윈도우 10 이상인 경우 아래 방법으로 설치할 수 있다.

C:\Users\ehei2> winget install "Windows Performance Analyzer"

 

위 방법이 안된다면 Windows Store에서 내려받는 방법도 있다.

로깅

이제 설치가 되면 GPU View를 실행할 수 있다. 그 전에 로그를 얻어야 한다. 이 로그는 ETL 형식이다. Event Tracing Log, 즉 윈도우 내부에서 발생한 모든 이벤트를 추적하여 기록한다. 공교롭게도 로깅을 하는 수단은 GUI가 아니라 콘솔 밖에 없다. GPUView가 설치된 곳으로 이동한다. 참, 그 전에 터미널은 관리자 모드로 실행되어야 한다.

C:\Users\ehei2> where /r c:\ gpuview.exe
c:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\gpuview\GPUView.exe
C:\Users\ehei2> cd "c:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\gpuview"
C:\Users\ehei2> log.cmd

 

아마 로케일이 한글로 설치된 윈도우에서는 아래와 같은 오류가 날 것이다. 코드 페이지를 영어로 바꿔주고 다시 실행한다.

c:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\gpuview> log.cmd
4000은(는) 예상되지 않았습니다.
c:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\gpuview> chcp 437

Active code page: 437

c:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\gpuview> log.cmd

 

이제 로깅이 시작되었다. 다시 log.cmd를 입력해주면 로깅이 끝나고 후처리한다. 그리고 Merged.etl 파일이 생성된다.

c:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\gpuview> log.cmd
...
Merged Etl: Merged.etl
Output: c:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\gpuview\Merged.etl

예제 1: 셔터링 원인 찾기

OpenTTD를 시작할 때 매우 큰 맵을 생성하면 셔터링이 생긴다.이유를 살펴보자.

 

이걸 GPUView로 살펴보자. 위에 언급한대로 로깅하고 GPUView를 실행 한 후 Merged.etl 파일을 열어본다. 파싱 작업이 끝나면 다음과 같은 화면이 표시된다.

 

아래로 내리면 openttd.exe 프로세스를 발견할 수 있다

Device Context가 두 개 있는 것은 모니터가 2개이기 때문이다. 둘다 어느 순간에 DC 업데이트 간격이 꽤 벌어진다. 좀더 원인을 자세히 보기 위해 스레드가 표시된 곳으로 스크롤 다운한다.

각각의 상자는 태스크를 의미한다. 마우스로 선택 영역을 지정한다. 선택 영역을 확대(View > Zoom > Zoom to Selection: CTRL+Z)하면 더 자세히 확대된다. 살펴보면 메인 스레드가 아주 잠깐만 일하고 있음을 알 수 있다. 대신 다른 스레드의 작업이 분주하다. 이 스레드 15496이 무얼 하는지 살펴보자.

이제 무슨 일을 하는지 보려면 이벤트 리스트를 살펴봐야 한다. Tools > Event List(CTRL+E)를 연다.

 

Selection Time을 누르면 선택된 구간의 이벤트들만 필터링한다.우리는 openttd.exe의 메인 스레드인 TID 12804의 이벤트를 먼저 살펴보는 것이 맞을 것이다. Proc|ThreadId Filter에 15496를 입력하고 엔터키를 누른다. 특정 스레드의 이벤트들만 표시된다.

 

NT - Stack Walk가 호출 체인을 캡처한 것이다. 이 중 하나를 선택하면 아래처럼 표시된다. 심볼이 연결되지 않았기 때문이다.

Options > Symbol Path를 선택해서 OpenTTD 심볼 파일의 경로를 입력하고 심볼 서버도 사용한다고 체크하자. 그리고 OK를 누르면 심볼이 연결될 준비가 끝난다.

참고로 심볼 경로를 환경 변수에 설정하면 더욱 편리하다. 그러면 시스템 DLL의 심볼도 볼 수 있다. 심볼 설정에 대한 상세 정보는 아래 참조에 있다.

그래도 심볼이 잘 연결되지 않을 경우 미리 내려받는 방법도 있다. Meged.etl을 Windows Performance Analyzer로 열고, Load Symbols을 선택하면 심볼을 로컬 저장소에 내려받는다.

 

이제 다시 이벤트 리스트로 돌아가보자.

 

이제 심볼이 연결되어 함수 시그니처를 볼 수 있다.

 

다른 이벤트들을 보면 해당 스레드에서 타일을 만드는 작업을 매우 오래하고 있음을 알 수 있다. 그럼 타일을 만드는 동안 메인 스레드가 했던 일을 보자. SleepTileNextTick()을 짧게 수행했다.

 

그 후 컨텍스트 스위칭되었다.

 

그 다음은 어떤 일을 했는지 살펴보자. 뮤텍스를 체크한 다음

 

윈도우 이벤트를 처리하고 있다.

 

계속 이것이 반복된다. 즉 뮤텍스가 깨어나는 주기가 늦기 때문에 셔터링이 발생함을 짐작할 수 있다.

 

이제 코드를 살펴보자. 뮤텍스 위치를 찾아보자. Video_Driver::Tick() 부분을 살펴보자. 주석을 보면 게임 스레드가 작업 중인 경우 대기하는 것을 알 수 있다. .

void VideoDriver::Tick()
...
			/* Tell the game-thread to stop so we can have a go. */
			std::lock_guard<std::mutex> lock_wait(this->game_thread_wait_mutex);
			std::lock_guard<std::mutex> lock_state(this->game_state_mutex);
...
		this->Paint();

 

이제 게임 스레드를 살펴보자. 위에서 발견했던 맵을 생성하는 함수의 호출 체인을 따라가면 VideoDriver::GameLoop()가 있다. 즉 GameLoop() 내부에서 맵의 크기가 큰 경우 작업이 많아지고 이로 인해 뮤텍스의 해제가 늦어져 셔터링이 발생함을 알 수 있다.

void VideoDriver::GameLoop()
...
	{
		std::lock_guard<std::mutex> lock(this->game_state_mutex);

		::GameLoop();

 

예제 2: 그래프 보기

아래 그래프를 보자. 그래프를 조립하는 블록 각각을 살펴보자. 그물이 그려진 블록은 Present Packet으로 화면에 표시하게 한다.

nvoglv64.dll 스레드를 보자. 이것은 Nvidia 그래픽 드라이버이다. 좀더 확대해보자. 청색은 DirectX 드라이버에서 실행되고 있음을 뜻한다. 적색은 커널 모드 드라이버에서 실행되고 있음을 듯한다.

 

오렌지는 Memory Mapped IO(MMIO) Flip이다.

 

각각의 블록에 대한 좀더 자세한 설명은 WinHEC 2008 발표 자료 또는 Using GPUView to Understand your DirectX 11 Game 문서에 있다.

참조

GRA-T799_Blythe_Taiwan.pdf
2.46MB

 

  • Tools for Investigating Graphics System Performance

GPUView.ppt
4.67MB

 

  • Stuttering in Game Graphics: Detection and Solutions

Stuttering_Analysis_EN.pdf
0.67MB

 

  • Using GPUView to Understand your DirectX 11 Game

Using GPUView to Understand your DirectX 11 Game.pdf
16.04MB

'학교 > 정리' 카테고리의 다른 글

constexpr 선언 시 저장 위치  (0) 2024.05.30
OpenTTD 빌드  (0) 2024.05.12
윈도우 가상 머신 다운로드  (0) 2024.04.25