게임 개발자를 성능 프로파일링 팁
원활한 성능은 플레이어에게 몰입감 넘치는 게임 경험을 제공하는 데 필수적입니다. 다양한 플랫폼과 디바이스에서 게임의 성능을 프로파일링하고 개선하면 플레이어 기반을 확장하고 성공 가능성을 높일 수 있습니다.
이 페이지에서는 게임 개발자를 위한 일반적인 프로파일링 워크플로에 대해 설명합니다. 무료로 다운로드할 수 있는 전자책 ' Unity 게임 프로파일링을 위한 궁극의 가이드 '에서 발췌한 내용입니다. 이 전자책은 게임 개발, 프로파일링, 최적화 분야의 외부 및 내부 유니티 전문가들이 제작했습니다.
프로파일링을 통해 설정할 수 있는 유용한 목표, CPU 또는 GPU에 묶여 있는 것과 같은 일반적인 성능 병목 현상, 이러한 상황을 더 자세히 파악하고 조사하는 방법에 대해 알아보려면 계속 읽어보세요.
게임의 프레임 속도를 초당 프레임 수(fps)로 측정하는 것은 플레이어에게 일관된 경험을 제공하는 데 적합하지 않습니다. 다음과 같은 간단한 시나리오를 생각해 보세요:
런타임 동안 게임은 0.75초 동안 59프레임을 렌더링합니다. 그러나 다음 프레임은 렌더링하는 데 0.25초가 걸립니다. 평균 전달 프레임 속도 60fps는 괜찮아 보이지만 실제로는 마지막 프레임이 렌더링되는 데 1/4초가 걸리기 때문에 플레이어는 끊김 현상을 느낄 수 있습니다.
이것이 프레임당 특정 시간 예산을 목표로 하는 것이 중요한 이유 중 하나입니다. 이를 통해 게임을 프로파일링하고 최적화할 때 확실한 목표를 설정할 수 있으며, 궁극적으로 플레이어에게 보다 원활하고 일관된 경험을 제공할 수 있습니다.
각 프레임에는 목표 FPS에 따라 시간 예산이 책정됩니다. 30fps를 목표로 하는 애플리케이션은 항상 프레임당 33.33밀리초(1000밀리초/30fps) 미만이 소요되어야 합니다. 마찬가지로 60fps를 목표로 하면 프레임당 16.66밀리초(1000밀리초/60fps)가 남습니다.
UI 메뉴나 씬 로딩과 같이 비대화형 시퀀스에서는 이 예산을 초과할 수 있지만, 게임플레이 중에는 초과할 수 없습니다. 목표 프레임 예산을 초과하는 한 프레임만 발생해도 장애가 발생합니다.
참고: VR 게임에서 일관되게 높은 프레임 속도는 플레이어에게 메스꺼움이나 불편함을 유발하지 않는 데 필수적입니다. 이 기능이 없으면 게임 인증 과정에서 플랫폼 홀더로부터 거부당할 위험이 있습니다.
초당 프레임 수입니다: 기만적인 지표
게이머가 성능을 측정하는 일반적인 방법은 프레임 속도 또는 초당 프레임 수입니다. 그러나 대신 프레임 시간을 밀리초 단위로 사용하는 것이 좋습니다. 그 이유를 이해하려면 위의 초당 프레임 수 대 프레임 시간 그래프를 보세요.
다음 숫자를 고려하세요:
1000ms/sec / 900fps = 프레임당 1.111ms
1000ms/sec / 450fps = 프레임당 2.222ms
1000ms/sec / 60fps = 프레임당 16.666ms
1000ms/sec / 56.25fps = 프레임당 17.777ms
애플리케이션이 900fps로 실행되는 경우 이는 프레임당 1.111밀리초의 프레임 시간으로 변환됩니다. 450fps에서 이는 프레임당 2.222밀리초입니다. 이는 프레임 속도가 절반으로 줄어든 것처럼 보이지만 프레임당 1.111밀리초의 차이에 불과합니다.
60프레임과 56.25프레임의 차이를 살펴보면 각각 프레임당 16.666밀리초와 17.777밀리초로 환산됩니다. 이 역시 프레임당 1.111밀리초가 추가되는 것을 의미하지만, 프레임 속도 감소는 비율로 보면 훨씬 덜 극적으로 느껴집니다.
개발자가 게임 속도를 벤치마킹할 때 FPS가 아닌 평균 프레임 시간을 사용하는 이유입니다.
목표 프레임 속도 이하로 떨어지지 않는 한 FPS에 대해 걱정하지 마세요. 프레임 시간에 집중하여 게임이 얼마나 빠르게 실행되는지 측정한 다음 프레임 예산 내에서 유지하세요.
자세한 내용은 '로버트 던롭의 FPS와 프레임 시간 비교' 원본 문서를 참조하세요.
열 제어는 모바일 디바이스용 애플리케이션을 개발할 때 최적화해야 할 가장 중요한 영역 중 하나입니다. 비효율적인 설계로 인해 CPU 또는 GPU가 최대 속도로 너무 오래 작동하면 해당 칩이 뜨거워집니다. 칩이 손상되는 것을 방지하기 위해(그리고 플레이어의 손에 화상을 입을 수도 있습니다!) 운영 체제에서 장치의 클럭 속도를 낮추어 냉각시켜 프레임 끊김과 열악한 사용자 경험을 유발합니다. 이러한 성능 저하를 써멀 스로틀링이라고 합니다.
프레임 속도가 빨라지고 코드 실행(또는 DRAM 액세스 작업)이 증가하면 배터리 소모와 발열이 증가합니다. 성능이 좋지 않으면 저가형 모바일 디바이스의 전체 세그먼트에서 이탈하여 시장 기회를 놓쳐 매출 감소로 이어질 수 있습니다.
열 문제를 다룰 때는 시스템 전체 예산으로 작업해야 하는 예산을 고려하세요.
초기 프로파일링 기술을 활용하여 게임을 처음부터 최적화함으로써 열 스로틀링과 배터리 소모를 방지할 수 있습니다. 대상 플랫폼 하드웨어에 대한 프로젝트 설정에서 열 및 배터리 소모 문제를 해결할 수 있습니다.
모바일에서 프레임 예산 조정
장시간 플레이 시 기기 발열 문제를 해결하기 위해 일반적으로 프레임 유휴 시간을 35% 정도로 유지하는 것이 좋습니다. 이렇게 하면 모바일 칩이 식을 시간을 확보할 수 있고 과도한 배터리 소모를 방지할 수 있습니다. 목표 프레임 시간을 프레임당 33.33밀리초(30fps의 경우)로 사용하면 모바일 디바이스의 일반적인 프레임 예산은 프레임당 약 22밀리초가 됩니다.
계산은 다음과 같습니다: (1000 ms / 30) * 0.65 = 21.66 ms
동일한 계산을 사용하여 모바일에서 60fps를 달성하려면 (1000ms/60) * 0.65 = 10.83ms의 목표 프레임 시간이 필요합니다. 이는 많은 모바일 디바이스에서 달성하기 어렵고 30fps를 목표로 할 때보다 배터리를 두 배 더 빨리 소모합니다. 이러한 이유로 대부분의 모바일 게임은 60프레임이 아닌 30프레임을 목표로 합니다. 이 설정을 제어하려면 Application.targetFrameRate를 사용하고, 프레임 시간에 대한 자세한 내용은 전자책의 '프레임 예산 설정' 섹션을 참조하세요.
모바일 칩의 주파수 스케일링은 프로파일링 시 프레임 유휴 시간 예산 할당을 파악하기 까다롭게 만들 수 있습니다. 개선 및 최적화를 통해 긍정적인 효과를 얻을 수 있지만, 모바일 디바이스의 주파수가 낮아져 결과적으로 더 차갑게 실행될 수 있습니다. FTrace 또는 Perfetto와 같은 맞춤형 툴을 사용하여 모바일 칩 주파수, 유휴 시간, 최적화 전후의 스케일링을 모니터링하세요.
목표 프레임에 대한 총 프레임 시간 예산(30프레임의 경우 33.33밀리초) 내에서 이 프레임 속도를 유지하기 위해 디바이스가 덜 작동하거나 더 낮은 온도를 기록하는 것을 확인한다면 올바른 방향으로 가고 있는 것입니다.
모바일 디바이스의 프레임 예산에 여유를 두어야 하는 또 다른 이유는 실제 온도 변동을 고려하기 위해서입니다. 더운 날에는 모바일 장치가 뜨거워지고 열을 방출하는 데 문제가 생겨 열 스로틀링이 발생하고 게임 성능이 저하될 수 있습니다. 프레임 예산의 일정 비율을 따로 설정하면 이러한 종류의 시나리오를 방지하는 데 도움이 됩니다.
모바일 디바이스에서 DRAM 액세스는 일반적으로 전력을 많이 소비하는 작업입니다. 모바일 디바이스의 그래픽 콘텐츠에 대한 Arm의 최적화 조언에 따르면 LPDDR4 메모리 액세스는 바이트당 약 100피코줄의 비용이 든다고 합니다.
프레임당 메모리 액세스 작업 수를 줄입니다:
- 프레임 속도 줄이기
- 가능한 경우 디스플레이 해상도 줄이기
- 버텍스 수와 어트리뷰트 정밀도를 줄인 더 단순한 메시 사용
- 텍스처 압축 및 밉매핑 사용
Arm 또는 Arm Mali 하드웨어를 활용하는 디바이스에 집중해야 하는 경우, Arm Mobile Studio 툴(특히 Streamline Performance Analyzer)에는 메모리 대역폭 문제를 식별하기 위한 몇 가지 훌륭한 성능 카운터가 포함되어 있습니다. 카운터는 각 Arm GPU 세대(예: Mali-G78)에 대해 나열되고 설명되어 있습니다. 모바일 스튜디오 GPU 프로파일링에는 Arm Mali가 필요합니다.
벤치마킹을 위한 하드웨어 계층 설정
플랫폼별 프로파일링 도구를 사용하는 것 외에도 지원하려는 각 플랫폼과 품질 등급에 대해 티어 또는 최저 사양 기기를 설정한 다음 각 사양에 맞게 성능을 프로파일링하고 최적화하세요.
예를 들어 모바일 플랫폼을 대상으로 하는 경우 대상 하드웨어에 따라 기능을 켜거나 끄는 품질 관리를 통해 세 가지 계층을 지원하기로 결정할 수 있습니다. 그런 다음 각 계층에서 가장 낮은 디바이스 사양에 맞게 최적화합니다. 또 다른 예로, PlayStation 4와 PlayStation 5용 게임을 모두 개발하는 경우 두 플랫폼 모두에 프로필을 등록해야 합니다.
전체 모바일 최적화 가이드는 다음을 참조하세요. 모바일 게임 성능 최적화. 이 전자책에는 게임을 실행하는 모바일 기기의 열 스로틀링을 줄이고 배터리 수명을 늘리는 데 도움이 되는 다양한 팁과 요령이 담겨 있습니다.
프로파일링 시에는 딥 프로파일링을 비활성화한 상태에서 시작하는 하향식 접근 방식이 효과적입니다. 이 높은 수준의 접근 방식을 사용하여 데이터를 수집하고 핵심 게임 루프 영역에서 원치 않는 관리 할당 또는 너무 많은 CPU 시간을 유발하는 시나리오에 대해 메모하세요.
먼저 GC.Alloc 마커에 대한 호출 스택을 수집해야 합니다. 이 프로세스에 익숙하지 않은 경우 다음 섹션의 '애플리케이션 수명 동안 반복 메모리 할당 찾기'에서 몇 가지 팁과 요령을 확인하세요. Unity 게임 프로파일링에 대한 최종 가이드.
보고된 통화 스택이 할당 또는 기타 속도 저하의 원인을 추적할 수 있을 만큼 상세하지 않은 경우, 딥 프로파일링을 활성화한 상태에서 두 번째 프로파일링 세션을 수행하여 할당 원인을 찾을 수 있습니다.
프레임 시간 '위반자'에 대한 메모를 수집할 때는 해당 프레임이 나머지 프레임과 어떻게 비교되는지 기록하세요. 이 상대적인 영향은 딥 프로파일링을 켜면 영향을 받습니다.
딥 프로파일링에 대한 자세한 내용은 Unity 게임 프로파일링에 대한 최종 가이드.
일찍 프로필 만들기
프로파일링은 프로젝트 개발 라이프사이클 초기에 시작할 때 가장 큰 이점을 얻을 수 있습니다.
프로젝트의 '성과 서명'을 이해하고 암기할 수 있도록 일찍 그리고 자주 프로필을 작성하세요. 성능이 급격히 저하되는 경우 문제가 발생한 시점을 쉽게 파악하고 문제를 해결할 수 있습니다.
가장 정확한 프로파일링 결과는 항상 대상 기기에서 빌드를 실행하고 프로파일링하는 것과 함께 플랫폼별 툴을 활용하여 각 플랫폼의 하드웨어 특성을 파악할 때 얻을 수 있습니다. 이 조합을 통해 모든 대상 기기에서 애플리케이션 성능을 전체적으로 파악할 수 있습니다.
이 차트의 인쇄용 PDF 버전은 여기에서 다운로드하세요.
일부 플랫폼에서는 애플리케이션이 CPU를 사용하는지 GPU를 사용하는지 쉽게 확인할 수 있습니다. 예를 들어, Xcode에서 iOS 게임을 실행하면 fps 패널에 총 CPU 및 GPU 시간이 표시된 막대 차트가 표시되므로 어느 것이 가장 높은지 확인할 수 있습니다. CPU 시간에는 모바일 장치에서 항상 활성화되어 있는 VSync를 기다리는 시간이 포함됩니다.
그러나 일부 플랫폼에서는 GPU 타이밍 데이터를 얻는 것이 어려울 수 있습니다. 다행히도 Unity 프로파일러는 성능 병목 현상을 파악할 수 있는 충분한 정보를 제공합니다. 위의 흐름도는 초기 프로파일링 프로세스를 보여 주며, 그 다음 섹션에서는 각 단계에 대한 자세한 정보를 제공합니다. 또한 실제 Unity 프로젝트의 프로파일러 캡처를 통해 살펴봐야 할 사항의 종류를 설명합니다.
GPU 대기 시간을 포함한 모든 CPU 활동을 전체적으로 파악하려면 프로파일러의 CPU 모듈에서 타임라인 보기를 사용하세요. 캡처를 올바르게 해석하려면 일반적인 프로파일러 마커를 숙지하세요. 일부 프로파일러 마커는 대상 플랫폼에 따라 다르게 표시될 수 있으므로 각 대상 플랫폼에서 게임 캡처를 살펴보고 프로젝트의 "정상" 캡처가 어떤 모습인지 파악해 보세요.
프로젝트의 성능은 가장 오래 걸리는 칩 및/또는 스레드에 의해 결정됩니다. 바로 이 부분이 최적화 노력을 집중해야 하는 영역입니다. 예를 들어, 목표 프레임 시간 예산이 33.33ms이고 VSync가 활성화된 게임을 상상해 보겠습니다:
- CPU 프레임 시간(VSync 제외)이 25ms이고 GPU 시간이 20ms인 경우 문제 없습니다! CPU에 제한이 있지만 모든 것이 예산 범위 내에 있으며 최적화를 해도 프레임 속도가 향상되지는 않습니다(CPU와 GPU 모두 16.66ms 미만이거나 60fps까지 올라가지 않는 한).
- CPU 프레임 시간이 40밀리초이고 GPU가 20밀리초인 경우 CPU에 종속되어 있으므로 CPU 성능을 최적화해야 합니다. GPU 성능을 최적화하는 것은 도움이 되지 않습니다. 예를 들어 일부 작업에서 C# 코드 대신 컴퓨팅 셰이더를 사용하는 등 일부 CPU 작업을 GPU로 옮겨 균형을 맞추는 것이 좋습니다.
- CPU 프레임 시간이 20밀리초이고 GPU가 40밀리초인 경우 GPU에 종속된 상태이므로 GPU 작업을 최적화해야 합니다.
- CPU와 GPU가 모두 40ms인 경우, 두 가지 모두에 구속되며 30fps에 도달하려면 33.33ms 이하로 최적화해야 합니다.
CPU 또는 GPU 바인딩에 대한 자세한 내용은 다음 리소스를 참조하세요:
개발 초기에 프로젝트를 프로파일링하고 최적화하면 애플리케이션의 모든 CPU 스레드와 전체 GPU 프레임 시간이 프레임 예산 내에 있는지 확인하는 데 도움이 됩니다.
위는 지속적인 프로파일링과 최적화를 수행한 팀이 개발한 Unity 모바일 게임에서 프로파일러를 캡처한 이미지입니다. 이 게임은 고사양 휴대폰에서는 60프레임을, 이 캡처의 휴대폰과 같은 중저사양 휴대폰에서는 30프레임을 목표로 합니다.
선택한 프레임에서 거의 절반에 가까운 시간을 노란색 WaitForTargetfps 프로파일러 마커가 차지하고 있는 것을 확인할 수 있습니다. 애플리케이션이 Application.targetFrameRate를 30fps로 설정하고 VSync를 활성화했습니다. 메인 스레드의 실제 처리 작업은 19ms 정도에 완료되며, 나머지 시간은 다음 프레임을 시작하기 전에 나머지 33.33ms가 경과할 때까지 기다리는 데 소요됩니다. 이 시간은 프로파일러 마커로 표시되지만, 이 시간 동안 메인 CPU 스레드는 기본적으로 유휴 상태이므로 CPU가 냉각되고 최소한의 배터리 전원을 사용합니다.
주의해야 할 마커는 다른 플랫폼에서 또는 VSync가 비활성화되어 있는 경우 다를 수 있습니다. 중요한 것은 메인 스레드가 프레임 예산 내에서 실행되고 있는지 또는 프레임 예산 내에서 정확히 실행되고 있는지, 애플리케이션이 VSync를 대기 중임을 나타내는 일종의 마커를 사용하여 다른 스레드에 유휴 시간이 있는지 확인하는 것입니다.
유휴 시간은 회색 또는 노란색 프로파일러 마커로 표시됩니다. 위 스크린샷은 렌더 스레드가 한 프레임에서 GPU에 드로 콜 전송을 완료하고 다음 프레임에서 CPU의 추가 드로 콜 요청을 기다리는 시간을 나타내는 Gfx.WaitForGfxCommandsFromMainThread에서 유휴 상태임을 보여줍니다. 마찬가지로 잡 워커 0 스레드는 캔버스.지오메트리잡에서 약간의 시간을 보내지만, 대부분의 시간은 유휴 상태입니다. 이 모든 것이 프레임 예산 내에서 편안하게 사용할 수 있는 애플리케이션의 신호입니다.
게임 프레임 예산이 부족한 경우
배터리 사용량과 열 조절을 고려한 예산 조정을 포함하여 프레임 예산 범위 내에 있다면 다음 번까지 성능 프로파일링을 완료한 것입니다 - 축하합니다. 메모리 프로파일러를 실행하여 애플리케이션이 메모리 예산 범위 내에 있는지 확인하세요.
위 이미지는 30fps에 필요한 약 22ms 프레임 예산 내에서 게임이 원활하게 실행되는 모습을 보여줍니다. VSync까지의 메인 스레드 시간 및 렌더 스레드와 작업자 스레드의 회색 유휴 시간을 패딩하는 WaitForTargetfps에 유의하세요. 또한 프레임마다 Gfx.Present의 종료 시간을 보면 V블랭크 간격을 관찰할 수 있으며, 타임라인 영역이나 상단의 시간 눈금에 시간 눈금을 그려서 이 중 하나에서 다음 프레임으로 측정할 수 있습니다.
게임이 CPU 프레임 예산 내에 있지 않은 경우 다음 단계는 CPU의 어느 부분이 병목 현상인지, 즉 어떤 스레드가 가장 바쁜지 조사하는 것입니다. 프로파일링의 핵심은 병목 현상을 최적화 대상으로 식별하는 것입니다. 추측에 의존하면 병목 현상이 아닌 부분을 최적화하게 되어 전체 성능이 거의 개선되지 않거나 전혀 개선되지 않을 수 있습니다. 일부 '최적화'는 게임의 전반적인 성능을 악화시킬 수도 있습니다.
전체 CPU 워크로드가 병목 현상이 되는 경우는 드뭅니다. 최신 CPU에는 독립적으로 동시에 작업을 수행할 수 있는 다양한 코어가 있습니다. 각 CPU 코어에서 서로 다른 스레드를 실행할 수 있습니다. 전체 Unity 애플리케이션은 다양한 용도로 다양한 스레드를 사용하지만, 성능 문제를 찾는 데 가장 일반적으로 사용되는 스레드는 다음과 같습니다:
- 메인 스레드입니다: 모든 게임 로직/스크립트가 기본적으로 작업을 수행하는 곳이며 물리, 애니메이션, UI, 렌더링과 같은 기능과 시스템에서 대부분의 시간이 소요되는 곳입니다.
- 렌더 스레드입니다: 렌더링 프로세스 중에 메인 스레드는 씬을 검사하고 카메라 컬링, 뎁스 정렬, 드로 콜 일괄 처리를 수행하여 렌더링할 사물의 목록을 생성합니다. 이 목록은 렌더 스레드로 전달되며, 렌더 스레드는 Unity의 내부 플랫폼에 구애받지 않는 표현을 특정 플랫폼에서 GPU에 지시하는 데 필요한 특정 그래픽스 API 호출로 변환합니다.
- 작업 워커 스레드입니다: 개발자는 C# 잡 시스템을 사용하여 특정 종류의 작업이 워커 스레드에서 실행되도록 예약할 수 있으므로 메인 스레드의 작업 부하를 줄일 수 있습니다. 물리, 애니메이션, 렌더링과 같은 Unity의 일부 시스템과 기능도 작업 시스템을 사용합니다.
메인 스레드
위 이미지는 메인 스레드에 바인딩된 프로젝트에서 어떻게 보일 수 있는지 보여줍니다. 이 프로젝트는 일반적으로 13.88밀리초(72프레임) 또는 8.33밀리초(120프레임)의 프레임 예산을 목표로 하는 메타 퀘스트 2에서 실행 중이며, VR 기기에서 멀미를 방지하려면 높은 프레임 속도가 중요하기 때문입니다. 하지만 이 게임이 30fps를 목표로 했다 하더라도 이 프로젝트에 문제가 있는 것은 분명합니다.
렌더 스레드와 워커 스레드는 프레임 예산 내에 있는 예시와 비슷해 보이지만, 메인 스레드는 전체 프레임 동안 작업으로 인해 분명히 바쁩니다. 프레임이 끝날 때 발생하는 소량의 프로파일러 오버헤드를 고려하더라도 메인 스레드는 45밀리초 이상 사용되므로 이 프로젝트는 22fps 미만의 프레임 속도를 달성합니다. 메인 스레드가 VSync를 대기 중이라는 표시가 없으며 전체 프레임 동안 사용 중입니다.
다음 조사 단계는 프레임에서 가장 시간이 오래 걸리는 부분을 파악하고 그 이유를 이해하는 것입니다. 이 프레임에서는 전체 프레임 예산보다 많은 16.23ms가 소요되며, 이는 PostLateUpdate.FinishFrameRendering이 소요되는 시간입니다. 자세히 살펴보면 5개의 카메라가 활성화되어 씬을 렌더링하고 있음을 나타내는 Inl_RenderCameraStack이라는 마커의 인스턴스가 5개 있음을 알 수 있습니다. Unity의 모든 카메라는 컬링, 정렬, 배치 등 전체 렌더 파이프라인을 호출하므로 이 프로젝트의 최우선 과제는 활성 카메라의 수를 단 한 대까지 줄이는 것입니다.
모든 MonoBehaviour Update() 메서드를 포괄하는 마커인 BehaviourUpdate는 7.27ms가 걸리며 타임라인의 자홍색 부분은 스크립트가 관리되는 힙 메모리를 할당하는 위치를 나타냅니다. 계층 구조 보기로 전환하고 검색 창에 GC.Alloc을 입력하여 필터링하면 이 메모리를 할당하는 데 이 프레임에서 약 0.33ms가 걸린다는 것을 알 수 있습니다. 그러나 이는 메모리 할당이 CPU 성능에 미치는 영향을 부정확하게 측정한 것입니다.
GC.Alloc 마커는 시작 지점부터 종료 지점까지의 시간을 측정하여 실제로 시간을 측정하지 않습니다. 오버헤드를 작게 유지하기 위해 시작 타임스탬프와 할당량 크기만 기록됩니다. 프로파일러는 최소한의 시간만 할당하여 표시되도록 합니다. 특히 시스템에 새로운 범위의 메모리를 요청해야 하는 경우 실제 할당에 더 오랜 시간이 걸릴 수 있습니다. 영향을 더 명확하게 확인하려면 할당을 수행하는 코드 주위에 프로파일러 마커를 배치하고, 딥 프로파일링에서는 타임라인 보기의 자홍색 GC.Alloc 샘플 사이의 간격을 통해 얼마나 오래 걸렸는지 알 수 있습니다.
또한 새 메모리를 할당하면 성능에 부정적인 영향을 미칠 수 있는데, 이는 직접 측정하고 원인을 파악하기 어렵습니다:
- 시스템에서 새 메모리를 요청하면 모바일 디바이스의 전력 예산에 영향을 미쳐 시스템에서 CPU 또는 GPU 속도가 느려질 수 있습니다.
- 새 메모리는 CPU의 L1 캐시에 로드되어야 하므로 기존 캐시 라인이 밀려날 수 있습니다.
- 증분 또는 동기 가비지 수집은 관리되는 메모리의 기존 여유 공간이 결국 초과되면 바로 또는 지연된 후에 트리거될 수 있습니다.
프레임이 시작될 때 Physics.FixedUpdate의 인스턴스 4개가 4.57ms를 추가합니다. 나중에 LateBehaviourUpdate(MonoBehaviour.LateUpdate() 호출)는 4ms, 애니메이터는 약 1ms가 소요됩니다.
이 프로젝트가 원하는 프레임 예산과 속도를 달성하려면 이러한 모든 메인 스레드 문제를 조사하여 적절한 최적화를 찾아야 합니다. 가장 오랜 시간이 걸리는 작업을 최적화하면 가장 큰 성능 향상을 얻을 수 있습니다.
다음 영역은 메인 스레드에 묶여 있는 프로젝트에서 최적화를 모색할 수 있는 좋은 장소입니다:
- 물리
- 모노비헤이비어 스크립트 업데이트
- 쓰레기 할당 및/또는 수거
- 카메라 컬링 및 렌더링
- 드로우 콜 일괄 처리 불량
- UI 업데이트, 레이아웃 및 리빌드
- 애니메이션
조사하려는 문제에 따라 다른 도구도 도움이 될 수 있습니다:
- 시간이 오래 걸리지만 그 이유를 정확히 알 수 없는 모노비헤이비어 스크립트의 경우 코드에 프로파일러 마커를 추가하거나 딥 프로파일링을 시도하여 전체 호출 스택을 확인하세요.
- 관리되는 메모리를 할당하는 스크립트의 경우 할당 호출 스택을 활성화하여 할당의 출처를 정확히 확인합니다. 또는 딥 프로파일링을 사용하거나 메모리별로 필터링된 코드 문제를 표시하는 프로젝트 감사기를 사용하여 관리되는 할당을 초래하는 모든 코드 줄을 식별할 수 있습니다.
- 프레임 디버거를 사용하여 드로우 콜 배치가 제대로 이루어지지 않는 원인을 조사하세요.
게임 최적화에 대한 종합적인 팁을 확인하려면 Unity 전문가 가이드를 무료로 다운로드하세요:
위 스크린샷은 렌더 스레드에 바인딩된 프로젝트의 스크린샷입니다. 이 게임은 아이소메트릭 시점과 목표 프레임 예산이 33.33ms인 콘솔 게임입니다.
프로파일러 캡처를 보면 현재 프레임에서 렌더링이 시작되기 전에 메인 스레드가 렌더 스레드를 기다리는데, 이는 Gfx.WaitForPresentOnGfxThreadmarker에 표시된 대로입니다. 렌더 스레드는 여전히 이전 프레임의 그리기 호출 명령을 제출하고 있으며 메인 스레드의 새 그리기 호출을 수락할 준비가 되지 않아 렌더 스레드가 Camera.Render에서 시간을 소비하고 있습니다.
현재 프레임과 관련된 마커와 다른 프레임의 마커를 구분할 수 있는데, 후자는 더 어둡게 표시되기 때문입니다. 또한 메인 스레드가 계속 진행하여 렌더 스레드가 처리할 드로우 콜을 발행하기 시작하면 렌더 스레드가 현재 프레임을 처리하는 데 100ms 이상이 걸리며, 다음 프레임 동안 병목 현상이 발생한다는 것을 알 수 있습니다.
추가 조사 결과 이 게임에는 9개의 서로 다른 카메라와 교체 셰이더로 인한 많은 추가 패스가 포함된 복잡한 렌더링 설정이 있었습니다. 또한 이 게임은 포워드 렌더링 경로를 사용하여 130개 이상의 포인트 라이트를 렌더링했는데, 각 라이트에 투명 드로우 호출을 여러 개 추가할 수 있습니다. 이러한 문제를 합치면 프레임당 총 3,000회 이상의 드로우 콜이 발생했습니다.
다음은 렌더 스레드 바인딩된 프로젝트에서 조사해야 할 일반적인 원인입니다:
- 특히 OpenGL 또는 DirectX 11과 같은 구형 그래픽 API에서 드로우 호출 배치가 제대로 이루어지지 않습니다.
- 카메라가 너무 많습니다. 분할 화면 멀티플레이어 게임을 제작하는 경우가 아니라면 활성 카메라는 하나만 사용해야 할 가능성이 높습니다.
- 컬링이 제대로 이루어지지 않아 너무 많은 것이 그려집니다. 카메라의 프러스텀 치수를 조사하고 레이어 마스크를 컬링합니다. 오클루전 컬링을 활성화하는 것이 좋습니다. 월드에 오브젝트가 배치되는 방식에 대해 알고 있는 내용을 바탕으로 간단한 오클루전 컬링 시스템을 직접 만들 수도 있습니다. 씬에 그림자를 드리우는 오브젝트가 몇 개 있는지 확인합니다. 그림자 컬링은 "일반" 컬링과 별도의 패스에서 이루어집니다.
렌더링 프로파일러 모듈은 매 프레임마다 드로우 콜 배치와 SetPass 호출의 개수를 보여줍니다. 렌더 스레드가 GPU에 발행하는 드로우 콜 배치를 조사하는 데 가장 적합한 도구는 프레임 디버거입니다.
메인 스레드나 렌더 스레드 이외의 CPU 스레드에 바인딩된 프로젝트는 그리 흔하지 않습니다. 그러나 프로젝트에서 데이터 지향 기술 스택 (DOTS)을 사용하는 경우, 특히 C# 잡 시스템을 사용하여 작업을 메인 스레드에서 워커 스레드로 이동하는 경우 이 문제가 발생할 수 있습니다.
위 캡처는 에디터의 플레이 모드에서 캡처한 것으로, CPU에서 파티클 유체 시뮬레이션을 실행하는 DOTS 프로젝트를 보여줍니다.
언뜻 보기에는 성공한 것처럼 보입니다. 작업자 스레드는 버스트 컴파일된 작업으로 빽빽하게 채워져 있으며, 이는 많은 양의 작업이 메인 스레드에서 이동되었음을 나타냅니다. 일반적으로 이것은 올바른 결정입니다.
그러나 이 경우 프레임 시간 48.14ms와 메인 스레드에 35.57ms의 회색 WaitForJobGroupID 마커가 표시되는 것은 모든 것이 정상적이지 않다는 신호입니다. WaitForJobGroupID는 메인 스레드가 작업자 스레드에서 비동기적으로 실행되도록 예약된 작업이 있지만 작업자 스레드가 해당 작업의 실행을 완료하기 전에 그 결과가 필요함을 나타냅니다. WaitForJobGroupID 아래의 파란색 프로파일러 마커는 작업이 더 빨리 완료되도록 대기하는 동안 작업을 실행 중인 메인 스레드를 표시합니다.
작업은 버스트 컴파일되지만 여전히 많은 작업을 수행하고 있습니다. 이 프로젝트에서 서로 가까운 파티클을 빠르게 찾기 위해 사용하는 공간 쿼리 구조를 최적화하거나 더 효율적인 구조로 바꿔야 할 수도 있습니다. 또는 공간 쿼리 작업을 프레임의 시작이 아닌 끝으로 예약하여 다음 프레임이 시작될 때까지 결과가 필요하지 않도록 할 수 있습니다. 이 프로젝트가 너무 많은 파티클을 시뮬레이션하려고 하는 것일 수 있습니다. 해결책을 찾으려면 작업 코드에 대한 추가 분석이 필요하므로 더 세분화된 프로파일러 마커를 추가하면 가장 느린 부분을 식별하는 데 도움이 됩니다.
프로젝트의 작업은 이 예시처럼 병렬화되지 않을 수 있습니다. 단일 워커 스레드에서 하나의 긴 작업이 실행되고 있을 수도 있습니다. 예약 중인 작업과 완료해야 하는 작업 사이의 시간이 작업을 실행할 수 있을 만큼 충분히 길다면 괜찮습니다. 그렇지 않은 경우 위의 스크린샷과 같이 작업이 완료될 때까지 기다리는 동안 메인 스레드가 멈추는 것을 볼 수 있습니다.
동기화 지점 및 작업자 스레드 병목 현상의 일반적인 원인은 다음과 같습니다:
- 버스트 컴파일러에서 컴파일되지 않는 작업
- 여러 작업자 스레드에서 병렬화되지 않고 단일 작업자 스레드에서 장기 실행되는 작업
- 작업이 예약된 프레임의 시점과 결과가 필요한 시점 사이의 시간이 충분하지 않습니다.
- 한 프레임에 여러 개의 '동기화 지점'이 있어 모든 작업이 즉시 완료되어야 하는 경우
CPU 사용량 프로파일러 모듈의 타임라인 보기에서 흐름 이벤트 기능을 사용하여 작업이 예약되는 시기와 메인 스레드에서 그 결과가 예상되는 시기를 조사할 수 있습니다. 효율적인 DOTS 코드 작성에 대한 자세한 내용은 다음을 참조하세요. DOTS 모범 사례 가이드를 참조하세요.
메인 스레드가 Gfx.WaitForPresentOnGfxThread와 같은 프로파일러 마커에 많은 시간을 소비하고 렌더 스레드가 Gfx.PresentFrame 또는 <GraphicsAPIName>.WaitForLastPresent와 같은 마커를 동시에 표시하는 경우 애플리케이션은 GPU에 바인딩된 것입니다.
다음 캡처는 삼성 갤럭시 S7에서 벌칸 그래픽 API를 사용하여 촬영한 것입니다. 이 예제에서 Gfx.PresentFrame에 소요되는 시간 중 일부는 VSync를 기다리는 것과 관련이 있을 수 있지만, 이 프로파일러 마커의 극단적인 길이는 이 시간의 대부분이 GPU가 이전 프레임 렌더링을 완료할 때까지 기다리는 데 소요된다는 것을 나타냅니다.
이 게임에서 특정 게임플레이 이벤트는 GPU가 렌더링하는 드로우 호출 수를 세 배로 늘리는 셰이더를 사용하도록 트리거했습니다. GPU 성능을 프로파일링할 때 조사해야 할 일반적인 문제는 다음과 같습니다:
- 앰비언트 오클루전 및 블룸과 같은 일반적인 원인을 포함한 고가의 풀 스크린 포스트 프로세싱 효과
- 비싼 프래그먼트 셰이더로 인해 발생합니다:
- 분기 로직
- 반 정밀도가 아닌 전체 부동 소수점 정밀도 사용
- GPU의 파면 점유에 영향을 미치는 레지스터의 과도한 사용
- 비효율적인 UI, 파티클 시스템 또는 포스트 프로세싱 효과로 인한 투명 렌더 대기열의 오버드로 발생
- 모바일 디바이스의 4K 디스플레이 또는 레티나 디스플레이와 같이 화면 해상도가 지나치게 높은 경우
- 밀집된 메시 지오메트리 또는 LOD 부족으로 인한 마이크로 트라이앵글은 모바일 GPU에서 특히 문제가 되지만 PC 및 콘솔 GPU에도 영향을 미칠 수 있습니다.
- 압축되지 않은 텍스처 또는 밉맵이 없는 고해상도 텍스처로 인한 캐시 누락 및 GPU 메모리 대역폭 낭비
- 지오메트리 또는 테셀레이션 셰이더는 다이내믹 섀도우가 활성화된 경우 프레임당 여러 번 실행될 수 있습니다.
애플리케이션이 GPU에 바인딩된 것으로 보이는 경우 프레임 디버거를 사용하여 GPU로 전송되는 드로우 호출 배치를 빠르게 파악할 수 있습니다. 그러나 이 툴은 특정 GPU 타이밍 정보는 제공하지 않으며 전체 씬이 어떻게 구성되는지만 제공합니다.
GPU 병목 현상의 원인을 조사하는 가장 좋은 방법은 적절한 GPU 프로파일러의 GPU 캡처를 검사하는 것입니다. 사용하는 도구는 대상 하드웨어와 선택한 그래픽 API에 따라 다릅니다.