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

ChangBeom·2024년 7월 10일

[1.6 메모리를 줄이자]

메모리를 줄이는 포인트는 큰 부분부터 줄이는 것이다. 비용 대비 효과를 고려하여 큰 것부터 줄여나가는 것이 효율이 좋기 때문이다.
본 절에서 메모리 절감에 사용하는 도구는 Profiler(Memory)이다.

[1.6.1 Assets]

Simple View에서 Assets 관련 항목이 많은 경우, 불필요한 자산이나 메모리 누수 가능성이 있다. 이 경우 조사해야 할 사항은 세 가지이다.

  1. 불필요한 에셋 조사
    불필요한 에셋은 현재 씬에 전혀 필요하지 않은 리소스를 말한다. 예를 들면 타이틀 화면에서만 사용하는 BGM이 게임 내에서도 메모리에 상주하고 있는 경우가 있다. 우선은 현재 씬에 필요한 것들만 정리해보자.

  2. 중복 에셋 조사
    에셋 번들 대응을 할 때 자주 발생하는 문제이다. 에셋 번들의 종속성을 잘 구분하지 않아 같은 애셋이 여러 애셋 번들에 포함되어 있는 상태이다. 에셋 번들에 대한 자세한 내용은 2.4.6 AssetBundle 에서 확인할 수 있다.

  3. 규정 체크하기
    각 항목에 대한 규정을 준수하고 있는지 검토해보자. 규정이 없는 경우, 메모리를 제대로 추정하지 못했을 가능성이 있으므로 확인해야 한다.
    예를 들어 텍스처의 경우 다음과 같은 내용을 확인하면 좋다.

    • 크기는 적절한가?
    • 압축 설정이 적절한가?
    • MipMap 설정이 적절한가?
    • Read/Write 설정이 적절한가? 등

    각 에셋별로 주의해야 할 점은 제 4 장 Tuning Pracice - Asset을 참고하자.

[1.6.2 GC(Mono)]

Simple View에서 GC(Mono)가 많은 경우, 한 번에 큰 GC.Alloc이 발생했을 가능성이 높다. 또는 매 프레임마다 GC.Alloc이 발생하여 메모리가 파편화되어 있을 수 있습니다. 이로 인해 관리 힙 영역에 불필요하게 확장되고 있을 가능성이 있다. 이 경우 GC.Alloc을 꾸준히 줄여나가야한다.

  • GC란? 가비지 컬렉터(Garbage Collector)의 준말. 메모리 관리 기술 중 하나로, 가비지 컬렉터에 의해 수행되는 프로세스를 의미한다. 여기서 가비지 컬렉터란 메모리 관리를 담당하는 시스템 또는 프로그램의 구성 요소이며, 메모리에서 더 이상 사용되지 않는 객체를 찾아 제거하여 메모리를 회수하는 역할을 수행하는 것을 뜻한다.

[1.6.3 Ohter]

Detailed View에서 의심스러운 항목이 없는지 확인한다. 예를 들어 Ohter 항목 등은 한 번 열어 조사해보는 것이 좋다.
이 문서의 필자는 SerializedFile이나 PersistenManager.Remapper가 상당히 부풀려져 있는 경우가 많았다고 한다. 여러 프로젝트에서 수치 비교가 가능하다면 한 번 비교해보자. 각 각의 수치를 비교해 보면 이상치를 발견할 수 있을지도 모른다.

[1.6.4 플러그인]

여기까지는 Unity의 측정 도구를 사용하여 원인을 파악해 왔다. 하지만 Unity에서 측정할 수 있는 것은 Unity가 관리하고 있는 메모리들 뿐이다. 즉, 플러그인에서 자체적으로 확보하고 있는 메모리 양 등은 측정되지 않는다. ThirdParty 제품에서 여분의 메모리를 확보하고 있는 것은 아닌지 살펴봐야한다.

[1.6.5 사양 검토하기]

이것은 마지막 수단이다. 지금까지 살펴본 내용들로 해결할 수 있는 부분이 없다면, 사양을 검토할 수 밖에 없다. 다음은 그 예시이다.

  • 텍스처의 압축률 변경하기
    - 텍스처의 일부분만 압축률을 한 단계 높인다.
  • 로딩/언로딩하는 타이밍을 변경한다.
    - 상주 메모리에 있는 오브젝트를 해제할 때마다 로드하도록 한다.
  • 로드 사양 변경하기
    - 인게임에서 로드하는 캐릭터의 종류를 1 가지로 줄인다.

모두 영향 범위가 크고, 게임의 재미에 근본적으로 영향을 미칠 수 있다. 따라서 사양 검토는 최후의 수단이다. 이를 방지하기 위해 초기에 메모리를 추정하고 측정하는 것이 좋다.

[1.7 버벅거림의 원인 파악]

이제부터는 처리 시간을 측정하고 최적화하는 과정을 소개한다. 화면의 처리 지연은 순간적인 처리지연인지 상시적인 처리지연인지에 따라 대처 방법이 달라진다.

  • 순간적인 처리지연은 스파이크라고 부르기도 한다.

    해당 그림은 정적 부하가 급혁히 증가하고 있고, 주기적으로 스파이크가 발생하는 측정 데이터이다. 두 가지 모두 성능 튜닝이 필요한 상황이다. 먼저 비교적 간단한 순간 부하 조사에 대해 설명한다. 이후에 정적 부하 조사에 대해 설명한다.

[1.8 순간 부하 조사하기]

스파이크 조사 방법으로는 Profiler(CPU)를 이용하여 원인을 조사한다.
우선 원인이 GC에 의한 것인지 아닌지를 구분한다. 원인 규명 자체에는 Deep Profile이 필요하지 않지만, 해결을 위해서는 필요하다.

  • Deep Profile이란? 스크립트 코드의 모든 부분을 프로파일링하고 모든 함수 호출을 기록하는 것이다. 이러한 정보는 코드가 애플리케이션 성능에 영향을 미치는위치를 파악하는데 도움이 된다.

[1.8.1] GC에 의한 스파이크

GC(가비지 콜렉션)가 발생하고 있다면 GC.Alloc을 줄여야한다. 어떤 프로세스가 얼마나 많이 할당하고 있는지는 Deep Profile을 이용하면 좋다. 우선적으로 줄여야 할 부분은 비용 효율성이 높은 부분이다. 다음 항목들을 중점적으로 수정해보자.

  • 매 프레임마다 할당하는 부분
  • 대량의 할당량이 발생하는 부분

할당량은 적을수록 좋지만, 반드시 제로가 되어야 한다는 의미는 아니다. 예를 들어 생성 처리(instantiate) 등에서 발생하는 할당량은 막을 수 없다. 이럴 때는 매번 객체를 생성하지 않고 객체를 돌려가며 사용하는 풀링(Pooling) 과 같은 방법이 효과적이다.

[1.8.2 과부하로 인한 스파이크]

GC가 원인이 아니라면 어떤 무거운 처리가 순간적으로 이루어지고 있다. 이 경우에도 Deep Profile을 이용하여 어떤 처리가 얼마나 무거운지 조사하고, 가장 많은 시간이 소요되는 부분을 점검해 보도록 하자.
흔히 볼 수 있는 일시적으로 무거운 처리라고 하면 다음과 같은 것들을 생각해 볼 수 있다.

  • Instantiate 처리
  • 대량의 오브젝트 또는 계층 구조가 깊은 오브젝트의 활성 전환
  • 화면 캡처 처리

이처럼 프로젝트 코드에 상당히 의존하는 부분이기 때문에 해결 방법은 일률적으로 이렇게 하면 된다라는 것은 없다. 실제로 측정하여 원인을 파악한 후 프로젝트 구성원들에게 측정 결과를 공유하고, 어떻게 개선해야할지 고민하는 것이 최선이다.

[1.9 정적 부하 조사하기]

정적 처리 부하를 개선할 때는 1 프레임 내 처리를 어떻게 줄일 수 있느냐가 중요하다. 1 프레임 내에서 이루어지는 처리는 크게 CPU 처리와 GPU 처리로 나눌 수 있다. 우선 이 두가지 처리 중 어느 쪽이 병목현상이 발생하는지, 아니면 동일한 수준의 처리 부하인지 구분하는 것이 좋다.

CPU에 병목현상이 있는 상태를 CPU 바운드, GPU에 병목현상이 있는 상태를 GPU 바운드라고 한다.

  • 병목현상이란? 시스템 내에서 전체적인 처리 속도를 떨어뜨리게 만드는 특정한 부분. 병의 목부분 처럼 넓은 길이 갑자기 좁아져서 정체되는 것을 비유한 표현.

쉽게 구분하는 방법으로는 다음과 같은 내용이 해당된다면 GPU 바운드일 가능성이 높다.

  • 화면 해상도를 낮췄을 때 처리 부하가 극적으로 개선된다.
  • Profiler로 측정했을 때 Gfx.WaitForPresent가 존재함.

반대로 이것들이 없다면 CPU 바운드일 가능성이 있다.

[1.9.1 CPU 바운드]

CPU 바운드는 지난 절에서 다루었던 Profiler(CPU)를 이용한다. Deep Profile을 이용하여 조사하고, 특정 알고리즘에 큰 처리 부하가 걸리지 않는지 확인한다. 큰 처리 부하가 없다면 균등하게 무겁다는 의미이므로 꾸준히 개선해 나간다. 꾸준한 개선에도 불구하고 목표한 감소량에 미치치 못한다면 1.1.4 품질 설정의 사양을 정한다 로 돌아가서 다시 생각해보는 것도 방법이다.

[1.9.2 GPU 바운드]

GPU 바운드의 경우 Frame Debugger를 이용해 조사하는 것이 좋다. 자세한 사용법은 3.3 Frame Debugger 에서 확인할 수 있다.

  1. 해상도가 적절한지
    Gpu 바운드 중에서도 해상도는 GPU의 처리량에 큰 영향을 미친다. 따라서 해상도를 적절하게 설정하지 않은 상태라면 우선적으로 적절한 해상도를 설정하는 것이 최우선이다.
    먼저, 예상 품질 설정에서 적절한 해상도가 설정되어 있는지 확인한다. 확인 방법은 Frame Debugger 내에서 처리하고 있는 렌더 타깃의 해상도에 주목하는 것이 좋다. 만약 의도적으로 다음 사항을 구현하지 않았다면 최적화 작업을 진행해야 한다.

    • UI 요소만 디바이스의 전체 해상도로 렌더링되고 있다.
    • 포스트 이펙트를 위한 임시 텍스처의 해상도가 높은 경우 등
  2. 불필요한 오브젝트 존재 여부
    Frame Debugger에서 불필요한 드로잉이 없는지 확인한다. 예를 들어, 필요 없는 카메라가 활성화되어 있고, 뒤에서 관련 없는 드로잉이 이루어지고 있을 수 있다. 또한, 다른 차폐물로 인해 직전의 드로잉이 낭비되는 경우가 많은 경우 오클루전 컬링을 고려하는 것도 좋다. 오클루전 컬링에 대한 자세한 내용은 7.5.3 오클루전 컬링 에서 확인할 수 있다.

    오클루전 컬링은 데이터를 미리 준비해야 하고, 그 데이터를 메모리에 전개하ㅣ 때문에 메모리 사용량이 증가한다는 점도 주의해야한다. 이처럼 성능을 향상시키기 위해 미리 준비된 정보를 메모리에 구축하는 것은 흔한 방법이다. 메모리와 성능은 반비례하는 경우가 많기 때문에, 무언가를 채택할 때는 메모리도 염두에 두는 것이 좋다.

  3. 배칭이 적절한지
    그리기 대상을 한꺼번에 그리는 것을 일괄적으로 그리는 것을 배치라고 한다. 한꺼번에 그려짐으로써 그리기 효율이 높아지기 때문에 GPU 바운딩에 효과가 있다. 예를 들면 Static Batching 을 이용하면 여러 개의 움직이지 않는 오브젝트의 메시를 묶어준다.
    배칭에 관해서는 여러 가지 방법이 있는데, 대표적인 몇 가지를 소개한다.

    • Static Batching
    • Dynamic Batching
    • GPU 인스턴싱
    • SRP Batcher 등
  4. 개별적으로 부하 살펴보기
    그래도 처리 부하가 높다면 개별적으로 살펴볼 수 밖에 없다. 오브젝트의 버텍스 수가 너무 많거나, Shader의 처리에 원인이 있을 수도 있다. 이를 구분하기 위해서는 개별 오브젝트에 대해 액티브 전환을 해보고, 처리 부하가 어떻게 변하는지 살펴봐야한다. 구체적으로는 배경을 지우면 어떻게 되는지, 캐릭터를 지우면 어떻게 되는지 등 카테고리별로 세분화한다. 처리량이 많은 카테고리를 파악했다면, 다음과 같은 요소를 추가로 살펴보자.

    • 그리는 오브젝트가 너무 많은지
      - 한꺼번에 그릴 수 없는지 검토한다.
    • 한 오브젝트의 버텍스 수가 너무 많지는 않은지
      - 리덕션, LOD를 고려한다.
    • 간단한 Shader로 대체하여 처리 부하가 개선될지
      - Shader 처리 재검토
  5. 그 외
    각각의 GPU 처리가 쌓여서 무겁다고 할 수 있다. 이 경우 꾸준히 하나씩 개선해 나가는 수밖에 없다.
    또한 이 역시 CPU 바운드와 마찬가지로, 목표 절감치에 미치지 못한다면 1.1.4 품질 설정의 사양을 정한다. 로 돌아가서 다시 생각해보자.

[1.10 마무리]

이 장에서는 성능 튜닝 전성능 튜닝 중에 주의해야할 사항을 다뤘다.
다시 한번 복습하자면 성능 튜닝 전에 주의해야할 사항은 다음과 같다.

  • 지표, 동작 보증 단말기, 품질 설정 사양을 정한다.
    - 양산 전까지 검증을 통해 지표를 확정할 것.
  • 성능 저하를 쉽게 알아차릴 수 있는 구조를 만들어야 한다.

그리고 성능 튜닝 중에 주의해야 할 사항은 다음과 같다.

  • 성능 저하의 원인을 파악하고 적절한 대응을 해야한다.
  • 측정, 개선, 재측정(결과확인)의 일련의 흐름을 반드시 수행해야 한다.

0개의 댓글