애플리케이션을 프로파일링할 때 발생할 수 있는 몇 가지 일반적인 문제가 있습니다. 이 페이지에서는 몇 가지 일반적인 성능 문제의 원인을 조사하는 방법에 대해 설명합니다.
시작 시간의 트레이스를 살펴보면 두 가지 주요 검사 메서드인 UnityInitApplicationGraphics
와 UnityLoadApplication
이 있습니다. 이 두 가지 메서드는 프로젝트의 설정, 에셋, 코드가 시작 시간에 영향을 미칠 수 있는 주요 부분입니다.
참고: 애플리케이션의 시작 시간은 플랫폼마다 다릅니다. 대부분의 플랫폼에서는 스플래시 화면이 나타나는 동안 시작합니다.
위의 스크린샷은 iOS 기기에서 실행되는 샘플 Unity 프로젝트의 Instruments 트레이스입니다. 플랫폼 종속적인 startUnity
메서드에 있는 UnityInitApplicationGraphics
와 UnityLoadApplication
메서드에 주목하십시오.
UnityInitApplicationGraphics
는 그래픽스 기기 설정이나 Unity 내부 시스템 초기화와 같은 다양한 내부 작업을 실행합니다. 또한 리소스 시스템 역시 초기화합니다. 이들 작업을 진행하려면, 리소스 시스템에 있는 모든 파일의 색인을 우선 로드해야 합니다.
Unity의 리소스 시스템은 프로젝트의 Assets
폴더에 위치하는 Resources
폴더에 있는 데이터의 모든 에셋 파일을 포함합니다. 여기에는 Resources
폴더의 자식 폴더에 있는 모든 파일이 포함됩니다. 따라서 리소스 시스템을 초기화하는 데 필요한 시간은 애플리케이션 프로젝트의 Resources
폴더에 있는 파일 개수에 따라 늘어납니다.
UnityLoadApplication
은 프로젝트의 첫 씬을 로드하고 초기화하는 메서드를 포함합니다. 이는 셰이더 컴파일링, 텍스처 업로드, 게임 오브젝트 인스턴스화와 같이 첫 씬을 표시하는 데 필요한 데이터를 역직렬화하고 인스턴스화하는 작업을 진행합니다. 또한 Unity는 첫 씬의 모든 MonoBehaviour
의 Awake
콜백을 실행합니다.
이 과정은, 만약 프로젝트에서 첫 씬의 Awake
콜백에 오래 걸리는 코드가 있는 경우 해당 코드로 인하여 프로젝트 전체의 초기 시작 시간을 지연시킬 수 있다는 점을 보여줍니다. 이를 해결하기 위해서는 느린 해당 코드를 제거하거나 애플리케이션의 다른 부분에서 실행되도록 해야 합니다.
초기 시작 시간 이후 캡처된 프로파일링 트레이스의 경우, 주로 고려할 곳은 PlayerLoop
메서드입니다. 이것은 Unity의 메인 루프이며 매 프레임당 한번씩 실행하는 코드입니다.
!샘플 Unity 프로젝트의 Instruments 트레이스](../uploads/Main/UnderstandingPerformanceinUnity-ProfilingSection_image_1.png)
위의 스크린샷은 PlayerLoop
내에서 가장 성능에 영향을 미치는 몇 가지 메서드를 보여줍니다. 참고: PlayerLoop
내의 메서드 이름은 Unity 버전에 따라 다를 수 있습니다.
PlayerRender
는 Unity의 렌더링 시스템을 실행하는 메서드입니다. 이는 오브젝트 컬링, 동적 배치 계산, GPU에 드로우 명령 전달 등과 같은 작업을 진행합니다. 모든 이미지 이펙트나 렌더링 기반 스크립트 콜백(OnWillRenderObject 등) 역시 이 곳에서 실행됩니다. 일반적으로, 메서드는 프로젝트가 상호작용을 하는 동안 CPU 사용 시간을 가장 많이 소모합니다.
BaseBehaviourManager
는 3개의 CommonUpdate
템플릿 버전을 호출합니다. 이는 현재 씬에서 액티브 게임 오브젝트에 포함된 MonoBehaviour
의 특정 콜백을 호출합니다.
CommonUpdate<UpdateManager>
는 Update
콜백을 호출합니다.CommonUpdate<LateUpdateManager>
는 LateUpdate
콜백을 호출합니다.CommonUpdate<FixedUpdateManager>
는 물리 시스템을 활성화한 경우 FixedUpdate
콜백을 호출합니다.일반적으로 BaseBehaviourManager::CommonUpdate<UpdateManager>
메서드는 검사해야 할 가장 유용한 메서드입니다. 왜냐하면, 이 메서드는 Unity 프로젝트 내에서 작동하는 대부분의 스크립트 코드에 대한 엔트리 지점이기 때문입니다.
다음과 같이 유용한 여러 다른 메서드도 있습니다.
UI::CanvasManager
는 프로젝트가 UGUI system를 사용하는 경우 다양한 콜백을 호출합니다. 이는 Unity UI의 배치 계산 및 레이아웃 업데이트를 포함하며, 이 두 개의 작업은 Unity 프로파일러에 매우 많은CanvasManager
가 나타나도록 합니다.DelayedCallManager::Update
는 코루틴을 실행합니다.PhysicsManager::FixedUpdate
는 PhysX 물리 시스템을 실행합니다. 이는 우선 PhysX의 내부 코드를 실행합니다. 리지드바디
와 콜라이더
같은 현재 씬의 물리 오브젝트 수는 PhysX의 내부 코드에 영향을 줍니다. 또한 물리 기반 콜백 특히 OnTriggerStay
와 OnCollisionStay
역시 여기에 나타납니다.만일 프로젝트가 2D 물리 시스템을 사용하는 경우, 이는 Physics2DManager::FixedUpdate
에서 유사한 호출 집합으로 나타납니다.
만일 스크립트가 IL2CPP로 크로스 컴파일된 플랫폼에서 호출되는 경우 ScriptingInvocation
오브젝트를 포함하는 트레이스 줄을 찾아봅니다. 이곳은 스크립트 코드를 실행하기 위해서 Unity의 내부 네이티브 코드가 스크립트 런타임으로 전환하는 지점입니다. 참고: 기술적으로 Unity는 IL2CPP를 통하여 C# 코드를 실행한 후 네이티브 코드가 됩니다. 하지만 크로스 컴파일된 코드는 주로 IL2CPP 런타임 프레임워크를 통해 메서드를 실행하며 손으로 쓴 C++ 코드와는 유사하지 않습니다.
위의 스크린샷에서 RuntimeInvoker_Void
줄 아래에 위치한 모든 메서드는 Unity가 프레임당 한 번 실행한 크로스 컴파일된 C# 스크립트의 일부분입니다.
트레이스 라인의 이름은 오리지널 클래스의 이름 뒤에 밑줄과 오리지널 메서드의 이름이 오게 명명되었습니다. 이 예제에서는, EventSystem.Update
, PlayerShooting.Update
와 같은 다수의 Update
메서드를 볼 수 있습니다. 이는 대부분의 MonoBehaviours
에 있는 기본 Unity Update
콜백입니다.
이러한 메서드를 확장하여 해당 메서드 내에서 CPU 시간을 소비한 메서드를 확인할 수 있습니다. 여기에는 프로젝트 내의 다른 스크립트 메서드, Unity API와 C# 라이브러리 코드를 포함합니다.
위의 트레이스는 StandaloneInputModule.Process
메서드가 프레임당 한 번씩 전체 UI를 통해 레이 캐스팅되었음을 보여줍니다. 이 메서드는 터치 이벤트가 UI 요소 위에 올라왔는지 또는 활성화하는지 여부를 감지합니다. 모든 UI 요소를 반복하고 마우스 위치가 경계 사각형 내에 있는지 테스트하는 방법은 리소스를 많이 사용합니다.
또한 CPU 트레이스에서 에셋 로드를 파악할 수 있습니다. 에셋 로드를 나타내는 주된 메서드는 SerializedFile::ReadObject
입니다. 이 메서드는 파일의 바이너리 데이터 스트림을 Unity의 직렬화 시스템에 연결합니다. 이 직렬화 시스템은 Transfer
라는 메서드를 통해 작동합니다. Transfer
메서드는 텍스처, MonoBehaviour, 파티클 시스템과 같은 모든 에셋 종류에 존재합니다.
위의 스크린샷은 Unity가 씬을 로드하는 트레이스입니다. 씬을 로드할 때 Unity는 씬의 모든 에셋을 읽고 역직렬화합니다.이 작업은 SerializedFile::ReadObject
아래 다양한 Transfer
메서드가 호출하고 있는 것으로 나타납니다.
런타임 중에 성능이 불안정한 현상이 나타나고 성능 트레이스에 SerializedFile::ReadObject
가 상당한 시간을 사용했음이 표시되면 에셋 로드가 프레임 속도를 줄였음을 의미합니다. 참고: SerializedFile::ReadObject
메서드는 일반적으로 SceneManager
, Resources
, AssetBundle API가 동기화된 에셋 로드를 요청할 경우 메인 스레드에 나타납니다.
이 성능 저하를 해결하기 위해 에셋 로딩을 비동기화 시키거나 (이는 무거운 ReadObject
호출을 워커 스레드에 넘기게 됩니다), 특정 무거운 에셋을 미리 로드할 수도 있습니다.
Transfer
호출은 Unity가 오브젝트를 복제할 경우에도 나타납니다(이는 트레이스에서 CloneObject
메서드로 표시됨). Transfer
에 대한 호출이 CloneObject
호출 아래에서 나타나는 경우 Unity가 스토리지에서 에셋을 로드하지 않습니다. 대신 이전 오브젝트의 데이터를 새 오브젝트로 전송합니다. 이를 위해 Unity는 이전 오브젝트를 직렬화하고 결과 데이터를 새 오브젝트로 역직렬화합니다.