데이터 파일 분할 압축
이미지, 사운드 등을 제외한 순수 데이터 파일
54만개, 642MB
XML과 유사한 태그가 존재하는 텍스트 파일
파일 이름만 모아도 30메가 수준
패킹 시 파일 별로 Binary XML과 비슷한 형태로 저장
300MB 수준
문제점
Memory mapped file 이용
메모리 공간 점유
XP SP2 이전 버전에서 실행 문제 발생
일정 크기 이상의 memory mapped file 생성 불가
하드디스크 읽기를 제어할 수 없음
데이터 분할 압축
전체 데이터를 압축하면 22MB
하지만 일부분만 얻으려면?
작게 쪼개서 압축해보자!
빠른 아이디어 검증
BLOCK = 64*1024
total = 0
for i in xrange(0, len(data), BLOCK):
total += len(zlib.compress(data[i:i+BLOCK], 9))
total ~ 55MB
다양한 블럭 크기 테스트- 64KB가 적절
실제 구현
비슷한 종류 파일들을 모아서 압축 - 35MB
압축된 내용 전체를 메모리에
54만개 파일의 이름 저장
증분 패킹
효과
게임 입장 후 메모리 사용량 100MB 감소
초기화 시간 - 큰 개선 없음
패치 용량 감소
데이터 파일 읽는 시간
mmap을 통해서 하드디스크 읽기 ~10ms
64KB 블럭 압축 해제 ~ 0.5ms (저사양 포함)
SSD ~ 0.5ms
애니메이션 파일
전체 데이터 파일의 50%를 차지 (27만개)
화면상에 출력되는 모든 것 = 애니메이션
캐릭터 하나 로딩 시 2000개 이상 생성
데이터 파일을 빠르게 로딩할 수 없을까?
하드에 저장된 내용을 읽어와서
내부 자료구조로 변형하는 과정(파싱)이 필요
(body.ani -> CAnimation::Load)
메모리에 저장된 형태와 똑같이 하드에 저장한다면
읽어온 후 바로 사용가능
그러나 일반적으로 불가능
힙에 할당되는 string, vector등의 멤버가 존재
메모리 복사로 읽을 수 있는 class
POD struct 라면 가능 (http://en.wikipedia.org/wiki/Plain_old_data_structure)
std::string, std::vector 등이 들어가면
동적할당이 일어남
메모리 위치가 연속적이지 않음
전체 데이터와 같이 복사가 가능한
string과 vector를 만들면 되지 않을까?
std::string name
“하재승”
아이디어
복사 후에도 값들이 valid 해야 한다
동적 할당 메모리도 버퍼에 포함되어있어야 한다.
포인터형 데이터가 필요
포인터는 상대 주소의 형태로 저장
할당되는 영역을 모아서 같이 저장
이왕이면
시리얼라이즈 코드도 없으면 좋겠다
void serialize(Archive & ar, const unsigned int version) {
ar & degrees;
ar & minutes;
ar & seconds;
} // boost::serialization
Reflection이 없는 C++
변수 추가 시 수정하지 않으면 문제 발생
일반적으론 코드 생성으로 접근
쓰기
classroom data;
data.class_name = "1001";
data.students.emplace_back(L"Alice",2);
data.students.emplace_back(L"Bob",5);
data.students.emplace_back(L"ud55cuae00",13);
ostringstream out;
dumpable::write(data, out);
// out.str()을 적절히 저장하거나 전송
읽기// (char*) buffer에 내용이 들어있다고 하면,
const classroom* pClassRoom = (classroom*)buffer;
// 메모리 상에 표현되는 그대로 저장되므로
// 캐스팅만 해도 값을 읽을 수 있다
사용하지 않은
경우와 비교
dumpable을 사용하지 않은 경우
// 아래와 비슷한 코드를 통해 파싱이 이루어지고
// 그 과정에서 속도 저하가 발생하게 된다.
classroom x;
stream.Read(x.class_name);
Uint32_t count;
stream.Read(count);
for(int i = 0; i < count; i ++)
{
student s;
stream.Read(s.name);
stream.Read(s.score);
x.students.push_back(s);
}
마도학자 스킬 (플로레 컬라이드)
주위의 적을 번개로 공격하는 오브젝트를 소환
캐릭터는 옆의 쳇바퀴에 탑승
공격 버튼 연타 시 더 빠른 속도로 주위의 적을 공격
유저 헬마치 영상 http://www.youtube.com/watch?v=l3Pd3hZP2vA
랙이 심하게 걸려 유저들 사이에선 랙마치라고 부름
기존 던파의 이미지 관리
이미지를 그릴 때 필요하면 읽어온다
일단 읽었으면 버리지 말자!
스킬 쓸 때마다 랙이 걸리게 할 순 없음
무엇을 기준으로 버릴지 불확실
처음에 읽을 때 랙 + 메모리 문제 발생
실패한 시도 : 소스 파일 분석
컴파일러를 통해 define등 전처리기 제거 후 결과물 파싱
if ( … == SKILL_EXCALIBUR)
“character/swordman/animation/excalibur/excalibur_sw
ord_start_%s_%s_glow.ani”
* 슬라이드에 등장하는 파일 이름은 실제 데이터와는 다르게 변형되었습니다.
소스 파일 분석의 한계
애니메이션 파일 읽는 다양한 함수
함수를 호출 하는데 그 안에서 파일을 읽는 경우
다른 스킬과 소스를 공유하는 경우
일부 추출된 결과로 랙이 줄길 기대했으나
해당 리스트만으론 불충분
좋은 결과를 내려면 제대로 된 정적 분석이 필요
유저 로그 기반 추출
유저 로그
스킬 사용
이미지 읽음
현재 던전
스킬 사용 후
읽은 이미지
타이밍 정보
던전과 연관된
이미지
스킬 애니메이션
스킬 사용 모션
던전 애니메이션
유저 사용 로그 남기기
이미지를 실제로 하드에서 읽는 루틴 수정
해당 이미지를 처음 읽는 경우
현재 던전, 읽은 이미지, 해당 애니메이션 파일
유저가 스킬을 사용한 시점을 기록
직업, 스킬 번호
테스트 서버 일주일치 데이터
csv 파일 15GB
이미지 읽은 기록
7750만 회
유저 스킬 사용 기록
140만 회
모든 스킬이 포함
분석 (1) – 간단한 통계
유저가 사용한 스킬 별로
사용 후 일정 시간 이내에 읽은 이미지를 모두 수집
스킬 사용 기준으로 이미지 읽은 시간 통계
스킬에 의해 출력되는 이미지라면
→ 매번 비슷한 타이밍에 읽을 것이다.
분석 (1) – 간단한 통계
분산(표준편차)을 통해 추정
스킬
사용
시점
분산이 작다: 스킬 사용에 의해 읽는 이미지!
분산이 크다: 스킬과 무관한 이미지
이미지 읽은 시점
분석 (2) - 스킬 전용 모션
2D 게임, 옷이 여러 파츠로 나뉘어짐
파츠 이름/모션이름 위치에 저장
body/RandomKick.ani face/RandomKick.ani
하나만 추출되어도 뭉쳐서 읽음
*/RandomKick.ani
분석 (3) - 던전
그래도 계속 렉이 걸렸기 때문에
스킬로 구분되지 않은 데이터 중
특정 던전에서 반복해서 읽는 경우 구분
처음의 랙 개선된 동영상이 완성!
유저 로그 기반 추출
유저 로그
스킬 사용
이미지 읽음
현재 던전
스킬 사용 후
읽은 이미지
타이밍 정보
던전과 연관된
이미지
스킬 애니메이션
스킬 사용 모션
던전 애니메이션
한계 (1)
포털이 있는 맵 방식
A맵에서 스킬 사용 후
다른 곳으로 이동했다가 다시 A맵으로 돌아가는 경우
이전에 출력되던 스킬 이펙트가 남아서 화면에 이어 출력됨
→ 타이밍이 다른 로그가 남음
최대 시간 제한을 줄여서 회피
한계 (2)
저사양 데이터가 섞여서 구분이 힘듬
GrabBlastBlood.ani
322개, 0~1초 308개(95%), 5~19초 14개
평균 0.767초, 표준편차 2.59초
관련 없는 항목이 포함되기도 함
Outlier에 강한 분포를 이용해서 잘 할 수 있지 않을까?
한계 (3)
출력 자체가 랜덤한 경우
일반 공격 시 일정 확률 발동 등
가정에서 벗어난 케이스
일단은 손으로 보강
많지 않을 꺼라 가정
미리 읽기 리스트의 업데이트
업데이트가 필요한 경우
리스트에서 빠진 파일 추가
직업, 스킬 추가나 이펙트 변경
던전 밸런스 수정 – 몹 배치 변경 등
이벤트로 인한 일시적인 데이터 추가
이벤트 던전, 이벤트 몬스터 등
미리 읽기 데이터 업데이트 고려점 (1)
유저가 읽은 파일 목록 기반으로 추가하는 시스템이므로,
이미 한 번 미리 읽기 리스트에 추가가 된 경우
더 이상 그 파일을 읽는지 안 읽는지 알 수 없게 된다
리스트가 계속 늘어나게 되는 문제점이 있음
자동으로 필요 없어진 파일을 삭제할 방법이 필요
자동으로 제거하기 위한 아이디어
랜덤하게 적은 개수의 파일(10% 미만)을 리스트에서 삭제
계속 필요한 파일의 경우
사용하면서 다시 리스트에 추가된다
필요 없어진 파일의 경우
읽지 않으므로 다시 리스트에 추가되지 않는다
랙에는 크게 영향은 안주면서 미리 읽기 리스트 갱신
아직 적용해보지 않아서 적절한 비율은 판단하기 어렵다
결과
던전 입장 시간 증가, 게임 중 랙 감소
메모리 사용량 증가로 크래쉬 증가 우려
일단 1인 플레이에만 적용
유저가 배운 스킬이 많은 경우
1GB 머신에서 메모리 부족으로 쓰레싱 발생
그 외 효과가 컸던 최적화
이미지 팩팩 색인, std::map 변경, TBB malloc, 코드 버그 수정
4기가 이상의 이미지 데이터
이미지 1000만 개 이상
이미지 팩 8만 개 이상
이를 다시 2천 개 이상 파일(이미지 팩팩?)로 묶음
이미지 팩팩을 배포
이미지 팩팩 사용 시 기동 시간 크게 증가
이미지 팩팩 사용 시 느린 이유
처음 게임이 실행될 때
2천 개의 이미지 팩팩(총 4GB)을
하나하나 열어서
내부에 어떤 이미지 팩이 들어있는지 읽은 후
리스트로 저장
이미지 팩팩 색인 파일 생성
스캔 후 결과를 저장하는 파일을 생성
2천 개 파일 읽기 → 파일 하나 읽기
기동 시간 약 20초 감소
std::map 변경
프로파일링 결과 상위권 중에 std::map이 자주 등장
boost::unordered_map으로 변경
VS 2010의 unordered_map 성능 문제
boost::unordered_map 사용 시
std::unordered_map보다 최대 열 배 빠름
TBB malloc 적용
기존 VC++ CRT malloc 사용
TBB scalable_malloc으로 변경
초기 기동 시간 약 10% 개선
불필요한 추적용 정보 제거
보안을 위해 primitive type 변수를 래핑
매번 사용 기록을 남김
정상 접근인 경우 포함
실수로 넣은 것으로 추정
제거 후 10-20% 이상의 성능 향상
유저 복제하는 몬스터
2초 이상 -> 0.5초 수준