Mobile Profiler
프로파일러 항목별 최적화
-
Meshskinning.SkinOnGPU
렌더링 전 버텍스당 스키닝을 각 프레임마다 재계산하는데, 이 폴리곤이 많아지면 연산부담도 증가하게 됨.
이 연산은 CPU에서 처리하는데 Unity에서 옵션값을 통해 GPU에게 할당할 수도 있음
Player Settings > Player > Other Settings > Compute Skinning
해당 값이 활성 > GPU 연산, 비활성 > CPU 연산
언뜻 보면 GPU에게 맡기면 빠를거 같지만 모바일 환경은 GPU 사양이 제한되어 있어, 병목 원인에 따라 선택해야 함
CPU로 연산하면 SIMD라는 아키텍처를 이용해 고속연산을 수행하기 때문에 모바일 환경이라면 비활성 하는게 이득
하지만 프레임 드랍의 원인이 CPU라면 GPU에게 처리하도록 하는게 더 나은 방법
*단, GPU 스키닝 연산은 OpenGL ES2에서는 지원되지 않음. DX11과 OpenGL ES3에서만 유효 (지원여부는 바뀔수있다) -
Dynamic Batching
메시 렌더링시 월드공간 변환을 VS(버텍스셰이더) 에서 담당하는데 (=GPU 연산)
해당 값 활성화시 이를 CPU에게 맡김 (CPU에게 오버헤드 가중)
메시 렌더링(드로우콜)은 그래픽스 API 영향이 커서 좋은 API (Metal, Vulkan) 일 경우에는 이를 GPU에게 맡기는 것이 낫다
Texture 최적화
- 한프레임당 생성할 텍스쳐가 많을 경우 new Texture()를 통해 LoadImage(byte[]) 함수를 쓰는 경우 이를 피하는 것이 좋다.
- 만약 local file path를 알고 있다면, UnityWebRequestTexture 를 통해 GetTexture()로 바로 얻어오는게 이득
- new Texture() 함수는 의외로 저비용. 만약 텍스쳐를 생성하는 중에 프레임 저하가 발생한다면, Texture에 색상값을 write하는 함수를 의심해볼것.
GC 최적화
- GC는 실행될때마다 메인 쓰레드를 잠시 멈추고 메모리를 한번 훑은 뒤 파편화된 공간들의 정리가 완료되면 메인쓰레드를 마저 실행해준다
- 당연하게도 사용중인 힙메모리가 많을수록 GC.collect 실행시간이 증가하게되고 이는 곧 프레임저하의 주요 원인인 GC Peak를 유발한다
- 주기적으로 호출하여 메모리를 자주 정리해주는건 좋지만 과도한 호출은 원활한 플레이에 지장을 줌
- 2019.3 버전부터 Incremental GC 기능이 추가되었는데, 이 기능은 GC 실행시 처리할 항목들을 여러 개로 나눠 처리하게 해주는 기능임. GC가 빈번히 호출되진 않지만, 호출시마다 GC Peak 가 가파른 환경에서 저 옵션을 켜는 것만으로도 눈에 띄는 효과를 볼 수 있음. 총 처리해야 할 총량이 줄어들진 않지만 나눠서 처리하기 때문에 많은 환경에서 이득을 기대할 만 함
- 위와 같은 처리를 해도 결국 메모리는 할당되어야 하고, 이 때문에 개발자들은 메모리를 절약할 수 있는 방법을 찾아야 함
- 같은 동작이라도 처리방식에 따라 메모리 사용량이 다르기 때문에 병목현상이 발생한다면 API교체를 고려해보자
- 총 힙메모리 양 대비 일정 가용량을 넘어서면 프레임 저하가 발생할 수 밖에 없으므로 현재 메모리 사용량을 항상 체크
- Profiler.usedHeapSizeLong과 Profiler.GetMonoUsedSize를 사용하여 현재 메모리 사용량을 파악할 수 있다
- usedHeapSize : 우리가 아는 그 힙메모리로서, 변수 할당, 클래스 선언부터 Unity에서는 Texture, GameObject, Material등 런타임 관련 오브젝트를 다룰때 할당되는 영역
- monoUsedSize : script, Asset, 등은 해당 영역에 할당. Unity의 스택 영역이라고도 볼 수 있다
- SystemInfo.SystemMemorySize : Unity가 쓸수 있는 메모리 총량
- Profiler.usedHeapSizeLong과 Profiler.GetMonoUsedSize를 사용하여 현재 메모리 사용량을 파악할 수 있다
Shader 최적화
- Galaxy S20 폰에서 몇몇 머티리얼 렌더링 시 프레임 렉이 발생(픽셀이 깨져 해상도가 매우 낮아보임)
- 60 frame 이상을 찍고 있는 상태에 렌더링 부분만 프레임 렉이 발생해서, 전형적인 GPU쓰로틀링 현상이라 쉐이더 픽셀연산을 살펴보았지만, 단순히 텍스쳐에 _Time.y 을 이용한 UV 애니메이션이 들어간 쉐이더조차 프레임 렉이 발생
- 원인은 _Time 변수 때문이었는데, 요 값은 게임시작 시부터 지금까지 흐른 시간을 받아오는 변수라서, 수많은 유니티 강좌에서 UV 애니메이션을 설명할때 자주 등장한다.
- 문제는 이 값이 계속 증가하면서 모바일GPU 칩의 부동소수점 연산처리 한도를 벗어나게 되어, 제대로 된 _Time.y 값이 반환되지 않았던 것이다.
- Galaxy S20의 GPU칩은 16bit 나 그 이하의 비트만을 부동소수점 연산으로 제공하기 때문에 문제가 도드라져 보였던 것
- 해결책으로는 _Time.y 값을 쓰지 않고 외부의 script에서 Time.deltaTime 값을 누적해 직접 쉐이더에 전달하고, 해당 값은 일정 값을 넘지 않도록 클램핑하는 것이다.
- _Time 을 이용한 UV 애니메이션이 들어간 모든 Shader 를 고치는 대공사를 진행함
프로그래밍 & 코드 최적화
- 적절한 targetFrameRate 설정
- Adaptive Performance 사용 고려
- 현재 메모리 가용상태에 대한 콜백을 제공하고, 병목의 원인이 GPU인지 CPU인지 정보도 알려줌
- PackageManager 에서 설치 후 콜백만 연결해주면 바로 사용 가능
- Galaxy S10 이상의 안드로이드만 지원
- 추후 지원폰을 확대하겠다고 했지만, 마지막 버전이 나온지 1년이 넘었네..
- Adaptive Performance 사용 고려
- 비동기 로딩 사용
- AssetBundle.LoadFromFileAsync() 등..Async가 붙은 함수를 사용하자
- 사용하지 않는 MonoBehaviour 함수 제거
- Start(), Update(), FixedUpdate() 등은 C++ (엔진) 단에서 C# 함수와 매핑과정을 거치는데 많을수록 엔진 메모리에 할당을 많이 해줘야 하므로 안쓰면 지워줘야 한다.
- 코드 내부에 아무것도 없으니까 남겨놔도 되겠지 생각하지만, 엔진은 내부가 비어있는지를 판단할 수 없기 때문에 무조건 호출함(맵핑함)
- Update() 내부에서 다음 함수들의 사용은 지양할것(모두C++단을 통해 찾은 뒤 C#단으로 넘겨주는 함수)
- GameObject.xxx (Find(), FindObjectsOfType() 등..)
- GetComponent()
- Camera.main
- Debug.Log() 주의
- 코드가 그대로 빌드에 넘어가므로 출시 전쯤에는 전처리를 이용해 비활성화 설정
- 정적 데이터 파싱 주의
- XML/JSON 은 런타임시 바로 읽는건 지양하고 Scriptable Object 형태로 읽을 수 있도록 개발툴을 마련하기
본 포스팅은 'Dev Weeks : 성능 프로파일링과 최적화' Youtube 영상을 정리한 것입니다.
'Unity' 카테고리의 다른 글
rigidbody, collider 개념 및 정리 (0) | 2020.10.08 |
---|---|
IL2CPP (0) | 2020.09.29 |
Addressables (0) | 2020.07.28 |
좌표계 (0) | 2020.06.09 |
유니티 셰이더 - UV 연산기법 (0) | 2020.05.22 |