충돌
최적화

프로파일링(Profiling)

첫 번째 단계

Unity는 스키닝, 배칭, 물리 연산, 사용자 스크립트, 파티클 등에 있어 CPU(x86의 SSE나 ARM의 NEON 등 SIMD 일부에 고도로 최적화)에 의존합니다.

GPU는 셰이더, 드로우콜, 이미지 이펙트에 사용됩니다.

CPU 또는 GPU의 바운드

  • CPU와 GPU의 처리 시간(ms)을 감지하는 내부 프로파일러를 사용해야 합니다.

파레토(Pareto) 분석

대부분의 문제(80%)가 몇 가지 핵심 원인(20%)에 의해 발생합니다. 에디터 프로파일러를 사용해 가장 프로세서 집약적인 함수 호출을 식별하고 이를 먼저 최적화해야 합니다. 많은 경우 몇 가지 핵심 함수를 최적화하는 행위만으로도 전체적인 실행 속도를 크게 향상시킬 수 있습니다.

함수가 꼭 필요한 경우에만 실행되고 있는지 확인해야 합니다. 예를 들어 OnBecameVisibleOnBecameInvisible 같은 이벤트를 활용해 언제 오브젝트가 보이지 않고 업데이트를 피하는지 감지할 수 있습니다. 코루틴은 정기적인 업데이트가 필요하지만 매 프레임마다 실행될 필요 없는 코드를 호출할 때 유용합니다.

// Do some stuff every frame:
void Update () {
}

//Do some stuff every 0.2 seconds:
IEnumerator SlowUpdate () {
   while (true) {
      //do something
      yield return new WaitForSeconds (0.2f);
   }
}


.NET System.Threading.Thread 클래스로 별도 스레드의 과중한 계산을 처리할 수 있습니다. 이에 다수의 코어를 실행할 수 있기는 하나 Unity API는 스레드에서 안전하지 않으므로 입력물과 결과물을 버퍼링해야 하며 Unity API 호출의 활용을 위해 이것을 읽고 메인 스레드에 할당해야 합니다.

CPU 프로파일링

사용자 코드 프로파일

모든 사용자 코드가 프로파일러에 표시되지는 않습니다. 하지만 Profiler.BeginSampleProfiler.EndSample 을 활용해 필요한 사용자 코드를 프로파일러에 표시할 수 있습니다.

GPU 프로파일링

현재는 Unity 에디터 프로파일러에 GPU 데이터가 표시되지 않습니다. Unity는 해당 데이터가 표시되도록 하드웨어 제조사와 작업을 진행 중이며 Tegra 디바이스가 처음으로 에디터 프로파일러에 표시됩니다.

iOS용 툴

  • Unity 내부 프로파일러(에디터 프로파일러가 아님)입니다. 전체 씬의 GPU 시간을 나타냅니다.
  • PowerVR PVRUniSCo 셰이더 분석기입니다. 다음을 참조하십시오.
  • iOS: Xcode OpenGL ES 드라이버 인스트루먼트는 고급 레벨의 정보만 표시합니다.
    • “Device Utilization %” - 렌더링에 걸린 총 GPU 시간입니다. >95%는 응용 프로그램이 GPU 바운드임을 의미합니다.
    • “Renderer Utilization %” - 픽셀을 그리는 데 걸린 GPU 시간입니다.
    • “Tiler Utilization %” - 버텍스를 프로세스하는 데 걸린 GPU 시간입니다.
    • “Split count” - 버텍스 데이터가 할당된 버퍼에 맞지 않는 프레임 스플릿의 수입니다.

PowerVR은 타일 기반의 디퍼드 렌더러이기 때문에 드로우 콜마다 GPU 타이밍을 얻는 것은 불가능합니다. 하지만 Unity 내장 프로파일러(결과물을 Xcode 아웃풋에 출력)를 활용해 전체 씬의 GPU 시간을 알 수는 있습니다. 현재 Apple의 툴은 GPU와 관련 파트가 얼마나 바쁜지 알려주지만 밀리초 단위로는 알 수 없습니다.

PVRUniSCo는 전체 셰이더의 사이클과 셰이더 코드 각 행의 대략적 사이클을 알려줍니다. Windows와 Mac 모두에 해당됩니다. Apple 드라이버의 실행 사항과 정확히 일치하지는 않지만 대략적인 지표로서는 괜찮습니다.

Android용 툴

  • Adreno(Qualcomm)
  • NVPerfHUD(NVIDIA)
  • PVRTune, PVRUniSCo(PowerVR)

Tegra에서 NVIDIA는 원하는 바를 모두 실현할 수 있게 해주는 환상적인 퍼포먼스 툴을 제공합니다. 드로우 콜 별 GPU 시간, 셰이더 별 사이클, 포스 2x2 텍스처, Null 뷰 사각형이 Windows, OSX, Linux에서 실행됩니다. PerfHUD ES는 소비자의 디바이스에서 손쉽게 작동하지 않으므로 NVIDIA의 개발 보드가 필요합니다.

Qualcomm은 탁월한 Adreno 프로파일러를 Windows용으로만 제공하지만 소비자의 디바이스에서 작동합니다. 타임라인 그래프, 프레임 캡처, 프레임 디버그, API 호출, 셰이더 분석기, 실시간 편집 기능을 갖추고 있습니다.

그래픽스 관련 CPU 프로파일링

내부 프로파일러는 모듈 별로 훌륭한 개요를 제공합니다.

  • OpenGL ES API에 걸린 시간
  • 배칭 효율
  • 스키닝, 애니메이션, 파티클

프로파일러 포트

Unity 프로파일러가 사용하는 포트는 다음과 같습니다.

  • MulticastPort : 54998
  • ListenPorts : 55000 - 55511
  • Multicast(유닛 테스트) : 55512 - 56023

네트워크 노드에서 액세스 가능해야 합니다. 다시 말해 프로파일하려는 디바이스는 프로파일러를 사용하는 Unity 에디터가 있는 기기의 포트를 볼 수 있어야 합니다.

메모리

메모리에는 모노 메모리와 Unity 메모리의 두 가지 타입이 있습니다.

모노(Mono) 메모리

모노 메모리는 스크립트 오브젝트, Unity 오브젝트의 래퍼(게임 오브젝트, 에셋, 컴포넌트 등)를 처리합니다. 할당이 사용 가능한 메모리에 맞지 않을 때나 System.GC.Collect() 가 호출됐을 때 가비지 컬렉터가 메모리를 정리합니다.

메모리는 힙 블록에 할당됩니다. 할당된 블록에 데이터를 저장할 수 없을 때 메모리가 더 할당될 수 있습니다. 힙 블록은 응용 프로그램이 닫힐 때까지 모노에 보관됩니다. 다시 말해 모노는 사용된 메모리를 OS(Unity 3.x)로 릴리스하지 않습니다. 일정 수준의 메모리를 할당하고 나면 모노용으로 확보해 OS에서 사용할 수 없습니다. 릴리스 후에도 내부적으로 모노용으로만 가능하며 OS에서는 사용할 수 없습니다. 프로파일러의 힙 메모리 값은 오로지 증가하며 절대 줄지 않습니다.

시스템이 할당한 힙 블록에 새 데이터를 저장하지 못하면(예를 들어 단편화의 이유로) 모노는 “GC”를 호출하고 새로운 힙 블록을 할당할 수 있습니다.

“힙 섹션이 지나치게 많다”는 뜻은 단편화나 과중한 사용 때문에 모노 메모리를 전부 소모했다는 의미입니다.

System.GC.GetTotalMemory로 모노 메모리의 총 사용량을 알 수 있습니다.

일반적인 조언을 드리자면 가능한 한 작은 할당을 사용해야 합니다.

Unity 메모리

Unity 메모리는 에셋 데이터(텍스처, 메시, 오디오, 애니메이션 등), 게임 오브젝트, 엔진 내부(렌더링, 파티클, 물리 등)를 처리합니다. Profiler.usedHeapSize로 Unity 메모리 총 사용량을 알 수 있습니다.

메모리 맵

아직 툴이 준비되지 않았으나, 다음을 활용할 수 있습니다.

  • Unity 프로파일러 - 완벽하지 않고 스킵이 일어나기도 하지만 개요를 파악할 수 있습니다. 또한, 디바이스에서도 작동합니다.
  • 내부 프로파일러. 사용된 힙과 할당된 힙을 표시합니다. 모노 메모리를 참조하십시오. 프레임별 모노 할당 수를 보여줍니다.
  • Xcode 툴 - iOS
  • Xcode 인스트루먼트 작업 모니터 - 리얼 메모리 열을 볼 수 있습니다.
  • Xcode 인스트루먼트 할당 - 생성 및 유효한 오브젝트의 순 할당입니다.
  • VM 트래커(텍스처는 보통 IOKit 레이블과 할당되며 메시는 VM Allocate에 들어감)

또한, Unity API 호출로 자체적인 툴을 만들 수 있습니다.

  • FindObjectsOfTypeAll (type : Type) : Object[]
  • FindObjectsOfType (type : Type): Object[]
  • GetRuntimeMemorySize (o : Object) : int
  • GetMonoHeapSize
  • GetMonoUsedSize
  • Profiler.BeginSample/EndSample - 자체적인 코드를 프로파일합니다.
  • UnloadUnusedAssets () : AsyncOperation
  • System.GC.GetTotalMemory/Profiler.usedHeapSize

로드된 오브젝트에 대한 참조 - 이를 파악할 방법은 없지만 public 변수에 대해 “씬에서 레퍼런스 찾기”를 차선책으로 활용할 수 있습니다.

가비지 컬렉터(Garbage Collector)

  • 시스템이 할당된 힙 블록에서 새 데이터 영역을 확보할 수 없을 때 실행됩니다.
  • 모바일에서는 OnGUI()를 사용하지 마십시오. 프레임마다 여러 번 실행되어 뷰를 완전히 새로 그리며 대량의 메모리 할당 호출을 불러와 결과적으로 가비지 컬렉션이 실행되게 합니다.

지나치게 많은 오브젝트를 너무 빨리 생성/삭제합니까?

  • 이는 단편화로 이어질 수 있습니다.
  • 메모리 활동을 추적하려면 에디터 프로파일러를 사용해야 합니다.
  • 내부 프로파일러를 모노 메모리 활동 추적에 활용할 수도 있습니다.
  • 끊김 현상이 있어도 무방하다면 System.GC.Collect()라는 .Net 함수를 쓸 수 있습니다.

할당 끊김 현상

  • 재사용 가능하고 미리 할당된 클래스 인스턴스 리스트를 사용해 자체적인 메모리 관리 체계를 구현합니다.
  • 프레임별로 큰 할당을 하지 말고 캐시 및 사전 할당을 활용합니다.
  • 단편화 문제입니까?

메모리 풀 사전 할당

  • 비활성 게임 오브젝트 리스트를 유지하고 인스턴스화와 삭제하는 대신 재사용합니다.

모노 메모리 부족

  • 메모리 활동 프로파일 - 첫 번째 메모리 페이지는 언제 가득 찹니까?
  • 하나의 메모리 페이지가 부족할 정도의 게임 오브젝트가 정말로 필요합니까?
  • 로컬 데이터에 클래스 대신 구조체를 사용해야 합니다. 클래스는 힙에 저장되는 반면 구조체는 스택에 저장됩니다.
  • 자동 메모리 관리의 이해 페이지를 참조하십시오.

메모리 부족에 따른 크래시

이론적으로는 메모리가 충분하지만 어느 시점에 게임이 “메모리 부족”으로 크래시할 수도 있습니다. 크래시가 일어나면 평상시 게임 메모리 사용량과 할당된 메모리 크기를 비교해볼 수 있습니다. 두 수치가 비슷하지 않다면 메모리 스파이크가 일어납니다. 그 원인은 다음과 같습니다.

  • 동시에 대형 씬 두 개가 로딩되는 경우 - 문제를 바로잡기 위해 대형 씬 사이에 빈 씬을 넣어야 합니다.
  • 추가 씬 로딩 - 사용하지 않은 부분을 제거해 메모리 크기를 유지해야 합니다.
  • 메모리에 로딩된 거대한 에셋 번들
  • 적절하게 압축되지 않은 텍스처 (모바일에서는 금지).
  • Get/Set 픽셀을 활성화한 텍스처. 메모리에 압축되지 않은 사본 텍스처를 필요로 합니다.
  • 런타임 시점에 JPEG/PNG에서 로드된 텍스처는 기본적으로 압축되지 않습니다.
  • 로딩 시 압축 해제하도록 체크된 대형 mp3 파일.
  • 씬이 바뀌어도 제거되지 않는 정적 모노동작 필드와 같은 특이한 캐시에 미사용 에셋을 둘 경우.
충돌
최적화