3. 황의권
서든어택2 프로그램팀 팀장
2007 - Tibero(RDBMS)
2009.봄 - 가을 카바티나스토리
- 2011.여름 메이플스토리
- 2012.봄 아이러브커피 외
- 현재 서든어택2
발표자 소개
취미 : 코스츔 플레이어
4. 빌드 시간은 개발 생산성에 밀접한 문제
- 특히 게임에선 개발 iteration 비용이 중요
누구나 얘기하지만 잘 챙기긴 어렵다
왜 빌드 발표요?
출처 : compiling (https://xkcd.com/303/)
5. 간단히 검색해 본 NDC 관련 세션
• Unity Build로 빌드타임 반토막내기 – 송창규 [NDC10]
• 레가시 프로젝트의 빌드 자동화 – 최재훈 [NDC11]
• 효율적인 모바일 게임 개발을 위한 모바일 빌드 시스템과 모바일 배포 시스템 구축 노하우 –
윤보선/김태효 [NDC15]
• 하루에 3번! 삼시세끼 빌드 만들기! – 안현석 [NDC15]
빌드 엔지니어링은 단골 주제
6. 다양한 개발 환경은 말할 것도 없고,
하나의 개발 환경에서도 수많은 상황을 고려해야 함
빌드 엔지니어링은 넓고 깊은 주제
7. 사례로 살펴보는
MSVC
빌드 최적화
팁
제목 부연 설명
서든어택2 서버 개발 중 있었던 일
Microsoft Visual C++
빌드 생성 과정에 대한 엔지니어링
Rule of Thumb 중심 + 가벼운 연관지식
9. 게임 서버 주요 환경
• 언어 환경 : VS2013, C++11(의 일부)
• 주요 라이브러리 : boost 1.5x, Intel TBB 4.x
• 저장소 : 아직은 Perforce
그 외 개발 스택
• C#, Python/flask
• MSSQL, Redis, MongoDB
• Linux, Git, AWS, …
개발환경
10. Incredibuild – 상업 분산 빌드 솔루션 사용 중
모든 빌드 관여 머신에 SSD 장착
-> 적절한 돈으로 해결할 수 있는 건 이미 해둔 상태
-> 대신 빌드 머신은 평범
개발 환경 - 빌드 관련 외부 환경
11. “서버 프로젝트 CI빌드가 느려요. 언제부터인진 몰라도..”
“원인은 파악 됐나요?”
“실마리는 아직, 해결하려면 좀 봐야 할 것 같아요”
“그럼 이번 릴리즈 끝난 후 봅시다”
이 대화가 세 번 쯤 반복된 후, 드디어 뚜껑을 열어보기로 결정
회의에서 나온 이야기
13. 전체 개발 사이클의 병목은 아니지만 commit이 망설여짐
상식 밖의 빌드 시간
• 당시 소스파일 크기(LOC) : 약 175K
• 데디케이티드 서버는 별도
심각성 분석 – 시간 문제
175K
도표 : 개발일정에 따른 LOC 변화
코드 정리
큰 마일스톤
14. 큰 용량이 기능과 성능에 영향을 주지는 않았음
하지만 바이너리가 109메가면 이거 너무 심한 거 아니오
심각성 분석 – 용량 문제
15. 가설 1 : 이런 이상 상황이 갑자기 발생한 특정한 시점이 있지 않을까?
진행 1 : 로그! 로그를 보자!
전략 선택 – 1단계
16. 이분검색을 해서 문제가 되는 커밋을 찾자
- 총 서버 변경 리비전 수 : 약 3000
-> 이분검색 12(2^12=4096)번, 간단하네?
로그를 분석한다
17. 빌드 시간 검색
• CC.NET CI 빌드 로그
• 검색하기 너무 불편
• 검색툴을 만들까? -> 배보다 배꼽이…
• 직접 빌드해보면 되지 않을까?
• 12번 리비전이동/빌드
• 하지만 문제가 생긴 시점이 특정된다는 보장이 없다!
로그분석 – 시간 추적
18. CI 빌드 바이너리는 저장소에 커밋되지 않고 있었고orz
대신 서비스 서버 관리 툴이 쓰는 중간 저장소에 드문드문 로그가 있었다
- 최초 서비스 배포 버전(리비전 약 1000)이 이미 30MB
- 시간에 따라 거의 선형 증가하는 모습
로그분석 – 용량 추적
30MB
109MB
19. 과거 기록 분석은 FAIL,
지금의 상황만 가지고 문제점을 찾아 돌파하는 정공법으로 전환
가설 2 : 바이너리 용량이 크게 나오는 게 빌드 시간에 영향을 주지 않을까?
진행 2 : 용량! 용량을 보자!
전략 선택 – 2단계
21. 단순화한 MSVC 컴파일 과정
전처리기
컴파일러
링커
전처리 지시자 처리(#include,#define, …)
모든 헤더파일이 풀린 큰 소스파일 생성
한 소스파일을 컴파일하여 obj 파일 생성
obj 파일과 lib 연결
exe/lib/dll 생성
22. 단순화한 SA2 게임서버 빌드 과정
코어 lib 프로젝트
코어.lib
공통로직.lib
네트워크.lib
데이터베이스.lib
역할별 서버
프론트엔드서버.lib
매칭서버.lib
커뮤니티서버.lib
(그 외 로직 서버들)
단일 런처
서버런처.exe
23. 어떤 문제인지 명확히 모르는 상태에서, 해결책 try
용량 관련한 빌드 옵션 체크 리스트
• 최적화 옵션 O1로?
• 디버깅 정보를 빼고 해볼까?
• 문자열 풀링이 안 되어 있다거나?
샷건 디버깅 – 용량 편
-> 효과는 0.1MB 미만
-> 위와 같음
-> 릴리즈 빌드에서는 강제 켜짐
실패
25. 맵 파일 : 빌드 때 옵션을 주면 생성되는 바이너리 구조 명세 TXT
영역별 시작 주소와 길이, 코드 영역의 각종 기호 포함
맵 파일 분석
Start Length Name Class
0001:00000000
0001:00025f30
…
0002:00000000
0002:00000b40
…
0002:000078e0
0002:063c7bf0
0003:00000000
…
00025f2cH
002ccb00H
00000b40H
00000008H
0637bf20H
00088b70H
000029b8H
.text$di
.text$mn
.idata$5
.CRT$XCA
.rdata
.xdata
.data
CODE
CODE
DATA
DATA
DATA
DATA
DATA
수상하다!
.rdata : (주로) 전역 상수,
문자열같은 것이 위치
26. DUMPBIN : MSVC 내장 툴
실행파일/라이브러리 구조를 조금 더 면밀히 보고 싶을 때 사용
File Type: EXECUTABLE IMAGE
Summary
2606000 .data
28000 .pdata
6455000 .rdata
9000 .reloc
1000 .rsrc
31D000 .text
1000 .tls
DUMPBIN 활용 – 기본 정보
0x6455000
= 105205760
(100MB)
27. DUMPBIN /section:.rdata
File Type: EXECUTABLE IMAGE
SECTION HEADER #2
.rdata name
6454BFA virtual size
31E000 virtual address (000000014031E000 to 0000000146772BF9)
6454C00 size of raw data
31C800 file pointer to raw data (0031C800 to 067713FF)
…
-> 문제가 맞아 보임, 하지만 아직 정보가 부족
DUMPBIN 활용 – 섹션 상세 정보
34. C++ 코딩 관련 기본 상식을 잘 지키는지 체크
• 헤더, 특히 시스템 헤더 include를 최소화
• 모듈 의존성을 단순하게
• 헤더와 몸통(cpp) 엄격한 분리 등
빌드 옵션 조절
• 디버깅 레벨 조절
샷건 디버깅 – 시간 편
35. • PCH(Pre-Compiled Header) 사용 중
• 헤더와 몸통의 분리
• 손으로 짠 모듈에서는 엄격히 지키는 편
• Pimpl idiom도 일부 선택 활용 중
• 자동 생성 코드에서 소홀한 점 추가 발견
• 시스템 / 외부 헤더 파일 관리
• 템플릿 전방선언을 끼얹을 요소 발견
• (헤더에서 boost 타입 참조하는 일부 경우)
우리 프로젝트 체크리스트
36. 자주 포함되는 코어 헤더들의 시스템 헤더 include를 정리하고
자동생성된 코드에도 규칙을 엄격히 적용한 결과
PacketAll.h(모든 패킷 헤더를 다 뭉쳐본 파일)의 전처리 끝난 LOC 변화
183,620 줄 -> 5,278 줄
줄어든 시간 :
• 10초 내외로 거의 변화 없음 orz
• 그 외 디버깅 레벨 등 옵션 조절도 모두 미미한 영향
코드 정리 결과
39. 봐야 할 기호의 수가 너무 많은 경우
• 네임스페이스를 열어버린 경우(예 : 헤더에서 using namespace std;)
헤더파일 의존성이 복잡한 경우
• 컴파일 타임에 드러나지 않고 링크 타임에 드러난다거나?
• 이번에도 자동 생성 파일이 문제인가?
분산 컴파일은 되는데 왜 분산 링크는 없나?
• 분산된 걸 모으는 게 링크인데 링크를 또 분산하면 분산된 링크를 모으는 링크가..
브레인스토밍 : 링크 시간에 관하여
40. 여기서 떠오른 팀원과의 대화
Q: 근데 빌드하는 데 15분이면 로컬 작업도 힘들잖아요?
A: 아뇨 로컬에선 디버그 빌드로..
디버그 빌드는 : 2분 30초
..아 이 이야기를 분명 처음에 들었는데
잠깐!
49. 들인 시간
• 업무시간(관여한 모든 사람의) 8시간 남짓
한 일
• 시행착오(샷건 디버깅), 야크쉐이빙(코드정리)에 시간 허비
• Map 파일 분석과 DUMPBIN을 써서 용량문제 발견
• 자동생성코드 위치이동으로 해결
• LTCG 옵션 꺼서(…) CI빌드 시간 단축
회고
50. 소위 삽질이 많았다
- 샷건 디버깅은 핵심 문제에 접근하는 데에는 비효율적
- 귀찮아도 문제를 분석하고 접근하는 버릇을 들이자!
- 실무에서 멀어졌던 팀장이 일 잡았을 때의 폐해
교훈
51. 링크 타임 코드 생성의 단위 비용을 줄일 순 없을까?
- 다단계 링크 구조를 개선해보면?
- Unity Build를 쓰면 더 빠를 것 같은 예감?
- 마지막엔 lib들끼리 링크하는데 소용 없을지도..
Future work