dev.log2011/10/25 15:34
최근 1~2년간 내가 몸담고 있는 게임에 크래쉬가 부쩍 늘었는데, 그 속을 들여다보면 절반 이상이 메모리 부족이다. 이 문제는 시스템 사양을 올려도 해결되지가 않는다. 제아무리 시스템 메모리가 넘쳐나는 세상이라고 해도, 아직 32bit 윈도우를 쓰는 사람도 많다. 그래서 게임은 32bit 프로세스로 돌아가게 만들어야 하고, 그렇게 하면 프로세스가 할당받을 수 있는 유저 메모리는 2GB가 한계이기 때문이다. 실제로는 스택 영역도 있고, 단편화나 뭐 이런저런 문제로 진짜로 쓸 수 있는 메모리는 2기가가 안되기 마련.

문제는 게임의 수명이 길어질수록 컨텐츠가 늘어나게 되고 그에 따라 사용하는 리소스의 양도 많아진다는 것.

어느 게임이든 그럿지만, 개발 당시에는 어떻게든 출시하는게 목표였을 것이기 때문에, 일단 되는 방향으로 개발해야만 했을 거다(그당시의 상황을 나는 모른다). 이렇다할 컨텐츠가 많이 없었기 때문에 게임에 포함된 모든 리소스를 메모리에 올려놔도 사용하는 것만 메모리를 할당하는 것과 몇MB차이가 안났을 게다. 그래서 의도인지 아닌지는 모르겠으나, 게임에 쓰이는 모든 리소스가 게임 구동시에 메모리를 차지하도록 구성되어 있었다. (두둥)
아마도 짐작컨데, 이런 시나리오였을 거다. FPS게임이 있고, 출시 당시에 총기를 대략 20종 준비해 놓고 출시했다고 가정해 보자. 한 방에서 게임하는 플레이어가 대충 8:8이라고 치고 최악의 경우를 상정하면 16명이 16종의 총기를 골고루 들고 나오는 건데, 게임 구동시 모든 총기를 다 로딩해 놔도 기껏해야 4종의 총기 리소스에 해당하는 메모리만 추가 부담하면 된다. 총기 하나에 5~6MB를 차지한다고 치면 껏해야 20MB. 이마저도 게임에 플레이어가 들어오고 나가고를 몇번 하다 보면 어차피 다 로딩되어야 하는 경우도 심심치 않았을 거다. 게다가 게임 구동시에 로딩이 일어나므로 맵 로딩 시간 및 난입 랙이 줄어드는 순기능마저 있다. (lol)
그러다가 총기를 추가하는 업데이트를 한다. 한번에 한두개의 총기만 추가되므로 추가되는 메모리 부담 역시 10MB안쪽이다. 요즘 같은 세상에서는 납득할 만한 수치이다. 게다가 총기 한개 더 넣자고 기존의 리소스 관리를 뜯어고쳐서 새로 만드는 것 역시 수지가 안맞는 일이다. 이런저런 이유로 기존의 방식 대로 총기 리소스가 들어간다.
그런데 세월이 흘러 총기 한두개씩을 넣는 업데이트 30회를 거쳤다. 총기가 3배로 늘어났다. 그럼 총기가 60종이 되는 건데, 최악의 상황을 가정해도 16명이 16종의 총기를 들고 나오는 거다. 그런데 기존 스킴 대로 게임 구동시에 모든 총기의 리소스가 로딩된다면 게임 중 쓰일 리가 없는 총기 44종의 리소스는 말 그대로 메모리 도둑일 뿐이다. 게다가 같은 총기를 들고 오는 사람이 있다면 낭비는 더 심해진다.

물론 이런 일이 일어나서는 안된다. 그런데 그것이 실제로 일어났습니다.

이는 부분적으로 언리얼 엔진의 구조 때문이기도 한데, 언리얼 스크립트는 게임 진행에 필요한 리소스가 참조될 경우 스크립트 로딩과 동시에 해당 리소스를 로딩해 버린다. 신경써서 짜지 않으면 실행경로에 없는 리소스도 마구 로딩돼 메모리를 차지하게 된다.
그럼 방법은? 리소스가 로딩될 시점을 프로그래머가 제어해 줘야 하는데, 이 역시 난관이 있다. 개발 작업에 사용하는 애셋에는 일반에 공개되면 안되는 리소스도 많이 있다. 그래서 패키징 시에 공개될 버전에서는 참조되지 않는 리소스를 제외하고 패키징하는데, 리소스를 프로그래머가 동적으로 로딩하면 언리얼 스크립트에서 제공하는 리소스 참조 관리를 거치지 않으므로 참조되지 않는 것으로 처리하여 패키징에서 빠지는 불상사가 일어난다.
동적으로 로딩하는 것들을 별도로 관리하는 매커니즘을 만들면 되긴 하지만, (그리고 실제로도 만들었다) 문제는 지금까지 작성된 스크립트를 고칠 엄두가 안난다는 것이다. 작업량도 많거니와, 새 컨텐츠 만들 시간도 없는데 기존의 것 고치자고 노가다를 하기도 좀 그렇고, 게다가 고쳤는데 버그라도 일어난다면 누가 책임진단 말인가.

해서, 우리 게임의 메모리 사용량은 계속해서 계속 계속 계속 증가하기만 해왔고, 급기야 이제는 클라이언트 크래쉬의 주 원인으로 떠오른 지경. (후새드) 이거야말로 가랑비에 옷젖는 줄 모르는 대표적 사례.

결말은? 근 2주에 걸쳐 각종 총기과 무기, 캐릭터에 주렁주렁 달려 있는 리소스를 동적로딩으로 바꾸고 대략 200MB를 절감할 수 있었다는 것. (올레!)

 
저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by uhm
dev.log2010/05/12 17:56
오늘 섬군이 나한테 이렇게 물어봤다.
[섬] 엄아 엄아
[엄]  ??
[섬] 이게 웨 3바이트로 나올까.. ;ㅁ;
union RGB565
{
 struct
 {
  byte r : 5;
  byte g : 6;
  byte b : 5;
 };
 WORD rgb;
};
[엄]  음; 컴파일러의 최적화 덕분이겠지;
[섬] #pragma pack(1) 이거 해도?
[섬] 아.. 짜증나 .. 완전 삽질하고 있었네 -_-;
[엄] 음.. 유니언은 pack이 아니라 다른 걸로 할거 같은데...
[섬] 에잇! 다 다시 짜야쥐.ㅠ.ㅠ 망할
[섬] 유니온 따위 쓰지 말아야.. 겠.. -_-
[엄] 아아 니가 쓴 타입이 byte라 그래. WORD같은 걸로 바까바바
[섬] 흠.. WORD로? 따로 계산 ? 음..
[엄] 유니언은 선언된 타입의 경계를 벗어나는 멤버는 그 형에 맞춰서 재배열하게 돼 있슴
[엄] 5+5 = 10이니까.. g멤버가 1바이트 경계에 걸쳐지잖아
[엄] 그러니까 g멤버는 다음 바이트로 넘어가는 거지.
[섬] 호오 ... 그렇군
[섬] 크 ... 모든건 그 비모로글 유니언 때문이었군..ㅠㅠ
[엄] WORD로 바꾸면 별 문제 없이 잘 될듯;;

섬군이 모를 정도면 다른 사람들도 잘 모를거 같아서 포스팅.
저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by uhm
TAG C++, union, 정렬
dev.log2009/11/15 01:13
몇년동안이나 책장에 덩그러니 꽂혀만 있던 D&EC++을 드디어 다 읽었다. 대략적인 느낌은 왜 지금의 C++이 이모냥밖에 못되었나에 대한 변명..이랄까... 이런 느낌인데, 뭐, (스타게이트 아틀란티스의) 닥터 로드니의 말을 따오자면 "완벽한 세상에서는" 이모냥밖에 안되지 않았겠지. 하지만 우리 세상은 완벽하지 않잖아? 안될거야 아마. -_-a

책을 보면, 각각의 언어 스펙에 대한 변천사가 개략적으로 기술되는데, 일부는 현재의 솔루션이 확실히 진보했다는 느낌이 들고, 일부는 호환성이나 기술적, 관습적 한계 때문에 쉽고 우아하고 효율적인 솔루션을 포기했다는 면이 안타깝다는 느낌.

현재의 상태라서 더 낫다고 생각하는 한가지 예로는 비야네가 '상속을 통한 제약조건'이라고 이름붙인 항목에서 기술한 것이다. 이는 템플릿이 어떤 종류의 매개변수로 스페셜라이제이션 될 수 있는가를 명시할 필요성이 있지 않겠느냐는 논의에서 비롯된다. 템플릿 매개변수가 만족해야 하는 조건을 명시할 필요성은 나도 느끼는 바이고, C++0x의 concept 개념이 매우 마음에 들었지만, 현재 C++0x 표준에서는 떨어졌다고 하는 걸 보면 만족스러운 해결책은 아직도 요원하다. 그런데 이러한 논의는 C++의 템플릿 명세를 처음 만들면서도 했다는 점이다. 비야네의 동료들이 제안한 방안은 템플릿 선언시 매개변수 선언부에서 특정한 클래스에서 상속받은 타입들로만 스페셜라이제이션할 수 있음을 명시하면 어떻겠느냐는 것이다.
template <class T>
class Comparable
{
    bool operator==(const T&, const T&);
};

template <class T : Comparable>
class XXX
{
  // .....
};
일견 타당해 보이기도 하지만, 비야네의 생각으로는 근본적으로 'T가 비교가능해야 한다'고 명시하는 대신에 'T가 Comparable에서 상속받아야 한다'라고 명시하는 것은 틀린 개념이라고 봤다고 한다. 여기엔 나 역시 동의한다. C++의 템플릿 매개변수가 저런 식으로 제약조건을 명시해야 했다면 활용도가 크게 떨어졌을 듯.

C++에서 빠져서 아쉬운 것중의 하나는, 비야네가 생각만 하고 있었다는 include 키워드(프리프로세서 명령이  아닌!).
#include 전처리기 명령은 매우 무식한 방법으로 작동하여, 스코프 룰을 완전히 무시하고, 순서의존성이 매우 크다. 윈도우에서 프로그래밍해본 사람이라면 std::max 템플릿을 쓰기 위해서는 windows.h 헤더에서 선언된 max 매크로를 요리조리 피해가야 했던 경험이 있을 것 같다. 비야네는 include라는 키워드를 도입하여 유일성을 자동으로 보장해주며,  include로 포함되는 헤더에서 선언된 매크로는 해당 헤더파일 안에서만 동작하고, 거꾸로 include로 포함된 헤더에서 선언되지 않은 매크로는 해당 헤더에서 동작하지 않도록 만들고 싶었다고 한다. 대략적으로 자바의 import와 비슷하게 동작할 수 있도록 만들고 싶었던 모냥. 이는 나도 적극 찬성인데, C++에 들어가지 못한 것은 매우 안타깝다.

책 전체에 걸쳐서 이건 이렇게 하려고 했었는데 이런이런 문제가 발생해서 결국 이렇게 되고야 말았다... 이런걸 구구절절하게 써놔서..... 난 무척 재미있었다. 아! 비야네도 불완전한 세상에 살수밖에 없는 엔지니어였구나. 흑_흑

비야네도 알고 있는, 인류에게 가장 도움이 되는 조언.

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by uhm