rein's world

KGC2012: Avoiding Developer Taxes

GDC 2012의 “Robustification Through Introspection and Analysis Tools (Avoiding Developer Taxes” 와 유사한 내용인 듯.

강연 내용에 해당하는 코드(clang-extract)는 https://github.com/sk-havok/clang-extract 에서 볼 수 있다.

내용 요약:

고질적인 게임 개발 비용들(taxes)

직렬화

메모리 정보 보기

스크립트 바인딩

버전 관리

Reflection

C++로 개발할 때 reflection 을 이용해서 푼다. 여기엔 이런 문제가 있다:

  • 어떻게 리플렉션을 만들까?
  • 어떻게 (소스 코드와 데이터에) 싱크가 맞을까?
  • 견고성 (robustness)

수동으로 처리하는 것은 문제가 많다. (buggy!)

regex 등으로 헤더를 처리하는 것도 마찬가지.

그리고…

  1. 하지만 C++에는 reflection이 없다
  2. GCC XML을 써봤다. 느리고 개발도 완성 상태가 아님 1
  3. clang + LLVM frontend를 수정해서 reflection 생성기 개발

C++ headers –> LLVM + clang –> DB 로 변환 그리고 이걸 이용해서 리플렉션 생성, 스크립트 생성. 그리고 정적 분석.

그리고 이 리플렉션을 쓰면 직렬화는 껌(…).

메모리 alignment를 해석해서 구조체의 메모리 배치를 효율적으로 바꾼다거나 하는 것도 가능.

LLVM+clang pass는 견고하고, 싱크가 안 맞는 문제를 방지

DB Consumer pass 는 컴파일 이전에 논리적인 문제를 찾고, 런타임 문제를 컴파일 시간에 잡게 한다.

유닛 테스트 역시 (자동으로) 리플렉션 데이터를 검사할 수 있게 됨.

Language Binding

C++을 스크립트에 바인딩 하려면 문제가 많음. 다만 리플렉션을 쓰면 데이터는 자연스럽게 됨. 그렇지만 callable들은 여전히 힘듬.

함수 trampoline 의 경우 C++ 함수 signature를 처리하기 위해서 생성하는데, 함수 signature마다 bridge 함수를 만드는 쪽이 함수마다 wrapper를 만드는 것보다 싸다.2

메모리 리포팅

Leap frog 테크닉을 쓴다: (일부) 메모리 할당의 타입을 기록하고, 이 오프셋과 사이에 있는 데이터를 유추. (indirect pointers)

그리고 stack walk하면서 이에 대한 데이터를 추정해서 메모리 할당 맵을 offline으로 생성할 수 있게 된다. 그렇지만 일부 컨테이너 류 등에서는 false positive가 있을 수 있고, obfuscated pointer 3 나 타입을 알 수 없는 void* 류는 처리 안됨. 그래도 디버그 모드는 콜스택과 시간을 남겨서 처리한다.

…로 annotated memory data를 만들 수 있게 함.

버전 관리

버전 관리는 쉽지 않음. 매뉴얼하게 하면 버그 나오기도 매우 쉽고 코드 관리도 힘들다.

이걸 snapshot versioning으로 구현. (reflection data의 CRC 이용해서 reflection 결과가 바뀌는 걸 추적)

하지만 이것의 오버헤드 때문에 좀 더 세밀한 단위로 변경 사항을 추적하고 이걸 patching하는 코드를 (semi-automatic하게) 만들게 함.

결론

코드 말고 데이터를 생성하자. 데이터가 더 작고 용도도 많다.


  1. 예전에 C++ 헤더들을 GCC-XML의 python bindiong으로 해석해서 이를 처리하기 위한 script 등을 만드는 작업을 했었다. 흑흑 이걸 좀 더 했으면 GDC감인가! ↩︎

  2. 함수 N개에 대해 400bytes 씩인 것 vs. 40 * N + 60 * 함수 signature 수 정도라서; 아 근데 60인지 manuscript 안 보면 확인을 못하겠다. ↩︎

  3. p ^= 1; 같은 포인터 연산. ↩︎