728x90
이 글은 Youtube - Unity Korea 영상을 보고 정리한 글입니다.

 

 

목차.

  1. 개요
  2. 병목이란?
  3. 드로우콜(Draw Call)
  4. 렌더 스테이트(Render States)
  5. 커멘드 버퍼(Command Buffer)
  6. 메모리(Memory)

 

개요

 

최적화는 게임 개발하면서 가장 중요하다고 할 정도입니다.

최적화에 관련된 이야기에서 빼놓을 수 없는 것이 바로 병목을 찾는 것입니다.

 

프로파일링을 통해서 문제점을 찾아야 하고,

이 프로파일링을 근거를 통해 최적화를 진행하는 것이 가장 일반적이고 확실한 최적화 방법입니다.

 

하지만 많은 사람들이 이 병목에 대한 문제를 생각하지 못하고 텍스쳐 사이즈 줄이기와 같은 행동을 하고 있습니다.

이번에 최적화를 위해 어떤 것을 신경 써야 하고 어떻게 해야 하는지에 대해 하나하나 정리해 보겠습니다.

 

병목이란?

개요에서 병목이라는 단어를 사용했는데 병목이 무엇일까요?

병목은 이름 그대로 병의 목이라는 뜻으로 데이터 흐름이 일어날 때 막힘없이 진행되어야 하지만,

병의 목에 걸리는 것처럼 전체적인 파이프라인이 막히고 포퍼먼스가 저하되는 현상을 병목현상이라고 합니다.

 

병목-예시-사진
병목 예시 사진

 

그렇다면 이런 병목현상이 왜 일어나는지, 그리고 일어난다면 어떻게 이 문제를 개선해야 하는지 알아보겠습니다.

 

드로우콜(Draw Call)

 

만약 병목현상이 발생했다면 CPU bound와 GPU bound 둘 중 무엇이 원인인지 찾아야 합니다.

알려드리기 앞서 드로우콜에 대해 먼저 설명드리겠습니다.

 

드로우 콜이란 CPU가 GPU에게 렌더링을 하라고 명령을 하는 것입니다.

명령을 줄 때 OpenGL이나 DirectX를 통해서 명령을 주게 되는데,

 

화면에 오브젝트를 그릴 때 눈으로 볼 때는 두 오브젝트가 동시에 그려지는 것 같지만,

 

유니티의-드로우콜
유니티의 드로우콜

 

실제로는 백그라운드, 부엉이, 플레이어 이런 식으로 한 프레임을 그릴 때 순차적으로 그려서 우리 눈에 보이는 것입니다.

 

오브젝트를 하나 그릴 때마다 GPU 메모리메쉬 데이터, 텍스쳐 데이터, 쉐이더 데이터와 같은 정보들이 들어있고 이것을 조합해서 부엉이를 그리고 플레이어도 같은 과정을 거쳐 그림을 그리고 최종적으로 화면에 보여줍니다.

 

렌더 스테이트(Render States)

 

GPU에 사용하는 메모리에 여러 가지 데이터들이 들어가 있습니다.

그것을 연결시켜 주는 것이 렌더 스테이트인데,

 

렌더-스테이트
렌더 스테이트

 

렌더링을 할 때 렌더 루프를 돌게 되고 렌더 루프가 시작되면

 

알파블렌딩 설정-> Z-Test 설정 -> 쉐이더 설정 -> 쉐이더 상수 -> 메시 설정 -> 텍스쳐 설정

 

다음 Draw와 같은 과정을 거치고 렌더 루프가 끝나게 됩니다.

 

이러한 상태 변경 하나하나가 다 DirectX나 OpenGL 입장에서 보면 전부 커멘드가 되는 것입니다.

ex) 메시설정(하나의 커멘드)...

 

커멘드 버퍼(Command Buffer)

 

CPU에서 GPU에게 커멘드(명령)를 줄 때 커멘드 버퍼라는 버퍼를 통해서 명령을 주게 됩니다.

 

커멘드-버퍼-작동-구조
Command Buffer를 통해서 CPU가 GPU에게 명령을 전달

 

커멘드 버퍼는 FIFO(First-In-First-Out) 개념으로 큐의 구조를 가지고 있습니다.

가장 먼저 들어온 커멘드를 처리하게 됩니다.

 

여기서 많은 사람들이 착각하는 것이 CPU가 일처리를 하다가 GPU에게 명령을 주고 GPU가 수행이 끝날 때까지 기다리다가 다시 CPU가 일 처리를 진행한다고 생각합니다.

 

CPU와-GPU
CPU 일처리중 GPU가 일처리할때 기다리다가 끝나면 다시 일하는모습

사실 CPU와 GPU는 병렬적으로 돌아갑니다.

 

CPU가 일을 하다가 그려야 할 것이 생기면 Draw 명령 또는 상태변경과 같은 명령을 GPU에게 던집니다.

그동안 CPU는 기다리는 것이 아니라 자기 할 일을 계속하게 됩니다.

중간에 커멘드 버퍼가 있어서 GPU는 커멘드 버퍼에서 순차적으로 일을 가져가게 됩니다(큐).

 

실제-CPU와-GPU-작동
CPU와 GPU가 병렬적으로 작동하는 모습

 

위 사진은 GPU가 CPU에 비해서 한가한 상태입니다.

반대로 상황에 따라 CPU가 한가한 상태가 될 수 있습니다.

 

만약 CPU가 한가한 상태가 되면 프로파일러로 봤을 때 Graphics.PresentAndSync 또는 Gfx.WaitForPresent 이런 식으로 GPU가 표현하기를 기다리는 상태라는 키워드가 보이게 됩니다.

 

여기서 문제는 유티니 버전마다 키워드가 조금씩 다르다는 것입니다.

 

어쨌든 CPU가 집중적으로 작업을 하고 있으면 CPU bound, 반대로 GPU가 집중적으로 작업을 하고 있으면 GPU bound입니다.

병목현상이 발생하면 두 bound 중 무엇이 원인인지 확인하는 게 첫 번째입니다.

 

무엇이 바쁘고 한가한 상태인지는 프로파일러를 통해서 확인할 수 있습니다.

 

이제 그다음으로 중요한 것은 메모리입니다.

 

메모리(Memory)

 

우리가 쓰는 모든 운영체제는 가상메모리라는 개념을 사용하고 있습니다.

 

프로그래밍을 할 때 하드의 주소를 직접 쓰는 게 아니고 가상메모리라는 형태를 통해서 os가 한 겹 맵핑한 주소를 받아서 사용합니다.

 

이렇게 하는 이유는 게임을 만들어서 데이터를 로드한다고 하면 하드디스크에 저장되어 있는 것은 CPU 메모리에 올라가게 됩니다.

만약 쉐이더, 텍스쳐 등 렌더링이 필요한 데이터라면 GPU에서 사용하는 메모리에 올라가게 됩니다.

 

메모리-작동-구조
메모리의 작동 구조

 

 

8G 메모리가 한계인데 16G를 사용하게 되면,

CPU에 있는 메모리가 하드디스크(저장장치) 일정 부분을 메모리로 활용하게 됩니다.

그런 부분을 Swap Page라고 합니다.

 

Swap-Page-작동-원리
CPU 메모리가 부족하다면 하드디스크 일부 공간을 가상메모리로 사용

 

실제 하드의 메모리뿐만 아니고 하드디스크 일부 공간을 가상 메모리로 사용하기 때문에 이런 메모리시스템을 운영체제가 맵핑하는 방식으로 제공해 줍니다.

 

PC에서는 이런 방식으로 CPU 메모리에서 부족하다 하면 하드디스크 일부를 메모리처럼 활용하기 때문에 실제 메모리보다 훨씬 많이 사용할 수 있습니다.

 

하지만 이런 SwapPage이 자주 일어나면 병목현상이 일어난다는 문제가 있습니다.

 

마찬가지로 GPU 메모리를 사용하다 부족하면 CPU 메모리의 일정 부분을 활용합니다.

 

Swap-Page-작동-원리2
GPU 메모리가 부족하다면 CPU 메모리 일부 공간을 가상메모리로 사용

 

모바일의 경우에는 단일 칩 시스템으로 하나의 칩에 CPU, GPU 이런 것이 다 들어가 있습니다.

 

컴퓨터와 다른 점은 메모리가 4G짜리라면 컴퓨터와 다르게 진짜 4G만 쓸 수 있는데 

이 4G도 그대로 다 사용가능한 것이 아니라 게임이나 앱에서 쓸 수 있는 것은 극히 일부입니다.

 

그 이유는 전통적인 개념인 스왑페이지가 없기 때문입니다.

 

병목, 메모리 등 다양하게 알아봤는데

이 글의 핵심은 병목현상이 발생하는 원인과 발생했을 때 어떻게 원인을 찾아야 하는지,

그리고 전체적인 구조가 어떻게 되는지에 대해서 알아봤습니다.

728x90

+ Recent posts