
처음 시작했을 때 언급했던 것처럼 나는 효율적인 성능을 생각하지 않고 기능을 구현하기 바빴다. 그렇게 Unity Performance Tuning Bible을 공부하고 내가 지금까지 얼마나 많은 성능적인 손해를 보면서 개발을 했는지 어느정도 깨닫게 되었다... 이제부턴 지금까지 겪지 못한 사건에 부딛히더라도 자신있게 프로파일링하고, 원인을 분석하여 이 서적을 통해 배운 이론을 바탕으로 조치를 취하며 실습을 통해 경험을 쌓아 내 것으로 만드려고 노력해야겠다!
아래부터는 각 장을 공부하며 느낀점과 같은 코멘트를 남기도록 하겠다.
해당 장에서는 성능 튜닝을 시작하기 전에 해야할 준비에 대해 많은 것을 배웠다.
- 프레임 속도, 메모리, 발열 등 어느정도까지 퍼포먼스를 뽑아낼 것인지 지표를 정하자.
- 목표 성능에서 동작을 보장하는 단말기를 결정하자.
- 한 가지 사양x, 여러 품질 설정을 통해 다양한 단말기에서 안정적인 동작을 보장하자.
- 성능 저하의 원인을 절대 추측하지말자.
- 성능 튜닝을 진행할 때 수정한 부분뿐만 아니라 전체적으로 성능 저하가 발생하지 않았는지 확인하자.
- 다양한 방법으로 메모리 누수를 조사하고 메모리를 절약하자.
- 순간적인 부하(스파이크), 상시적인 부하를 구분하고 해결하자.
- 특히, 상시적인 부하는 1프레임 내 처리를 어떻게 줄일 수 있느냐가 중요하다.
해당 장에서는 하드웨어, 3d 렌더링, Unity등 여러 기초 지식을 배웠다.
- CPU, 캐시, GPU, 메모리, 스토리지 등 여러 하드웨어의 역할을 기억하자.
- 렌더링으로 그래픽을 화면에 그리는 처리가 어떻게 진행되는지 기억하자.
- 데이터의 기본 단위와 그 데이터로 이미지, 메시등을 어떻게 저장하는지 기억하자.
- Unity는 IL2CPP를 사용해서 C#으로 작성된 코드를 C++로 변환한다는 것을 잊지말자.
- 에셋, 스레드, 게임 루프, 게임 오브젝트 등 무슨 역할을 하는 지 기억하자.
- 스택과 힙, GC, 구조체를 이해하고 시간복잡도에 대해 기억하자.
- 리스트, 연결 리스트, 큐, 스택, 딕셔너리등 기본적인 자료구조를 기억하자.
3장에서는 프로파일링하는데 도움이 되는 여러 도구를 소개 받았다.
- Unity Profiler를 사용하면 1프레임마다 다양한 정보를 수집할 수 있다.
- Profile ANalyzer는 Unity Profiler의 CPU Usage에서 얻은 데이터를 보다 세밀하게 분석하기 위한 툴이다. 예를 들면 지정한 프레임 구간을 기준으로 평균값, 중앙값, 최소값을 얻을 수 있다.
- Frame Debugger는 현재 표시되고 있는 화면이 어떤 흐름으로 렌더링되고 있는지 분석할 수 있는 도구이다.
- Memory Profiler는 캡처한 데이터가 스크린샷과 함께 로컬에 저장되고, 각 카테고리의 메모리 점유량이 시각화되어 알기 쉽고, 여러 데이터를 비교할 수 있다는 장점을 가지고 있다.
- Heap Explorer는 개인 개발자 Peter77의 오픈소스 툴이며, Memory Profiler와 마찬가지로 메모리 조사를 할 때 많이 사용하는 도구이다.
- Xcode는 애플이 제공하는 통합 개발 환경 도구이다. Xcode에는 Instruments라는 세부적인 측정 및 분석에 특화된 도구가 있다.
- Android Studio는 안드로이드의 통합 개발 환경 도구이다.
이번 장에서는 에셋을 튜닝할 때 필요한 실무적인 지식을 배웠다.
- 텍스처, 메시, 머티리얼, 애니메이션, 파티클 시스템, 오디오 등의 성능 튜닝을 위한 다양한 설정 관련 지식을 다시 한번 보고 해당 설정이 무슨 역할을 하는지 기억하자.
- 리소스 및 스트리밍에셋 폴더는 실제로 필요하지 않은 파일도 빌드에 포함되기 때문에 정기적으로 파일을 검토해서 필요없는 것은 삭제하자.
해당 장에서는 AssetBundle의 설정 및 구현 방침에 따라 통신 용량과 저장 공간을 낭비하지 않는 방법에 대해 배웠다.
- 동시에 사용되는 에셋은 하나의 AssetBundle로 묶고, 여러 에셋에서 참조되는 에셋은 별도의 AssetBundle로 묶자.
- AssetBundle에서 에셋을 로드할 때 사용할 수 있는 API는 3가지가 있는데 보통 AssetBundle.LoadFromFile을 사용한다. 왜냐하면 가장 빠르고 메모리를 절약할 수 있기 때문이다.
6장에서는 물리연산에 대한 최적화에 대해 배웠다. 특히 3D에 대한 내용이 많았던 것 같다.
- 게임내에서 물리 연산이 필요하지 않은 경우 물리엔진을 꺼두자.
- 콜리전은 구, 캡슐, 박스, 메시 순으로 비용이 낮다. 상황에 따라 사용해야되는 모양이 다르겠지만 가능하면 낮은 비용의 콜리전을 사용하자.
- 충돌할 필요가 없는 레이어 간의 체크박스는 모두 해제하자.
- 레이캐스트의 길이는 적당히, 부딪힐 필요가 없는 오브젝트는 layerMask를 통해 충돌을 비활성화하자.
- 상황에 따라 적절하게 Collider와 Rigidbody를 쓰자. (정적, 동적, 키네마틱 동적 콜라이더)
- Rigidbody의 슬립상태를 적극적으로 활용하자.
- 이산적 충돌 판정과 연속적 충돌판정에 대해 이해하고 상황에 따라 적절히 사용하자.
이번 장에서는 유니티의 그래픽 튜닝에 대한 여러 방법을 배웠다.
- 요즘 모바일 기기는 디스플레이 해상도가 매우 높아서 렌디링 해상도를 적절한 값으로 바꾸는 것이 중요하다.
- 텍스처가 완전히 투명한 영역도 그리기 대상이 되므로 가급적 줄여주자.
- 오버드로우가 발생할 수 있는 오브젝트에는 최대한 가벼운 셰이더를 사용하자.
- 반투명 머티리얼은 웬만해서 사용하지 말자.
- 동적 배칭의 조건을 충족한다면 적극적으로 활용하자. 정적 배칭도 마찬가지다.
- GPU 인스턴싱을 사용하면 풀이나 나무같은 메시를 여러 번 그리는 경우 드로우 콜을 줄일 수 있다.
- SRP Bacher를 사용하면 렌더링의 CPU비용을 절감할 수 있다.
- Sprite Atlas는 여러 개의 스프라이트를 하나의 텍스처로 묶는 방법으로 드로우 콜을 줄일 수 있다.
- 셰이더와 라이팅은 종종 큰 부하를 일으키므로 주의하자.
- LOD기법을 사용해서 멀리 있는 오브젝트의 디테일을 줄여 부하를 줄이자.
- 텍스처 스트리밍을 사용해서 텍스처에 필요한 메모리 용량과 로딩 시간을 줄이자.
8장에서는 UI에서 발생하는 부하를 줄이는 방법을 배웠다.
- Canvas를 적절히 분할하자. Canvas내의 요소에 변화가 생기면 Canvas 전체 UI를 리빌드하기 때문에 Canvas내 UI의 수가 많으면 처리비용이 커지기 때문이다. 하지만 Canvas를 분할하면 드로잉 배치가 작동하지 않으므로 신중하게 고려해서 분할하자.
- UnityWhite는 Unity에서 지원하는 기본적인 흰색인데 SpriteAtlas와는 다른 텍스처이기 때문에 드로우 콜이 증가한다. 따라서 UnityWhite를 사용하지말고, SpriteAtlas에 작은 흰색 사각형 이미지를 추가하고 그 Sprite를 이용하는 방법을 채택하자.
- Layout 컴포넌트는 객체를 정렬하는 컴포넌트인데 처리 비용이 많이 들기 때문에 최대한 사용하지 않는 것이 좋다.
- 기본적으로 Image, RawImage는 Raycast Target이 true로 설정되어있는데 클릭이나 터치의 대상이 되지 않아도 되는 객체라면 비활성화하는 것이 좋다.
- Mask는 최대한 사용하지말자. RectMask2d가 그나마 좋지만 RectMask2d를 사용할 때, 그 마스크 대상이 늘어날수록 그에 비례하여 매 프레임 컬링의 CPU부하가 발생하는 현상이 발생할 수 있다.
- TextMeshPro를 사용할 때 SetText를 적극적으로 활용하자.
9장에서는 Unity의 내부 구현과 관련된 성능 튜닝 방법을 배웠다.
- 사용하지 않는 Start, Update 함수는 반드시 삭제하자.
- tag와 name을 통한 접근은 GC.Alloc이 발생한다. 따라서 여러 번 접근하는 경우 캐싱해두자.
- GetComponent()도 마찬가지로 GC.Alloc이 발생하기 때문에 자주 접근하는 경우 미리 캐싱해두자.
- position과 rotation을 접근할 때 SetPositionAndRotation()을 사용하면 함수 호출 횟수를 줄일 수 있다.
- Texture2D, Sprite, Material, PlayableGraph는 명시적으로 파기해야 한다.
- Animator의 재생할 상태 지정, Material을 조작할 때 문자열을 사용하지 말자.
- JsonUtility를 사용할 때 null처리를 고려하자.
- Render.material에서 가져온 머티리얼, MeshFilter,mesh에서 가져온 메시는 명시적으로 파기해야 한다.
- 로그 출력도 다소 무거운 처리이므로 로그 출력코드를 최대한 제거하거나 커스텀하여 최적화해서 사용하자.
- SIMD를 사용하여 하나의 명령어를 동시에 여러 데이터에 적용하자.
이번 장에서는 C# 코드를 통한 성능 튜닝에 대해 배웠다.
- 매프레임마다 GC.Alloc하는 코드를 피하자.
- 되도록이면 람다식을 사용하지 말자.
- 예상치 못한 박스화를 방지하자.
- 우리의 목적은 GC.Alloc을 없애는 것이 아니라, 프레임당 처리 시간을 단축하는 것이 최종 목적임을 잊지말자.
- for문과 foreach문은 똑같아 보이지만 코드 작성 방식에 따라 효율성이 다르다.
- 오브젝트를 동적으로 생성하지 않고 미리 생성해서 사용하는 오브젝트 풀링 기법을 적극적으로 활용하자.
- 문자열은 어떤 방법으로도 ToString()을 통한 메모리 비용을 피할 수 없으므로, 미리 사용할 가능성이 있는 문자는 string 오브젝트를 미리 생성해서 사용하자.
- LINQ는 편리한 언어 기능이지만, 사용하면 힙 할당과 실행 속도는 사용하지 않을 때보다 더 나빠지므로 되도록이면 사용하지 말자.
- 코드를 인라인화해서 메소드 호출 비용을 절감하자.
11장에서는 Project Settings의 Player항목에 있는 성능 향상에 영향을 미치는 설정 항목에 대해 배웠다.
- IL2CPP를 사용하자.
- IL2CPP를 사용하면 C++ Compiler Configuration을 선택할 수 있게 되는데 가능하면 Master를 사용하자.
- Accelerometer Frequency는 iOS 전용 설정으로 가속도 센서를 사용하지 않는다면 반드시 비활성화하자.
마지막 장인 12장에서는 Third Party 라이브러리를 도입할 때 성능 측면에서 주의해야할 사항에 대해 배웠다.
- DOTween은 스크립트에서 부드러운 애니메이션을 구현할 수 있는 라이브러리이다.
- UniRx는 Unity에 최적화된 Reactive Extensions를 구현한 라이브러리이다.
- UniTask는 Unity에서 고성능 비동기 처리를 구현하기 위한 라이브러리로, 값형 기반의 UniTask 타입을 통해 제로 할당으로 비동기 처리를 할 수 있는 것이 특징이다.
이 정도는 기억해두자!!!