IT 이모저모

메모리 최적화

exien 2018. 5. 9. 16:49

출처 : http://letjude.tistory.com/entry/unity3d-메모리-최적화

[unity3d] 메모리 최적화

근 열린 유니티 부트캠프에서 공개된 자료에 의하면 유니티(Unity)를 가장 많이 사용하는 도시가 서울이라고 한다. 하루가 멀다 하고 유니티 관련 서적들이 쏟아져 나오고 있고 유니티를 이용한 게임 개발의 열기도 뜨거워지고 있다. 모바일 시대에 유니티는 어쩌면 시대를 매우 잘 타고난 툴일지도 모른다는 생각이 든다. 기능적으로는 기타 PC 온라인게임 엔진들보다 부족한 점이 많지만 모바일에 보다 특화된 배포 체계와 통합된 에디터, 그리고 상대적으로 저렴한 가격 등을 이점으로 삼아 시장을 순식간에 선점해 버렸다.해당자료는 http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=41284 에서 퍼왔습니다.

양승헌 shyang@nexon.co.kr | 넥슨코리아에서 프로그래머로 근무하고 있으며 유니티를 이용해 게임을 개발하고 있다.

다른 엔진들도 모바일 버전을 속속 내놓고 있지만 유니티의 인기는 당분간 지속될 것으로 보인다. 필자는 이 글을 통해 유니티를 이용해 게임을 만드는 과정에서 경험한 내용을 공유하고자 한다. 유니티를 이용한 게임의 종류가 다양한 만큼 각 게임의 상황이나 추구하는 바에 따라 최적화 포인트도 다양하다. 여기서 필자는 그 중 메모리에 관련된 부분에 한정해 설명하고자 한다.


<그림 1> Unity Editor : Top Ten Usage Editor(출처 : http://twitter.com/aras_p/status/189626769731616768/photo/1)

모바일 플랫폼
최적화는 무척 고통스러운 작업이다. 유니티처럼 플랫폼을 한 꺼풀 가리고 있는 경우 더욱 그 작업을 어렵게 만든다. 플랫폼을 모르고 개발을 진행할 순 있지만 결국 개발의 후반부에 다다르면 반드시 성능 문제가 발생하고 이 문제의 해결은 툴이 아닌 개발자의 몫으로 다가오게 된다. 툴의 옵션을 변경하는 수준의 최적화로는 늘 한계가 있다. 최적화를 위해서는 결국 플랫폼에 대한 이해가 우선 이뤄져야 한다. 특히나 모바일 플랫폼의 경우 이제 정보가 조금씩 공개되고 있다고는 해도 아직 많은 부분들은 플랫폼 개발사들이 제대로 공개하지 않고 있다. 이런 까닭에 최적화 사례도 충분히 공유되지 않은 것 같다.

필자가 학교를 다닐 무렵에는 임베디드 플랫폼이라는 키워드가 한창 이슈가 됐다. 그럴 때마다 교수님은 임베디드 플랫폼은 “그저 작은 데스크톱 플랫폼”이라고 말하며 데스크톱이나 열심히 공부할 것을 강조하곤 했다. 당시에 필자는 교수님이 시대의 트렌드를 제대로 이해하지 못한 건 아닌가라는 의구심을 가지기도 했지만 지금 생각해 보면 임베디드 플랫폼을 가장 잘 정의한 표현이 아닌가 싶다. 세부적인 뉘앙스가 좀 다를 수 있겠지만 그 키워드를 대체하고 있는 지금의 모바일 플랫폼의 경우 데스크톱 OS의 많은 기능을 수용하고 있고 CPU의 성능도 수년 전의 데스트톱의 CPU 클럭에 필적하는 수준으로 향상됐다. 

그럼 정말 모바일 플랫폼은 그저 작은 데스크톱 플랫폼일까? 과거뿐 아니라 지금까지도 인기를 끌고 있는 일본 애니메이션 ‘에반게리온’을 보면 과거의 다른 메카닉물들과 차별화되는 점이 있다. 바로 애니메이션에 등장하는 메카닉들이 충전식이고 배터리 용량이 허용하는 범위 내에서만 동작할 수 있다는 점이다. 이 점은 모바일 플랫폼과 무척 유사한데 최대 클럭은 데스크톱 플랫폼에 필적한다지만 전력 소모를 최소화하면서(Power Management Scheme에 의해) 동작하다 보니 스펙상 최고 성능을 내기가 어렵다. 지금도 새로운 모바일 제품이 출시될 때면 으레 거치형 콘솔에 육박하는 성능을 가진 GPU가 탑재됐다는 광고를 심심치 않게 보게 되지만 실제로 활용할 수 있는 성능은 아니다.

모바일 CPU와 GPU
다음은 요즘 모바일 CPU들의 흔한 스펙이다.

- Over 1GHz Clock
- Multi-Core
- Vector Floating Processor Unit
- NEON (SIMD Instruction)
- ARM / Thumb / Thumb-2
- Dynamic Power Management(DCVS, Variable SMP)

인텔이 모바일 CPU 시장에 열심히 도전하고 있지만 아직까지는 ARM이 대세를 이루고 있다. 멀티코어 지원이나 NEON 관련해서는 게임의 성능 최적화에 활용할 부분이 많이 있지만 다행히 유니티에서 해당 리소스를 잘 활용하고 있어서 개발자가 모바일 CPU에 대해 신경 써야 할 부분은 거의 없다.

이와 반대로 모바일 GPU는 살펴봐야 할 부분이 좀 있다. 우선 간단히 모바일 그래픽스의 연대기를 살펴보자.


<그림 2> 모바일 그래픽스 연대기

2003년에는 모바일 그래픽스의 대표적인 API인 OpenGL ES 1.0과 Nokia N-Gage란 게임폰이 출시됐다. N-Gage는 비록 성공적인 모델이라 할 수는 없지만 이후 다른 회사 게임폰 출시에 영향을 주었다. 
2005년은 한국 모바일 시장의 큰 사건이 일어난 해다. 대표적인 휴대폰 제조사들이 게임폰을 출시하고 통신사는 GxG, GPang이란 3D 게임서비스를 시작했다. 각 게임폰은 모바일플랫폼 최초로 3D 그래픽이 가속기능을 갖춘 GPU를 탑재했다. 하드웨어 스펙은 당시 다른 피처폰 대비 월등했지만 게임서비스에 대한 경험이 별로 없던 한국 하드웨어 벤더나 통신사는 3D 게임사업에서 큰 손실을 입는다. 그리고 이후 게임폰은 후속모델이 나오지 않게 됐다. 

2006년의 국내시장은 암흑기를 겪고 있었지만 해외에서는 몇 가지 사건이 일어난다. ATi(현재 AMD)가 핀란드의 BITBOYS란 그래픽스 회사를 인수했고 같은 해에 ARM은 노르웨이의 falanx를 인수한다. 이후 2009년에 ATi 모바일 부분을 퀄컴이 인수하게 된다. ARM의 경우는 삼성 모바일 칩셋에 자사의 Mali GPU를 탑재하게 됨으로써 현재 모바일 그래픽스 시장에서도 상당한 시장점유율을 차지하고 있다. 
2007년 아이폰이 출시되면서 모바일 GPU는 다시 각광받게 되는데 기존에는 단지 게임가속으로나 활용한다고 인식되던 모바일 GPU를 플랫폼과 완벽히 통합함으로써 화려한 UI 애니메이션을 가능하게 하는 역할로 재조명받게 됐다. 다음해 구글에서도 안드로이드 G1을 출시했고 아이폰 OS만큼은 아니지만 GPU를 활용해 UI를 가속하는 기능을 선보였다. 

2009년 모바일에서는 처음으로 OpenGL ES 2.0을 지원하는 아이폰 3GS가 발표됐다. 그리고 올해는 여러 개의 GPU 코어를 탑재한 뉴 아이패드(The New iPad)와 아이폰5가 발표됐다. 초기 게임폰 시장을 제외한 나머지는 아이폰이 모바일 그래픽스 시장을 주도했다고 봐도 과언이 아니다. 모바일 게임 개발자의 입장으로서 2005년의 암흑기를 생각하면 모바일 GPU의 재등장을 가능하게 해준 아이폰은 구세주와 같은 존재였다.

모바일 GPU 네 가지
그래픽스 하드웨어 업체들이 중심이 된 크로노스 그룹에서 OpenGL ES 스펙을 제정하고 배포하고 있지만 칩셋들은 고유의 비디오 메모리를 다루는 방식과 압축 텍스처 포맷에서 차이가 발생한다. 압축 텍스처의 경우 통일된 포맷에 대한 요구가 끊이지 않고 있지만 특허와 관련된 이해관계가 첨예하게 얽혀 있어서 현재는 확장 기능 형태로 제공되고 있다.

모바일 GPU는 앞서 언급했듯이 지난 몇 년간의 인수합병 과정을 거쳐 4종이 살아남았고 특히 Mali의 경우 삼성 엑시노스 칩셋에 탑재되면서 최근 빠르게 점유율을 높여가고 있다. 아이폰2G를 비롯해 초기 스마트폰들은 CPU가 사용하는 영역과 GPU가 사용하는 메모리 영역이 분리돼 있어 비디오 메모리를 많이 사용하는 게임의 경우 문제가 많이 발생했다. 하지만 최근에는 영역이 엄격히 구분되어 있지 않고 공유하는 방식을 택하고 있다.


<표 1> 모바일 GPU 코어별 특징

벤더마다 방식의 차이는 있지만 압축 텍스처를 사용할 경우 여러 가지 이점이 있다. 우선 스토리지 공간을 줄일 수 있고 File I/O 시간이 빠르며 비디오 메모리를 적게 차지하고 렌더링 속도도 빠르다. 물론 포맷에 따라 정도는 다르지만, 화질 열화가 발생함에도 텍스처가 활용되는 상황에 따라 적절히 사용하면 많은 이점을 얻을 수 있다. 아이폰의 경우 PowerVR만 사용되기 때문에 PVRTC만 사용하면 되지만, 안드로이드 플랫폼의 경우 디바이스마다 다른 텍스처 포맷 때문에 혼란에 빠질 수 있다. 유니티에서도 이 부분은 개발자에게 선택권을 주고 있는데(File → Build Settings 메뉴에서), 여러 버전으로 배포해야 된다는 부담감 때문에 많은 게임들이 텍스처 압축을 사용하지 않고 있다. 이와 관련해 ETC1이란 공통된 지원 포맷이 있지만 기능이 미약해 많이 활용되진 않고 있다.

유니티 플레이어
유니티 에디터에서 프로젝트를 빌드하게 되면 OS 플랫폼에 따라 프로젝트(Project)가 생성되거나(iOS), 앱 패키지가 생성된다(안드로이드). 두 경우 모두 결과물은 게임 데이터와 이를 구동하기 위한 플레이어로 이루어져 있다. 플레이어는 유니티의 스크립트를 구동하는 모노 런타임과 네이티브 코드 실행을 위한 플러그인, 각종 써드파티 라이브러리(물리, 네트워크, 사운드), 그리고 이를 통합하는 유니티 플레이어로 구성되어 있다. 최적화 관점에서 눈여겨봐야 할 부분은 시스템 메모리를 소비하는 모노 런타임과 비디오 메모리를 소비하게 되는 Texture, VBO(Vertex Buffer Object)가 할당되는 유니티 플레이어다.


<그림 3> 유니티 플레이어의 구조

유니티 3.x에 탑재되어 있는 모노 런타임의 경우 2.6 버전이 포함돼 있다. 이 버전은 C와 C++를 위한 가비지 컬렉터(Garbage Collector)로 많이 사용되던 Boehm-Demers-Wiser Conservative Garbage Collector(이하 BDW GC)가 기본으로 포함되어 있다. BDW GC는 몇 가지 특징이 있는데 우선 10초마다 GC 루틴이 실행되고 애플리케이션 종료 시 Plugins들에 대한 처리는 특별히 하지 않는다. 그리고 결정적으로 이 버전은 장기간 구동 시 메모리 누수가 발생하는 문제가 보고돼 있는데, 이 부분은 아직 명시적으로 GC를 여러 번 호출(System.gc.collect)하는 방법 외에는 뚜렷한 해결책이 없는 상황이다. 유니티에서 수정해서 사용하고 있는 모노 런타임의 소스 코드는 인터넷에서 직접 확인 가능하며 개발 과정에서 소스에 프로파일링 코드를 삽입하는 방식으로 활용하면 최적화에 많은 도움이 되는 정보를 얻을 수 있다(https://github.com/Unity-Technologies/mono).

메모리 프로파일링
요즘 출시되는 안드로이드 스마트폰의 경우 2GB를 장착한 모델이 출시되고 있다. 하지만 최신 모델에만 동작하는 게임을 만들지 않는다면 다양한 하드웨어 사양의 스마트폰에 동작하도록 메모리 사용량을 최적화해야 한다. 최적화의 가장 첫 번째 작업으로 프로파일링을 하게 되는데 iOS, 안드로이드 모두 시스템 메모리 프로파일링이 가능하도록 툴을 제공하고 있다. iOS의 경우 Xcode의 Intruments를 통해(Product → Profile → Allocations 선택), 안드로이드의 경우 DDMS 내에서나 adb shell dumpsys meminfo를 통해 확인할 수 있다.

비디오 메모리의 경우 iOS는 역시 Xcode를 통해(Product → Profile → OpenGL ES Driver 선택, Gart Used Bytes 설정) 확인할 수 있지만 안드로이드의 경우 상황이 좋진 않다. 지원하는 GPU별로 Profiler를 제공하기도 하지만 사용상 제약사항이 있어서 실질적으로 많이 사용되진 않는다. 이를 직접 해보고 싶다면 참고자료에 명시된 NVIDIA의 PerfHUD ES나 Qualcomm의 Adreno Profiler의 링크에서 확인해 보길 바란다.

유니티 메모리 최적화
유니티 스크립트 중에 최적화란 관점에서는 중요한 두 가지 함수가 존재한다. 명시적으로 GC를 호출하는 System.gc.collect와 현재 사용하지 않는 리소스를 해제하는 Resources.Unload UnusedAssets란 함수다. 보통 scene이 바뀌거나 메모리를 한번 비우고 가고 싶을 때 호출하게 되는데 두 함수만 열심히 호출한다고 해서 모든 문제가 해결되는 것은 아니다. 

특히 비디오 메모리의 경우 정확한 가용 메모리에 대한 파악이 어려워 일단 문제가 발생하면 화면에 출력되는 모델과 텍스처의 화질을 낮추는 식의 접근을 하게 된다. 필자 또한 비슷한 접근방법을 취했었으나 쉽게 해결되지 않았다. 프로파일링을 거듭한 결과 원인을 발견할 수 있었다. 

필자가 개발하던 게임에서는 GUI를 위한 여러 텍스처들이 사용되고 있었는데 몇 개의 텍스처가 NPOT(non power of two)로 제작돼 있었다. OpenGL ES2를 지원하는 최근의 디바이스들은 NPOT 텍스처를 제한적으로 지원하고 있으나 유니티는 호환성을 위해 NPOT 텍스처의 경우 내부적으로 별도의 처리를 하는 것으로 보인다. 

프로파일링 결과 POT 대비 2배의 메모리를 사용하는 것으로 생각되는데, 비디오 메모리를 많이 사용하는 게임에서는 특히 유념해야 할 부분이다.

결론
이 글의 서두에서도 언급했듯이 메모리 최적화에 정답은 없다. 마지막으로 몇 가지 가이드라인을 제시하며 이 글을 맺고자 한다.

● 시스템 메모리
1. 메모리 프로파일러(Instruments, DDMS)를 늘 곁에 두고 지낸다.
2. 모노 런타임에 기인한 메모리 릭이 발생할 수도 있다는 사실을 기억한다.
3. 모노 런타임 분석에도 도전해 본다.

● 비디오 메모리
1. 어떠한 경우에서라도 NPOT 텍스처를 피한다.
2. OpenGL ES Profiler와 친해진다.
3. 가능하다면 텍스처 압축을 사용한다.
4. 지금 선택한 텍스처의 color bit이 적절한지 다시 생각해 본다.

참고자료
1. Mono runtime(Unity patched) : https://github.com/Unity-Technologies/mono
2. PerfHUD ES : http://developer.nvidia.com/perfhud-es
3. Adreno Profiler : https://developer.qualcomm.com/mobile-development/mobile-technologies/gaming-graphics-optimization-adreno/tools-and-resources
4. iOS texture momory 확인법 : http://blog.zincroe.com/2009/04/how-to-check-iphone-texture-memory-usage-with-instruments/