[넥슨개발자컨퍼런스(NDC) 발표자 소개] 하재승 개발자는 넥슨 라이브인프라실에서 서비스 중인 게임에 필요한 기반 기술을 쌓고 있다. 무점검 실시간 게임 정보 수집 툴 ZERO 개발, 프로파일링 툴 JYP 개발, 던전앤파이터 최적화 담당, 엔씨소프트 서버 버너 등 툴 및 프레임워크를 개발했다.

게임 개발자가 만들어진 게임을 다듬고 최적화하기 위해서는 프로파일링 과정이 필수다. 하재승 개발자는 프로파일링에 대한 중점적인 분석과 더불어, 어떤 코드 영역에서 불필요하게 시간이 소요하는지 파악하는 노하우를 NDC 2018에서 들려줬다. 또한, 먼저 개선할 병목 지점을 찾아내는 방안을 제시하고 업계 종사자들에게 효율적인 솔루션을 제시했다.

특히 안드로이드 기반에서 C++의 프로그래밍 언어로 만들어진 앱에 사용할 수 있는 프로파일러 프로그램을 찾기 어려운 현재의 개발 상황을 파악해, 활용 가능한 안드로이드용 네이티브 프로파일러들을 소개하고 대안을 제시했다. 더불어 하재승 개발자는 최적화를 위한 프로파일러의 선택과 적용과정에서의 시행착오를 공유하며, 실무자들이 안드로이드 프로파일링 업무에 바로 적용할 수 있는 정보를 공유했다.



프로파일링이 필요한 이유는 단순하다. 게임의 속도를 빠르게 하기 위해서다. 최신 사양의 모바일 기기는 다소 프로파일링의 효용이 떨어지지만, 낮은 사양의 모바일 기기에서 게임이 원활히 작동되게 하기 위해서는 프로파일링이 필수다. 이런 프로파일링 작업도 우선순위가 있다. 모든 함수를 최적화하기보다, 가장 문제가 되는 부분을 고치는 것이 좋다. 또한, 이런 우선 작업이 '일 한 티가 난다'고 하재승 개발자는 귀띔했다.

만약 90의 문제를 1/3으로 줄인 경우와 10의 문제를 1/10으로 줄인다면, 앞선 경우는 30으로 크게 줄일 수 있지만 뒤의 경우는 1이 되었을 뿐이다. 결과적으로 일은 후자가 더 하고 복잡했지만 결과는 전자가 낫다는 것이 하재승 개발자의 설명이다.

▲ 프로파일링도 우선순위가 있다

하재승 개발자가 소개한 프로파일링 방식은 '코드 삽입'과 '샘플링'으로 나뉜다. 코드 삽입은 관심이 가는 함수나 문제 등에 정보를 수집하는 코드를 삽입해 정보를 측정한다. 이 방식은 적은 실행으로도 꽤 자세한 정보 수집이 가능하다는 장점이 있다. 그러나 코드를 삽입하는 방식이어서 측정 대상에 직접 영향을 끼칠 수 있다.

두 번째 방식인 샘플링은 코드 영향 없이 일정 시간마다 어떤 영역에서 실행되는지 콜스택을 수집한다. 다만, 통계 방식이어서 충분히 실행해야만 의미 있는 값을 가질 수 있다. 샘플링 방식은 코드 삽입과 다르게 측정하고자 하는 프로그램에 최소한의 영향만 끼치고 왜곡되지 않은 정보를 수집할 수 있다는 장점이 있다.

간략한 프로파일링 소개 이후, 하재승 개발자는 '안드로이드에서 유니티 프로젝트 네이티브 프로파일링 삽질기'를 소개했다. 이 경험은 강연자가 비공개 모바일 프로젝트에 투입됐을 당시 경험담이다. 해당 게임은 성능 좋은 최신폰이나 아이폰에서는 특별한 문제가 발생하지 않았다. 만약 아이폰에서 문제가 발생했다면, 인스트러먼트(Instrument) 프로그램을 사용해 문제를 파악했을 거라고 덧붙였다.

당시 하재승 개발자가 당면한 문제는 모바일 안드로이드 게임 최적화 경험이 없었고, 모바일 기기는 2015년에 출시된 안드로이드 5.0.2(롤리팝) 버전, 사양은 퀄컴사의 스냅드래곤 410이었다. 더군다나 제작사에서 아이폰 느낌이 나도록 커스텀을 심하게 한 상태였다. 또한 개발팀에서는 바로 측정할 수 있도록 루팅 하지 않았으면 한다는 요구가 있었다. 여러 제약사항이 프로파일링 작업을 힘들게 했다.

프로파일 작업을 위해 먼저 선택했던 툴은 '안드로이드 스튜디오', '안드로이드 NDK 프로파일러', '유니티 프로파일링', 'NVIDIA Tegra 프로파일러'. '스냅드래곤 프로파일러'이다. 먼저 떠올린 '안드로이드 스튜디오'는 자바 전용 분석 도구여서 시도조차 하지 못했다.

▲ 바람직한 프로파일링 프로그램, 사용할 수만 있다면...

다음으로 선택한 '안드로이드 NDK 프로파일러'는 Linux gprof의 안드로이드 포트를 연결, 사용하려면 빌드 시 링크를 해야 한다. 그러나 2015년 이후 지원되지 않고, 해당 repository가 필요하다면 fplutil을 사용하라고 안내될 뿐이다. 하재승 개발자는 "그래서 fplutil을 따라가 봤더니 지원하는 기기, 지원 안 하는 기기 2개씩을 적어놨더라... 현재 프로그램을 테스트라도 하는지 모를 정도"였다고 소개했다. 결국 '안드로이드 NDK 프로파일러'도 사용하지 못했다.

이후 하재승 개발자는 '유니티 내장 프로파일러'를 살펴봤다. '유니티 내장 프로파일러'는 대체로 많이 사용되는 최적화 도구이다. 빠르게 상황을 파악하는 데 도움을 주고 설정만 잘하면 모바일에서 최적화도 가능하다. '유니티 내장 프로파일러'로 콜스택을 보려면, Deep Profile 옵션을 켜거나 관심 있는 영역에 직접 코드를 추가해야 한다. 모든 함수의 앞뒤에 정보를 추가하는 코드 삽입형 프로파일러이다.

좋은 도구이지만 '유니티 내장 프로파일러(Deep Profile)'의 한계도 존재한다. 우선 프레임이 눈에 띄게 떨어지고 메모리 사용량에 따라 프로파일리 불가능한 경우도 있다. 또 자주 호출되거나 콜스택이 깊을 때 결과가 왜곡된다. 일례로 프로젝트 내에 memoization 등을 활용하면 속도가 꽤 빠르지만, 한 프레임에 약 2,000번 호출되는 함수도 존재한다. 하재승 개발자는 '유니티 내장 프로파일러' 사용 시 문제없는 함수가 마치 심각한 성능 저하의 원인으로 보이는 경우가 있다고 전했다. 결국 '유니티 내장 프로파일러' 역시 쓸 수 없었다.

이후 '스냅드래곤 프로파일러'와 'NVIDIA Tegra 프로파일러'를 사용하려 했지만 모두 자사의 최신 칩 전용이서 사용하지 못했다. 두 프로그램은 제조사가 제공하는 프로파일러인데, 우선 NVIDIA Tegra 칩을 사용하는 모바일 기기는 매우 적어서 효용이 낮다. 스냅드래곤은 비교적 많지만, 하재승 개발자가 최적화하고자 하는 기기는 지원 대상이 아니었다. 더군다나 커스텀된 기기여서 프로파일링 프로그램은 설치할 수도 없었다.

이런저런 시도 끝에 하재승 개발자가 선택한 프로그램은 'simpleperf'이다. 'simpleperf'는 CUI 프로파일링 툴로, '안드로이드 NDK r13b' 이상에 포함된다. r16b 기준으로 여러 헬퍼 파이썬 스크립트를 함께 제공한다는 장점도 있다.

하재승 개발자가 'simpleperf'를 먼저 선택하지 않았던 이유는 당시 사용했던 '유니티5'의 NDK 버전은 r10e여서, NDK를 새로 받아야 하는 귀찮음 때문이었다. 또 같이 제공된 'app_profiler.py'가 문서대로 작동하지 않기도 했다. 개발자는 당시에 커스텀 기기의 문제일 것으로 생각했다.

그래도 'simpleperf'를 어떻게든 사용할 수 있도록 하재승 개발자는 다음과 같은 작업을 했다.


프로파일링 테스트를 위해 하재승 개발자는 마인크래프트로 '이동에 따라 주변에 지형을 계속 생성'하는 게임을 만들었다. 결과적으로 'simpleperf'는 잘 작동했다. 하지만.



위와 같은 결과물이 코드 10만여 줄 이상으로 출력됐다. 작동은 되나 저품질 UI가 'simpleperf'의 문제였다.

'simpleperf'의 저품질 UI를 개선하기 위해 하재승 개발자가 선택한 프로그램은 'FlameGraph'이다. 개발자는 "좋은 결과는 좋은 비쥬얼에서 나온다"고 소개했다. 아쉽게도 두 프로그램은 자동으로 연동되지 않는다. 그러나 다행히도 두 프로그램의 파일 형식이 동일한 덕분에 리눅스에서는 연결할 수 있다.

1) 리눅스 계열 OS로 'simpleperf'의 perf.date를 복사 후
2) perf script perf.date --sympath binary_cache > out.perf 한 뒤
3) 이후 flamegraph 공식 홈페이지를 참고한다.

▲ 보기 쉽도록 결과를 정리해주는 flmaegraph (출처: flmaegraph 사이트)

'simpleperf'와 'FlameGraph'의 연동에도 한계는 있다. 하재승 개발자는 데이터 수집이 목적인 프로그램이 256개까지만 쌓고, 더 수집 못 하는 경우가 발생했다고 전했다. 이 문제의 원인은 아직 파악하지 못했지만, 유니티 엔진 문제가 아닌 안드로이드 OS 문제라 추정하고 있다. 현재 개발자는 될 때까지 자동으로 재실행을 반복하는 툴을 개발해 문제를 해결했다.

결과적으로 하재승 개발자가 맡은 비공개 프로젝트의 최적화는 성공적으로 이루어졌다. 최적화 결과는 아래와 같다.


당시 발견한 문제는 유니티 개발자라면 자주 겪을 수 있는 문제였다. 유니티로 개발을 진행하면 당연히 object pool을 자주 사용하는데, pool에서 객체를 꺼낸 뒤 동적으로 고쳐야 하는 경우가 있었다. 그런데 if문과 특정 함수에 따라 AddComponent가 실행되는 문제가 발생했다. 이 문제를 추적한 결과 100% 객체가 같은 Component를 추가한다는 것을 확인했고, 이후 pooling 할 때 미리 추가하게끔 변경했다. 이 작업으로 50% 이상 성능 개선이 이루어졌다.

하재승 개발자는 스스로 개발한 simpleperf와 flamegraph 연동 프로그램을 오는 주말(28일)까지 완성해 자신의 깃허브에 올리겠다고 강연 막바지에 약속했다.


■ Q&A

Q. 안드로이드에서 유니티 엔진을 사용하면서 최적화에 어려움을 느낀 부분은?

하재승 : 유니티는 단순 게임 엔진이어서 프로파일의 어려움은 크게 없었다. 다른 엔진도 동일하다. 오히려 안드로이드 OS 최적화가 장애였다.


Q. simpleperf에 필요한 빌드는?

하재승 : 개발 빌드가 필요하다. 개발 이후에는 apk 안에 symbol이 있는데, 그것도 필요하다.


Q. 유니티를 사용하면서 "이게 왜 돌아가지?" 싶었던 게 있나?

하재승 : 넥슨은 유니티와 계약을 통해 엔진 소스 접근 권한이 있다. 코드를 보면서 최적화를 했기에 신경은 덜 썼다.


Q. 일반적인 모바일 기기에서 프로파일 프로그램을 추천한다면?

하재승 : 개인적으로 아이폰에서는 인스트러먼트(Instrument). 가장 상식적인 프로그램이다.