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; 같은 포인터 연산 []

NDC 참관기: 덤프 파일을 통한 사후 디버깅 실용 테크닉

슬라이드는 “NDC2012 덤프 파일을 통한 사후 디버깅 실용 테크닉“에서 볼 수 있음.

넥슨 김이선 님의 발표.

내용 요약:

돈을 벌기 전에는 배포 전 디버깅이 중요하다.
라이브 상황에서는 배포 후 디버깅이 더 중요해진다.

덤프 파일

특정 상황 — 특히 안좋은 상황 — 에서 프로세스 상태를 디버깅 용으로 남기는 것.
풀 메모리 덤프를 남기면 좋지만 커서(…) 미니 덤프를 남긴다.
상용 서비스까지 갔다면 미니덤프를 남기고 이를 수집해서 디버깅하는 시스템은 필수.

하지만 미니 덤프 디버깅의 현실은…

  • 덤프 파일을 열었는데 심볼이 없다고 어셈블리 코드가 보이는 경우
  • 여기서 죽으면 안되는데 왜 죽었나 싶은 경우?
  • 콜스택이 깨졌다거나
  • 풀 메모리 덤프를 남겼는데 내가 보려는 객체는 어디에?

덤프 파일과 pdb 파일 연결하기

  • 미니덤프 헤더는 RSDS 헤더, GUID, age, pdb-path 등이 들어감
  • pdb, exe, minidump는 이 GUID로 연결된다
  • 심볼서버는 당연히 써야
  • 보안 솔루션 (더미다 류) 을 사용하면 덤프 파일의 GUID가 바뀐다
  • 하지만 COFF file format의 링크 시간을 기록한 타임 스탬프는 유지
  • 클라이언트 덤프 수집단계에 미니덤프와 해당하는 PDB를 맺어주는 단계를 넣자(클라이언트 버전 정보; 타임 스탬프를 이용해서)

여러 개의 덤프 파일을 분석할 때

일어나지 않을 것 같은 일도 덤프 파일이 많으면 분석할 수 있음[1]

  • 분류 기준으로 EIP, callstack, 게임에 따른 정보(맵, 장비, …)
  • 외부 모듈이나 말이 안되는 곳이면 어떻게하지? 클라는 환경을 통제할 수 없으니 외부 변수를 추적한다
  • EIP와 다른 변인 (OS, CPU, GPU) 등을 보면 예상되는 것과 다른 분포인 경우가 있다 (=환경 편재)
  • OS라면 드라이버 버그, CPU의 문제 (보통은 mainboard), GPU 문제, 외부 모듈 (보안 프로그램이나 해킹 툴)

사례 분석

 

  1. DF의 코드 베이스를 VS 2003에서 VS 2008로 이전함.
  2. 특정 유저 (소수) 에서 채널 선택하는 순간 HeapAlloc/HeapFree 중에 크래시 하는 버그 발견
  3. Heap-corruption이라,
    • 게임 시작에서 채널 선택하는 부분까지의 코드를 리뷰
    • 이 영역에 많은 수의 trap code와 HeapValidate 코드 삽입
    • 하지만 문제는 발견되지 않음
  4. 모듈 목록에 환경 편재가 있음: BFCO0GAF.dll 등의 알파벳/숫자가 섞인 특정 패턴의 모듈 발견
  5. 해당 dll이 악성 프로그램이 삽입한 것; 2003과 2008의 heap 구현이 달라서 생긴 일

콜 스택이 깨지는 경우

  • EBP, ESP 중 하나만 깨져도 콜 스택을 복원할 수 없다
  • 이를 다음 휴리스틱으로 극복
    1. 스택 메모리에서 주소를 추려낸다 (모듈 정보의 주소 영역을 이용해서)
    2. 스택 메모리에서 스택 주소를 추려낸다
    3. 1 + 2를 해서 콜 스택을 추정
  • 프로그래머의 해석이 필수

힙 메모리 문제

  • 메모리 덤프가 있으면 디버깅에 큰 도움이 된다
  • 하지만 모든 메모리 객체에 접근하는 건 쉽지 않음
  • 메모리를 exhaustive하게 검색
  • vfptr 값 이용
  • typeid(T)의 구현 형태를 이용해서 타입 이름을 이용해서 검색

이걸 포함한 오픈소스 툴을 공개

 

 

감상:

이번에 네 개 세션 밖에 못들었는데, 그 중 제일 괜찮은 강연이었다. 다음 세션을 회사 돌아갈 시간이라 못 들은게 아쉬움.

내가 예전에 했던 유사한 시도는 통계적으로 분류하고 / 적당한걸 찾을 수 있게 도와준다 정도였는데 (VS 안띄우게 하는게 목표였으니..) 이렇게 실제 사례를 바탕으로 한 내용을 알고 있었다면 훨씬 좋게 할 수 있었을 듯.
그리고 바이너리가 수정된 경우의 처리는 제대로 못했는데 — ipkn 패치해서 돌아가게 수정 — 이 부분에 대한 정보도 얻게 되어서 좋았다.

게다가 강연자 분이 “이런거 되는걸 모아서 새로 짜서 공유했습니다”라고 말한건 대박(…).
아마 `지식 공유’란 점에서 모델이 될만한 수준.

summerlight 님이 녹화한 영상도 토런트 시딩되고 있을테니 (촬영 가능한 세션이었음) 관심 생기신 분들은 한 번씩 보는걸 추천합니다.

  1. 이건 이전 회사에 있을 때 내가 포스트모텀 디버깅, 그것도 최대한 많은 수를 수집해야 한다고 주장한 이유와 같음 []

VS 2010 빌드 속도 올리기

C++만 가지고 쓰는 얘기.

작년에 지급 받은 머신 셋팅을 수정해서 어떻게 빌드 속도를 올렸는지 정리. 아마 한동안 업으로 Win32에서 C++ 할 일은 없을 것 같아서 기억이 사라지기 전에 정리.
저 머신 자체가 내 손을 이미 떠나서 틀린 부분이 있을테니 발견하면 지적 좀 해주시길.

머신 스펙은  대략,

  • CPU: intel i7-2600
  • DRAM: 16 GiB
  • Storage: intel SSD 80 GB + SATA HDD 500 GB

그리고 SSD 용량이 적어서 아래 같은 일을 추가로 했다.

  • 4 GiB의 RAM drive를 할당하고, TEMP, TMP에 해당하는 디렉터리를 RAM 드라이브에 맵핑
  • VisualStudio와 Windows Platform SDK는 SSD에 설치
  • 기타 프로그램(오피스나 LaTeX)은 HDD에 설치
  • 소스 체크아웃은 HDD 쪽에
  • 빌드 스크랫치도 HDD 쪽에
  • IPCH 파일 저장되는 위치 변경. 방법은 민장님 블로그의 “Visual Studio (C/C++) 2010 몇 가지 팁” 참조

대략 이 정도만 설정해도 꽤 빨라지더라. 이전 머신 (Q8600 + 4GiB RAM + HDD only) 에서 빌드할 때 20+분 걸리던게 2+분 수준으로 줄어듦. 예전엔 인크레디빌드 써봐야 빌드 시간이 18분 수준이라 안 쓰느니만 못한 꼴을 몇 번 봤고, 머신 변경 후에는 아예 안 쓰게 되었음. 최대 코어 수도 12개던가로 제한되어 있기도 하고(구입한 라이센스 문제).
그 원인이 된건 다음 것들인듯.

  • IO가 두 디스크에 분산되서 소스 로드 속도가 빨라졌다; 어차피 Windows 헤더와 개발 중이 프로그램 헤더가 모두 로드 되야 하니…
  • VS가 북키핑 용으로 쓰는 듯한 일부 .log, build.unsuccessful[1] 파일들 (내용물이 없거나 매우 짧은) 을 임시 디렉터리에 해당하는 곳에 잔뜩 쓴다. 이게 RAM drive로 옮겨가서 디스크 IO 감소 + 분산
  • CPU 아키텍쳐 변화 (무려 두 세대) + 코어 수 증가(4에서 8로)로 단일 파일 컴파일 속도 증가
  • Win32/x64가 다 있는 경우 개발 중에는 x64 빌드만 사용. 빌드 / 테스트 수행 시간 모두 짧더라

등의 이유로 빨라졌다고 생각한다.

 

궁금한 것:

  • 모든 소스가 SSD 두 개, 혹은 SSD RAID 위에 올라가 있다면 좋겠지만 그런 설정은 받아본 적이 없으니 궁금증만…
  • 빌드 스크랫치 전체를 RAM 위에 올려놓고 싶긴한데, ECC RAM 아니면 살짝 불안하기도 하고, 그 정도로 메모리를 풍족하게 써본 적도 없고…
  • 여기에 unity build를 섞었다면 어땠을까 하는 것..

해보신 분들의 의견을 구합니다(…).

 

PS. 근데 가장 좋은건 Google이나 Microsoft 처럼 빌드 팜이 있는 거겠지만, 그 정도의 엔지니어링을 하는 건 아니었으니…

 

  1. 정확한 파일 이름은 누가 좀.. []

node.js 혹은 CPS 단상

지난 주에 둘째 태어나서 병원에 있는 동안 SNS와 블로고스피어를 스쳐지나간 글 중 제일 눈에 띄는 부분이 “CPS와 node.js” 관련된 내용이었다.

iPhone은 정리에는 그다지 좋은 기기라고 못하겠다. 내가 생각하던 문맥을 저장하기엔 내 활용 방법의 문제거나 기기가 부족하거나; 여하튼 기억나는 글들을 여기 정리하자면 (twitter는 정리가 불가능하니 제외),

웹 서버 아키텍처와 프로그래밍 모델의 진화 http://ricanet.com/new/view.php?id=blog/110903 (node.js의 I/O 모델을 점검하기에 좋은 글)

Node.js의 소개글 들에 대한 유감 http://himskim.egloos.com/3810574

이에 대한 홍민희 님의 글 http://blog.dahlia.kr/post/18300740247

그리고 다시 (홍민희 님의) 비동기 I/O 프로그래밍 모델 관련 글 http://blog.dahlia.kr/post/18355002657

 

이에 대한 내 생각을 정리하자면:

1. node.js는 hype이라 생각한다. node.js 광신도(…)들이 말하는 장점은 이미 오래된 얘기고 (이벤트 기반의 비동기 I/O 모델, 단일 이벤트 루프와 그로 인한 싱글 스레드의 단순함), 이런 시도를 하는게 node.js만 있는 것도 아니다. 다만 세상에 JavaScript 프로그래머가 많기 때문에 그렇게 느껴지는 것 뿐이지. 서버 혹은 백엔드 프로그래밍에선 너무 오래된 — (게임) 서버 프로그래밍을 주로 하던 이들 사이엔 이게 왜 이슈가 되는지 궁금해하는 사람들이 나온다 — 이슈라서 말이지.

다음 언어들로도 `흔히’ CPS 스타일의 프로그래밍을 할 수 있다. 그리고 이 언어들 모두에서 node.js에서 `하면 안된다’라고 주장하는 한 이벤트에서 긴 계산을 하는 일도 실행 모델에 따라선 해도 된다. 그리고 이 언어들에선 `흔히 하는 일’에 불과하다.

Go: goroutine 자체가 일종의 CPS 스타일 프로그래밍이다. 그리고 일반적인 이벤트 큐 처럼 쓸 수 있는 channel 개념 역시 존재한다. 그리고 이 두 개념을 최대한 활용해서 만든 언어가 Go다. 자세한 내용은 내 과거 포스팅을 참조하자 (https://rein.kr/blog/archives/tags/go) 개인적으론 이런 류의 I/O 비동기 / 멀티플렉싱을 생각하고 짠다면 Go가 가장 깔끔한 프로그램이 나온다고 생각한다.[1]

C#: C#의 익명 함수 (delegate라고 부르던데) 역시 이런 CPS 스타일로 프로그래밍 할 수 있다. Nexon의 `마비노기 영웅전’과 `마비노기 2’도 이런 개념을 활용해서 서버를 구현했다. 2011년 NDC 세션의 내용을 정리해놓은 것처럼 (https://rein.kr/blog/archives/2696https://rein.kr/blog/archives/2671) 이걸가지고도 JS에서 흔히 하듯이 성공/실패 처리를 cps로 작성한다.

C++: C++11의 lambda 혹은 그 이전의 std::function[2] 을 이용하면 역시 CPS 형태의 코드를 작성할 수 있다. 내가 작성한 게임 서버 라이브러리 역시 이를 이용한 CPS 스타일의 코드가 포함되어 있다.

 

2. node.js가 단일 스레드, 즉 단일 문맥만으로 처리하기 때문에, computation-intensive한 작업은 처리할 수 없다. 그래서 어쩔 수 없이 백그라운드 태스크 큐 등을 써야하고, 이로 인해 생기는 지연 시간(latency) 문제는 극복할 수 없다.
게임 서버에서도 node.js처럼 (혹은 그보다 낫게)

  • 복수의 스레드가 네트워크 이벤트(read/write)를 비동기로 처리하고,
  • 네트워크 이벤트를 서버 메인 스레드(그러니까 논리적으론 단일 스레드 프로그램)로 전달하고,
  • 서버 메인스레드가 내부 로직을 처리해서 다시 네트워크로 보내는

구조를 사용한 서버들이 있다. 다만 이 경우엔 계산 량이 많은 경우 지연 시간이 길어진다. 그것도 게임 상에서 상호 연관이 없어 보이는 부분들 사이에 영향을 주는 형태로 -_-;
게임 서버는 웹과는 좀 다르게 persistent storage에 다 저장하지도 못하고, 각 연결 간에 상호 작용도 많아서 어쩔 수 없이 생기는 문제이긴하지만 맨 첫 글에서 얘기하는 것 처럼 node.js가 `유니크’한 것은 아니다. JavaScript로 서버 프로그래밍을 진행한다는 것은 꽤 참신하지만 말이다.

그리고 서버 메인스레드가 1개가 아니라 복수개 일 수도 있고 — 내가 본 코드에선 이게 주류다 — 걔 중에도 IO 스레드가 full swing을 하돼 non-blocking한 알고리즘을 이용해서 복수의 큐를 사용하고, 블럭킹을 제거한 구현체도 있다.

 

3. 프로그래밍 모델은 CPS는 `쉽게 만들어낼 수 있지만’, 프로그래머에겐 꽤 골치거리라 생각한다. 홍민희 님의 말대로 가장 쉬운 형태는 co-routine일 것이라 생각한다. CPS처럼 이전 문맥에 대해 생각할 필요도 없고, 불필요한 nesting (node.js말하는 거임) 도 필요치 않다. 다만 이 경우에는 언어적인 수준에서 지원하지 않으면 안된다. 불행히도 내가 사용하는 주요 언어인 C/C++의 경우 co-routine을 사용할 수단은 OS fiber수준 정돈데, 이 경우엔 다른 동기화 객체들을 쓸 때, 동기화 단위가 스레드라서 fiber끼리 switching할 때 지옥을 보게 된다. 하지만 node.js 정도의 프로그래밍 모델에선 이게 가장 간편할 것이다. 각 연결 별 문맥끼리 상호작용이 적다면 더더욱 말이다.

  1. 이 언어를 만든 회사가 Google이란 생각을 하면 더 그렇다 []
  2. 혹은 그 이전 구현체인 std::tr1::function, 혹은 이거랑 별반 다를게 없는 boost::function을 생각하면 더 이전부터 []

2011, 내 프로그래밍 언어는…

twitter에 #code2011 에 덧붙여 글 하나 쓰기. 2011년에 주로 사용한 언어는,

  • C++
  • Python
  • C#
  • JavaScript
  • SQL
  • Lua
  • Go
순으로 쓴 것 같다. (트윗에는 Lua를 못 적었음…)

C++

우선 주 언어로 쓴 C++. 내년에 공개될 모 서버를 작성했다. 사내 테스트까지 나간 것 중 C++11을 사용한 것으론 처음일듯.
그리고 옮겨간 팀의 서버 역시 C++로 작성하고 있다. 크래시 보고 도구도 밑 단은 C++…

내가 주목한 특징을 꼽자면,

  • 바위에 끌로 새기는 것 같이 작업함 – 집중해서 빡쎄게;
  • lambda로 이전에 boost 쓰는 걸 대체 – C++11 만세!(…)
  • 괜찮은 성능 – 내가 사용하는 모든 언어 중 가장 월등한 성능을 자랑한다
  • Bare metal 위에서 돈다 (위랑 같은 의미?)
  • 원하는 수준만큼 저 수준에서, 원하는 만큼 세밀하게 제어할 수 있다
그래서 이걸로 여전히 게임 서버를 짜고 있는듯…
 

Python

팀 내의 코드 생성기나 대부분의 스크립트는 python으로 작성한다. 올해에는 대략,

  • 데이터 변환 도구; .xls -> sqlite -> (C++ internal form) 으로 몇 가지 데이터를 옮겼는데, 이 중 첫 단계는 모두 python에서 해결
  • 크래시 덤프 분석 및 집계 기능. 크래시 덤프를  cdb 수준에서 분석하면 이를 토대로 다른 덤프들과 비교하고, 이에 대해서 웹에서 볼 수 있게 했다
  • 덤프 수집 (web)
  • 크래시 덤프 뷰어 (web)
  • 빌드 도구나 덤프 관련 툴에서 메일 생성 / 알림 기능

에서 python을 주로 썼다. 물론 예전부터 쓰던 코드 생성기, 빌드 보조 도구에도 python을 쓰고 있다.

…덧붙이자면 요즘 .svg를 python으로 만드는 뻘짓을 했다. 근데 생각해보니 python-cairo를 쓰는 게 더 간단하잖아?;;;

 

C#

연초에 파일 전송 도구를 하나 짜야 했다. 원래 생각했던 건 python 기반으로 빨리 짜고 딴거 하고 놀자(…)였는데, 

이거 누가 유지 보수 하냐

라는 태클에 물건너 갔음. 흑흑.

그래서 실 서비스에서 써본적(…)도 없는 C#으로 짰음. 사실 태클건 사람들은 태클 걸면 C++로 짤 거라고 생각했다고…
애매하게 편하고, 애매한 수준까지만 제어할 수 있어서 개인적으로 불만이 좀 많았음. 내가 python을 안 쓴다면 모를까 Orz. 

누가 비슷한거 시키면 그땐 정말 python 쓸 테다… 일단 저거 linux에서 옮겨서 쓸 생각을 하니 눈 앞이 캄캄하기도 하고 — 누가 Mono 관련 조언 좀…

 

JavaScript

까놓고 말하면 js에는 완전 초보임. 그래서 python쪽에서 js를 쓰긴 했는데, 크롬에서만 돌길래(…) 포기하고 jQuery로 전부 옮겼다. JavaScript를 썼다기 보단 jQuery를 쓴 느낌이지만 (…).

CouchDB에서 map-reduce 용 언어로 js를 쓰긴하지만, 이건 그렇게까지 복잡하게 활용한게 없으니 좀 애매하다. 따지고 보면 올해는 js보다 json을 더 많이 본 것도 같다(운다).

올해 얻은 건 jQuery를 좀 더 익숙하게 쓰게 되었다 정도?

 

SQL

ORM하나 제대로 쓰고 싶었습니다만 꿈도 희망도 없어; 그냥 python위에서 클래스 수준으로 포장해서 하단의 데이터 스토어로만 쓴 듯.
위안이라면 MySQL / CouchDB를 선택해서 쓸 수 있게 만들었다 정도지만; 

Lua

팀 내에서 사용하는 서버 용 부하 테스트 툴은 (NDC에서 ipkn이 발표한 내용은 http://ipkn.upnl.org/ndc.html 을 참조) lua를 스크립트 언어로 쓴다.
그런 의미에서랑, 몇 가지 도구에서 lua를 썼다. 이쪽에선 내가 발전이 전혀 없는듯… 

Go

연초에는 좀 만졌지만 — 위에서 C#/Python으로 헀던 일 중 일부는 원래 Go로 하려던 건데 — 그 이후엔 망했음.
과연 내년에는 나의 야망(…)대로 Go로 돌아가는 서비스를 만들 수 있을까;

일단 올해에는 회사 안에서 / 실제 서비스에서 linux 기반으로 해도 된다는 건 확인했으니, 내년을 기약합시다(…). 

 

요약:

  • C++/python을 여전히 많이 쓰고 있다.
  • C#은 안 편했음; 내년에는 아마 다른 선택을 할 듯.
  • 웹 질(…)로 JavaScript/SQL을 좀 만졌다. 내년엔 기약 없음…
  • Go. linux 기반 서비스를 좀 더 해도 될 것 같으니 내년을 기약 

Rest in peace, dmr

Dennies MacAlistair Ritche (dmr) 사망. (실제 날짜는10/8일이라 한다)

여기서 그의 명복을 빌어본다.

지난 주의 스티브 잡스 사망도 슬픈 일이지만, 개인적으로는 데니스 리치의 사망이 더 임팩트 큰 일.

간단히 말해서, 우리가 현재 쓰고 있는 SW의 가장 밑 바닥을 지탱하는 기술과 개념이 데니스 리치 손에서 태어났다.

  • 시스템 프로그래밍의 가장 널리 쓰이는 C 언어의 창시자다; 우리가 쓰는 linux, Windows는(아마?)는 C 기반의 커널로 되어 있다. 대표적인 웹 서버 (아파치나 nginx) 역시 C로 만들었다. 그리고 이 C로 Unix를 재 작성(!)해서 `앞으로 OS를 어떻게 만드느냐’에 관해선 사실 상 결론을 내려버렸다.[1]
  • 내가 가장 사랑하는 *nix의 일관성 – 간단한 텍스트 기반 I/O의 프로그램을 연결해주는 파이프.
  • 현대적인 개념의 C-runtime IO 방식을 고안.
  • 내가 읽어 본 가장 간결하고 군더더기 없는 프로그래밍 책 – The C Programming Language의 저자.

좀 더 쓰자니 그냥 여길 링크: http://j.mearie.org/post/11385959754/rip-dennis-ritchie 에 더 잘 정리되어 있다.

  1. 더불어 이 두 업적으로 1983년에 켄 톰슨과 함께 튜링 어워드를 받았다 []

파일 대신 SQLite 쓰기

최근에 홍민희님이 링크한 sqlite는 fopen( )대신이다란 글이 있다.[1] 최근에 모 개발 중인 서버에서 시작시점에 데이터 파일 읽는 부분을 SQLite로 바꿔봤다.

원래의 구현은 엑셀 파일(.xls)을 서버 시작시점에 읽어서 특정 시트를 각 시트에 맞는 데이터 타입으로 바꿔서 저장하는 것. 이를 위해 Microsoft Office용 일부 dll (MS Visual Studio 2010 Tools for Office Runtime, AccessDatabaseEngine_x64)을 필요로 함. 이걸 써서 OLE로 값을 읽는다. 그리고 좀 엄하게도(…) 일부 셀은 리스트, 그것도 구조체 비슷한 것의 리스트를 적당한 토큰으로 구분해놨다. 이 부분은 문자열로 읽고 정규 표현식으로 분리해서 메모리에 올린다.

 

개인적으로 이 구현은 굉장히 맘에 안들었다. 문제라고 생각하는 부분은 두 가지. 일단 속도. 백 개 정도의 테스트 케이스를 실행하면 140초쯤 걸린다. 교체 대상인(…) 내 Q8400에서 약 140초쯤 걸림. 물론 콘솔 출력을 날려버리면 (> nul) 8x ~ 9x초 수준까진 줄어든다. 느린 부분이,

  • OLE로 데이터 가져오는 부분(700ms)
  • regex 로 데이터 언마셜링하는 부분(500ms).

물론 기능 테스트에서만 전체 데이터를 로드하는 거지만 전체 테스트가 느려지는 건 스트레스.  그리고 60% 이상의 케이스가 기능 테스트이고, 이 때마다 서버 런타임 전체를 초기화 하기 때문에 여기 들어가는 시간이 매우 스트레스였다.
일단 OLE에서 데이터 읽는 부분은 처음 읽을 때만 사용하고, 이 때 데이터를 텍스트 형태로 메모리에 올린 후, 그 이후 테스트 케이스에선 이걸 읽게 했더니 꽤 빨라지긴 했지만(140초->100초) 여전히 남은 정규 표현식 Orz. 그리고 이건 데이터 늘어나면 더 느려질게 뻔하니…

두 번째는 외부 의존성. 서비스하기 위해 추가로 설치해야 하는 외부 프로그램이 있는게 좀…

그래서 (a) SQLite 테이블로 바꾸는 스크립트, (b) SQLite 테이블에서 데이터 읽는 코드 이렇게 두 가지를 만들었다.

우선 엑셀 파일(.xls)로 되어있는 데이터를 SQLite 테이블로 변환하는 코드를 작성했다. python xlrd 팩키지로 특정 시트를 읽고 여기 있는 첫 번째 행에 있는 데이터를 열 이름으로 처리하고 나머지 행에서 셀 데이터 타입을 받은 후 여기서 빈 셀이 아닌 것의 주요 타입을 열 타입으로 고름. 다만 정수/실수 선택은 적당히 실제 셀 값을 뒤져서 골랐다. 근데 SQLite에 문자열로 넣고 실행시간에 select한 후 읽는 쪽에서 고르는 게 나을지도 모르겠다. 이 테이블에 개별 행을 하나 씩 읽어서 삽입.  (INSERT ?, ?, … , args)로 끝.

마지막으로 이전에 정규 표현식으로 처리하던 부분을 python에서 해석하는 단계에서 별도 테이블을 생성하게 바꿨다. 나중에 유효성 검사할 걸 생각하면 역시 단계가 하나 더 있어야 하고, 이걸 여러 번 할 이유가 없거든…

SQLite에서 데이터 읽는 부분은 흔히 하는 sqlite3 open, prepare(SELECT 구문), 데이터 다 나올 때 까지 step k번으로 해결. OLE로 읽는 거 보다 당연히 빠르고 텍스트로 캐싱한 것 읽는 거보다 조금 늦은 수준으로 끝나더라.

이제 외부 의존성은 없고(sqlite.c가 추가되지만) 테스트 케이스 실행은 50초 수준까지 줄었다. nul로 콘솔 출력 보내면 20초 미만…

여하튼 이거 오늘 git에서 메인 스트림으로 보내버리고 딴 버그 잡고나니 하루가 지나가긴 했다. (상당 수의 작업은 금요일에 했음)

  1. 링크에 적어놓은 말 자체는 Hacker News 페이지의 제목이고, 실제로 연결된 페이지는 SQLite 사이트의 화이트 페이퍼 페이지다 []

프로그래머의 일상: race-condition, 글쓰기, …

Race condition

5월 말에 예비군 훈련을 다녀오고나니 (…), 내가 작성한 C++/Win32 기반의 서버 런타임에 문제가 발생했더라. 정확한 요인은 모르겠지만, 메모리 사용량이 팍 뛰었고, 32bit 프로세스의 한계로 사망 -_-

다행히 ipkn이 작성한 부하 테스트 툴로 원인을 좁힐 수 있었다. 해당 프로세스에 미리 할당하는 소켓 수 이상으로,

  1. 연결을 계속해서 맺고
  2. 과다한 트래픽을 흘려보내고,
  3. 다시 끊고

…을 수 시간 반복하면 서버가 크래시하더라.

이미 수 개월 동안의 라이브 서비스 동안 별 문제 없어서 안정화 끝났다라고 생각했는데 그게 아니었던 모양.

처음 찾은 버그는 이런 거였다. 대략 소켓 래퍼 객체의 참조 카운트를 가지고 최종적으로 할당된 자원을 재활용하는데, 단 하나의 시나리오에서 이 패킷 버퍼와 소켓의 참조 카운트 해제하는 순서가 반대(…)로 되어있더라. 그래서 대략 k 시간의 코드 리뷰 후에 잡았음. 그나마 패킷 버퍼 쪽에서 문제가 되는 코어 덤프가 있었으니 Orz.

…그리고 이 상태로 다시 부하 테스트 시작. 또 죽는다? 다행히도 디버그 빌드에서도 재현이 된다. 문제는 heap corruption이라는 것? -_-;; 정확히 1 bytes가 0로 셋팅되더라.[1] 그래서 서버 런타임 전체에서 1바이트 세팅하는 코드를 전부 뒤졌다.
그 결과, 일부 per-thread 타이머 객체가,

  • 해당 스레드에서 타이머 객체가 종료되어 자원 해제
  • 다른 스레드에서 해당 타이머 객체 invalidate

하는 경우가 있을 수 있더라. 역시나 참조 카운터 문제 -_-; 선형화처리 코드는 들어가 있지만, 해당 객체가 살아있다는 보장이 없는 상황이라, 대부분 잘 돌지만(…) 이렇게 레이스가 일어나면 얼마든지 맛이 갈 수 있더라;

이 두 가지 문제 해결하고나니 대략 (눈에 보이는) 버그는 잡은 거 같지만…
일단 부하 테스트는 잘 버티고 있다.

…근데 애초에 문제가 되었던, 갑자기 메모리 사용량 1G 점프하던건 뭐였을까? 십중팔구는 처리가 늦어지고, 그에 따라 센드 큐가 늘어나고, 그에 따라 처리가 더 늦어지고(…) 하는 악순환의 결과였던 거 같지만 -_-;

만약 크래시 덤프도 없고, 디버그 모드에서 재현 못했으면 어쨌을지를 생각하니 좀 끔찍하긴 하구나 -_-;

글쓰기

역시 공대생을 위한 글쓰기 수업을 들었어야 했나 -_-;
내가 얘기하고자 하는 부분이 아닌 걸 계속 써야하나?

예전에 책 번역 할 때도, 영어보다 한국어 실력이 더 문제란 느낌이었으니 에휴.

  1. Win32 DEBUG heap이 마킹해주는 덕에 이건 파악할 수 있었다. 정말 다행임 []

NDC 참관기: Server Burner: 범용 네트워크 스트레스 테스트 툴

NDC 2011 2일 차(5/30)에 있었던 하재승 군의 세션.

여기에는 하재승 군과 같은 팀에 있어서(…) 좀 더 우호적인 관점의 내용이 있다는 점을 생각하며 읽어주기 바람;

이하는 내용 요약

게임 오픈 혹은 대규모 패치 후에 서버가 폭주(?)하는 사례가 많다.

네트워크 스트레스 테스트 툴이 필요하다?

  • 예상되는 스트레스 상황 테스트 / 반복 실행
  • 미리 문제를 찾고 해결하자

Pros.

  • 쉽게 테스트 작성
  • 대량의 테스트 진행
  • 테스트 진행 중 데이터 자동 수집 / 모니터링

미리 작성된 테스트 시연

간단한 채팅 서버를 가지고,

  • 1 명 들어와서 채널에 메시지 말하고 나가기
  • 100 명 들어와서 같은 동작
  • 테스트 동안 모니터링한 결과 보여주기

잘 알려진(…) 올해의 게임 Minecraft를 가지고,

  • 여러 마리의 봇이 로그인해서,
  • 봇들은 (미리 지정한) 유저를 따라다니고, 유저가 삽질(마우스 왼쪽 클릭)하는걸 따라함(…)
  • 100 명 수준이 되어가면 서버가 심하게 랙이 걸리는 걸 볼 수 있음[1]
  • 더불어, 이 동안의 시스템 모니터링한 그래프를 보여주기

테스트 자동으로 작성하기

  • 클라이언트 -> 복수의 서버 군(로그인, 채널, 게임, 챗, 몬스터, …)의 통신을 캡쳐해서 자동으로 테스트를 작성
  • 상대적으로 쉽고 간편하게 + 버그 재현에 써먹기
  • 바로 캡쳐해서 쓸 수는 없다 -> 툴을 이용해서 쉽게
  • 미리 만든 스트레스 테스트 용 아이디 (특정 prefix + 숫자)가 필요함

툴을 이용해서 테스트 스크립트 수정

한 눈에 패킷 보기, 패킷에 들어가는 값을 특정 전역 변수랑 연동/편집, 스크립트 내보내기… 를 처리하는 GUI 편집툴 제공

Client.Send_LoginRequest(A, "test"..index(), "testpass")
local pkt = Recv(A)
if pkt.__id__ ~= Server.LoginResult then fail() end

…같은 형식의 스크립트가 최종적으로 나옴.

결과 분석

  • 가상 유저 별로 받은 패킷 순서, 전달된 패킷의 개별 값, 특정 메시지 간 시간, 특정 행동(로그인)에 걸린 시간 값 등등을 측정
  • 서버 자원 모니터링

요약

프로토콜 파일 작성

  • 캡쳐/스트레스 테스트에 이용
  • 클라이언트 — 서버 간 패킷 내용 정의 ;(Python, lua, C#, C++ 코드 생성)
  • 패킷 정의가 없으면 해당 프로젝트에 쓰는 방식을 분석하고 정의 파일 만들기
  • 구조체 기반인 경우 아예 C++ 헤더를 파싱해서 만들기도 함
  • 헤더/푸터,암호화,체크섬 등 처리 가능

UDP 통신 / HTTP 통신 / 네트워킹 모듈 기능 테스트

계획

  • 복잡한 테스트 수행 (장시간 반복하기; 현재는 쉘 스크립트로 대체)
  • 테스트 결과 분석 강화
  • 네트워크 상태 시뮬레이션 (패킷 로스, 지연 상황)

촌평.

온라인 게임으로 한정할 경우, 많은 네트워크 테스트 도구가 갖는 문제가 뭐냐하면,

  • 부적당한 추상화 수준 : TCP/IP 패킷 레벨, HTTP 메시지, …
  • 부하 테스트 시나리오를 생성할 방법
  • 부하 테스트 시나리오를 유지 관리할 방법
  • 충분한 수준의 부하를 생성할 방법
  • 복수의 서버군이 있을 때 이에 대한 추상화는 어떻게 할지

등등?

이런 문제에 대한 접근법으로, 적당한 도메인 분리 + 개별 공략은 타당한 선택지라고 판단된다. 대략 서버 버너 구조 자체가,

  • 프로토콜 데이터의 별도 표현(yaml)
  • 메시지 캡쳐 / 편집 프로그램
  • (수동) 추가적인 스크립트 편집
  • 복수의 머신에서 동시에 부하 스크립트 동시 시작/종료/통계 처리

로 쪼개져 있다. 그리고 이 중 수동으로 스크립트를 추가 수정하는 부분만 빼면 많은 경우 그다지 복잡할 게 없다.

우선 추상화 수준의 문제.

사실 게임…이라고 해도 장르 자체가 다르면 기술적인 의미에서 추상화 수준이 서로 다르다. 그래서 ‘메시지’라는 좀 저수준이지만 공통(?)일 수 있는 수준을 잡고, 여기에 대해 UDP/TCP/혹은 응용프로그램 수준 메시지로 잘 쪼개서 처리하는 접근은 적당하다고 생각한다. 대략 게임이라면 TCP, TCP 기반의 메시지 (HTTP라거나? 전용 포맷?), UDP (전용 p2p 프로토콜 류) 정도를 생각할 수 있는데, 이거에 대해서 프로토콜 데이터만 기술해주면 대략 이 수준의 추상화는 메서드 호출처럼 표현할 수 있다.[2] 메시지를 특정 형태로 기술하는 일은, 팀 내에서야 이게 공통 포맷이라(…) 시간이 0이 걸리고, 사내 다른 팀 껄로도 거의 전환 시간 없이 가능했던걸 생각하면 어찌어찌 가능은 할 듯 하다. 대략 엔지니어 하나 붙여놓고 하루 안에 적용이 가능했으니…[3]

예를 들어 인증 메시지 보내는 것은: SendAuthMessage(utf-8-string-id, bytes-credential, bytes-session-key) 수준 정도로?

다음으로 메시지 캡쳐, 편집.

일단, 부하테스트 시나리오 생성을

  • 패킷을 아예 캡쳐하고, 이걸 적당히 조작(시연 동영상을 보면 좀더 이해가..) 하거나,
  • 아예 중간에 이걸 릴레이하는 서버 (개발팀이라면 언제라도 가능한) 를 두고 모든 메시지를 가로채거나
  • 서버가 여러 대인 경우에도 이걸 적당한 스트림으로 보여주고, 일부를 병합하거나

하는 식으로 실제 메시지를 가지고 만들어내고, 이걸 lua 스크립트 수준에서 처리하면 된다는 점이…

복수 머신에서 부하 자체를 생성하는 일

머신 한 대에서 생성하는 부하에는 한계가 있다. 왠만큼 시간을 들여 짜고 + 튜닝하지 않고서야, 머신 당 유저 수는 크게 제한된다. 차라리 가짜 유저 간 상호 작용을 줄이고 머신 붙인 만큼 유저를 더 만들어 내는게 대략 맞는 방향인 거 같다.
부하자체는 테스트 스크립트를 시작하는 프로세스와, 이 테스트 스크립트를 받아 실제로 메시지를 보내는 k개의 프로세스를 써서 보내기 때문에 (물론 머신도 k개에 준하게…) 물리 머신 수만 충분하다면 원하는 수준의 부하를 만들 수 있다. 다만 밑단의 lua 스크립트는 짜야겠지만(…).

Q/A 팀에서 일일이 부하테스트를 짜는건 약간 힘들지 몰라도, 서버 프로세스를 짜는 프로그래머라면 큰 무리 없이 쓸 수 있는 수준의 도구라고 생각한다.

  1. 테스트할 때 쓴 서버는 대략 Win7 x64, RAM 4GiB, Q8600 정도 []
  2. 다만 UDP는 별도로 .dll을 짜서 python/lua 바인딩을 제공해줘야 한다… []
  3. 강연 때도 나온 얘기지만 아예 xml 기반의 프로토콜이나 압축, 별도 체크섬 같은거 붙이는건 큰 일이 아님… []

NDC 참관기: 네트워크 비동기 통신, 합의점의 길목에서

넥슨 마비노기 2팀[1] 김재석 씨의 강연이었다.

네트워크 통신 일반론이 아니라 이전에 참관기를 쓴 마영전 서버 구조를 생각하면서 내용 요약을 읽어주기 바란다.
네트워크 프레임웍 개발을 목표로 데브캣 스튜디오의 게임을 개발하면서 얻은 경험에대한 설명이었다.
강연 중에도 부연 설명한 부분이지만, 이 이하에서 주로 얘기하는 두 개의 주체는 다음 의미다

  • 서비스: 비동기 네트워크 메시지를 처리하는 주체
  • 클라이언트: 비동기 네트워크 메시지를 보내는 주체

이하는 강연 요약. 잘못된 사항, 빠진 부분 등에 대한 지적/수정요청은 매우 매우 환영(…).

목표

  • 네트워크 코딩 스트레스를 주지 말자
  • 지나친 하위 레이어의 지식을 요구하지 말자

문제가 되는 부분은,

  • 네트워크 통신은 비지니스 로직이 아니라는 것
  • 정보가 필요한 시점에 ‘즉시 계산되는 것’으로 생각할 수 있으면 좋겠다
  • C++과 C#을 모두 지원해야하는 점[2]

방향

  • 네트워크 코드 생성
  • C/C++ 상호 운용
  • 논리 흐름에 따라 코드를 생성한다 (흐름을 끊지 말자)
  • 저수준의 네트워크 기술 지식은 숨기자

시스템 프로그래머와 응용 프로그램 프로그래머의 ‘설계 목표’가 상충된다.

그리고 이런 문제의 본질은 ‘적당히 돌아가면 그냥 쓰고 미완이 되는’ 데에 있다…

1차 시도 메시지 생성 / 프로토콜 반복기(enumerator)

메시지 생성

  • C#: 2.0 Lightweight code generation 사용 (4.0이라면 ExpressionTree)
  • C++: VS 매크로 혹은 하드 코딩으로[3]

pros.

  • C#에서 데이터 포맷만 정의하면 끝남

cons.

  • C++ 코드 생산성이 떨어진다
  • C# 형식의 가변성 혹은 generic처리의 문제

프로토콜 반복기

(Client)
var op = new Operation()
op.onComplete += () => { … }
op.onFail += () => { … }
RequestOperation(op)
(Service)
IEnumerable<Object> Run() {
yield return info.ID;
yield return info.Name
}

pros.

  • 클라이언트 코드를 짤 때 연관 코드가 한 자리에 모인다

cons.

  • cons. enumerator 를 배우는데 드는 비용
  • C++ 에서 사용할 수 없다[4]
  • 중간 처리가 필요한 비동기 작업이 적기 때문에, 굳이 enumerator를 쓸 이유가 없다

2차 시도 delegate 기반으로 코드 생성

마영전에 사용한 방법이라고 한다.

C# delegate를 IDL로 사용했다.  Client용 코드는 C++로, Service용 코드는 CIL을 생성하게 했다.

Hindley-Milner type inference를 사용한다. 불변성은 ref 가변성에 대해 공변성은 out, return 반공변성은 in 이용;

pros.

  • C# IDL만 가지고 클래스가 자동 생성된다

cons.

  • 프로토콜의 연관성을 기술할 수 없고,
  • 클라이언트/서버가 대칭성이 없고
  • 배경 지식이 많이 요구되며  유지 보수가 좀 불편하다 (C++/CIL)

 

3차 시도: interface 기반 코드 생성

차기 프로젝트에 사용하고 있는 방법이라 한다.

C# ‘interface’를 IDL로 사용해서 추상 클래스를 양쪽(C++/C#)에 생성한다.

  • Client/Service CodeDOM을 생성
  • C++/CIL을 대칭적으로 생성한다
  • Microsoft Asynchronous Programming Design Patterns의 권고안 대로 네이밍 및 기타 등등
  • Trivial한 serialize 형식은 C++에 복제해서 기타 작업을 간편하게

pros:

  • 대부분의 코드를 자동 생성하고
  • C++/Common Language Spec. 양쪽에서 일관성있게 작업

cons:

  • 상속을 써서 생기는 제약


촌평.

강연자체는 시도했던 방법들을 분석했다는 점만으로도 매우 훌륭하고, 전반적으로 말이 좀 빠른게 불만이긴했지만, 각 방법들을 간결하게 분석한 점이 괜찮았다.

다만 내가 기대한게 ‘일반적인 상황’에서의 비동기 통신 방법에 대해 얘기할거라고 생각한거여서 약간 망했음. 일단 여기서 언급한 건 (대부분의 경우) 마영전 서버에서 언급한 것과 같은 구조를 배경에 깔고 있다. 내가 만드는 서버 런타임에서 목표로하는 추상화 수준과 너무 달라서 Orz. 사실 이 세션 들을 때 기대한 것은 현재 참여하고 있는 한 프로젝트에서 클라이언트-서버 인터페이스 (네트워크 레이어와 그 래퍼 수준) 를 좀 달리 해볼까 하는 고민에서였는데,일단 서버 구조가 목표로하는 추상화 수준과, 이 추상화에서 내보이려는 부분이 마비노기 영웅전 서버와는 상당히 상이하기 때문에 GG. 내가 만드는 서버 런타임은 전반적인 비동기 메시지 ‘처리’에 집중하고 있고, 이쪽은 ‘통신 추상화’에 집중하고 있어서;;제목에서 기대한 거랑, 내가 가서 들은게 약간 덜 겹쳤다는 것?

잘 이해가 안가는 점: ‘왜 C#으로 중간 단계를 기술하는 것(IDL로 사용하는 것)에 집착할까?

나는 꽤나 다른 방법으로 근 몇 년간 시도해서 잘 쓰고 있다. 데이터를 설명하는 종류의 언어[5] 를 IDL로 써서 주고 받는 코드를 몽땅 생성하는 것.
물론 기반 런타임은 필요한 언어마다 짜긴 해야하지만, 이건 안 바뀌는 부분이라..

대략 데이터 포맷 파일인 yaml 파일이 적당한 생성기를 거치고나면, C++, C#, lua, python, 매우 실험적으로 Google go … 소스코드로 변환되고 이 과정은 IDE나 스크립트 수준에서 빌드할 때 자동으로 생성;
이 중 어느 언어 쌍을 골라도 서로 통신할 수 있게 만들었다.사실 이 생성기가 위에서 언급한 불변성/공변성을 몇 가지 경우에 대해 만족하지 못하긴 하지만, ((메시지 보낼 때 vector<int>를 vector<long long>대신 못 넣는건 확실히 불편하다))  실용적인 목적에서 큰 차이는 없을 것 같다.
일단 이 대칭적인 코드 생성기가 lua/python에서 안되면 팀에서 쓰는 부하 테스트 도구(ServerBurner)를 못 쓰기 때문에 약간 불편한 부분이있긴하지만, 일단 ‘돌아가고’ ‘쉽게 (다른 사람이) 확장할 수 있는’게 목적이니 나는 큰 불만이 없다(…완전히 신입으로 들어온 사람도 잘 적응하는거 보니 그렇게 느껴짐).

또한, C#의 enumeration 개념을 이해하는게 프로그래머들에게 벽이면, 왜 C++에서 우회할 방법 — 아마 맨 처음 기반 코드는 C++이었을텐데 — 이 많고 (boost function, c++ 0x lambda) 러닝커브도 큰 차이 없을텐데 왜 그건 시도하지 않았을까 하는 점.
2차 시도의 비동기 메시지 처리 형태는 boost::function과 (이후에) C++ 0x lambda로 쉽게 만들 수 있었고 (그리고 쉽게 쓰고 있고), 3차 시도의 구현 자체는 IDL이 data 기술 언어란 점만 빼면 시연 과정과 비교해봐도 큰 차이가 없는데…

그냥 이건 취향 문젠가?

  1. 안내 책자가 없어서 틀릴 수 있다; 잘못된 부분 수정 좀;; []
  2. 이건 데브캣 프로젝트 들의 언어 선택 문제인 듯 합니다 []
  3. 나중에는 주로 하드 코딩하게 되었다고 한다 []
  4. 세션 중에 연사가 말한 내용이기도 하고, 내가 예전부터 했던 일이기도 한데, 이 부분은 C++ 라이브러리나 C++0x의 기능을 쓰면 충분히 쓸 수 있다 []
  5. lua (6개월), xml(2년),yaml(2년) 순으로 바뀌었고, 최종적으론 계속해서 yaml을 쓸 듯 하다. []