gcgc- cgcii 서버 엔진에 적용된 기술 (2) - perfornance

62
Pool System CGCII Cho sanghyun’s Game Classes II

Upload: -

Post on 19-Jul-2015

316 views

Category:

Software


3 download

TRANSCRIPT

Page 1: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

Pool SystemCGCIICho sanghyun’s Game Classes II

Page 2: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

멀티테스크 환경하에서 제한된 메모리를 효율적 사용을 위한 메모리 시스템 .필요할 경우 필요할 만큼 할당요구하고 다 사용하면 반납하는 시스템 .

풀 (Pool) 시스템

CGCIICho sanghyun’s Game Classes II

풀시스템

성능에 초점을 둔 풀 시스템의 필요성 엄청 대두 !!

힙 (Heap) 즉 “동적 할당기”=

하지만 , 메모리 용량이 커지고 빈번한 사용에 따라 성능 문제도 이슈화 !우리가 인식하지 못하는 사이 수도 없이 많은 동적 할당과 해제를 사용하고 있다 !(std::list 나 std::map 에 push/pop 을 할 때도 매번 동적 할당과 해제가 일어난다 .)

풀의 기본적 개념

Page 3: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

은 jemalloc 라는 힙을 사용해 속도 향상을 얻었다고 한다 .

동적 할당 성능의 중요성

의 tcmalloc 를 개발해 내놓았다 !!!( 어떤 곳에서 단지 tcmalloc 의 사용만으로 20% 의 성능향상을 얻었다고 한다 .)

완전 답게 !! 기본 힙 (heap) 은 그렇게 좋은 성능을 가지고 있지 못했다 !

그래서 는 성능을 업그레이드 시킨 힙 시스템인

LFH(Low Fragmentation Heap) 을 내놓았다 . (Windows XP 이후는 기본 적용된다 .)

CGCIICho sanghyun’s Game Classes II

풀시스템

Page 4: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 객체 풀

객체 풀이 단순한 메모리만을 할당하는 풀에 비해 매우 유리한 장점이 존재한다 .

1. 메모리 할당 자체에 대한 성능 향상을 기대할 수 있다 . ( 이것은 공통 )

2. 객체의 생성자와 소멸자의 호출 및 초기화 에 드는 비용을 최소화할 수 있다 .

→ 최소한의 재생 (Recycle) 과정만을 거쳐 할당할 수 있다 . → 복합객체나 초기화 비용이 큰 객체일수록 엄청난 비용 차이가 난다 . → 이 효과는 단순한 메모리 할당자인 tcmalloc 나 jemalloc 로는 기대할 수 없다 .

3. 객체 풀을 기반으로 메모리 풀을 만든다 .

Page 5: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCII 에서 사용한 객체 Pool 의 기본 원리

CGCIICho sanghyun’s Game Classes II

풀시스템 객체풀 시스템의 기본 원리

Pool

AllocAlloc

FreeFree

GarbageStack

1. 할당해줘 !2.[ 스택 ] 에 저장된 거 있냐 ? 엄슴 !

3. 새로 만들어 주자

1. 잘썼다 ! 할당받았던 거 돌려줄께 !

3. [ 스택 ] 에서 꺼내주자 !

2. 오케 !! Stack 에 쌓아놓자 !

1. 또 할당해내 !!!2. [ 스택 ] 에 저장된 거 있냐 ? 어 ! 있슴 !

delete 절약delete 절약

new 절약new 절약

이렇게 new 와 delete 할 것을 계속 절약하는 것이 쌓이면성능이 향상될 것이다 !!!!!!!!!!

다만… [ 스택 ] 에 쌓고 [ 스택 ] 에서 꺼내오는 것이 new/delete 보다 빠르다는 전제하에서 ~~

Page 6: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 객체풀 시스템의 기본 원리

일단은 성능 좋은 [ 스택 ] 을 구현하는 것이 핵심이닷 !!!단 , 조건이 있다 !

첫째 , new/delete 혹은 malloc()/free() 보다 넘사벽으로 빨라야 한다 !

둘째 , 쓰레드에 대해 안전 (Thread-safe) 해야 한다 !!!

[ 스택 ] 이라면 뭐 .. 간단히 ~~ 간단히 !!!STL 에 std::list<T> 나 std::vector<T> 를 쓰면 간단히 해결되겠네 !!!!!!!!역시 결론은 STL!!여기에다 쓰레드 안전을 위해 크리티컬 섹션 걸어서 쓰면 되겠네 !!!!

이건 뭐 엄청 쉽다 !!!! 초딩도 할 수 있을 것 같아 ~

라고 생각하다 보통 멘붕을 맞게 됨 ...

초간단데스네 !!

셋째 , 안정성이 확보되어야 한다 !!!

Page 7: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 객체풀 시스템의 기본 원리

일단은 std::list 는 push 등을 수행할 때 node 생성을 위해 동적 할당 (new) 을 사용한다 ! pop 을 할 때 delete 를 사용한다 !!!그것도 allocator 를 통해 ..

앗 ! 그럼 push/pop 때 동적 할당 제거를 위해 pool allocator 를 사용하면 되지 않을까 ???라는 생각은 pool 을 만들기 위해 다른 pool 을 쓴다는… 무한반복

그럼 std::vector<T> 가 등장하면 어떨까 ? list 와 같은 동적 할당은 없지만 미리 할당된 크기만큼 reserve 해둬야 하고만에 하나 이 예측이 틀리면 엄청난 피를 봐야 한다 ~

그래서 !!!!

[ Lock-free Singly Linked-List ]

가 등장함 !!!

이미 실패!

이미 실패!

역시 실패!

역시 실패!

Page 8: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 객체풀 시스템의 기본 원리

Lock-free 의 구현을 위해서는 Stack되는 객체 자체가 다음 객체를 가리키는 포인터 변수가 필요하다 !!

그래서 풀의 대상객체들은 ICGPoolable<T> 를 상속받게 만들었다 .- ICGPoolable<T> 에는 [Single Linked List] 를 위한 변수가 존재함 .

template<typename TTYPE>struct SCGLIST_ENTRY{public:

TTYPE* Next;};

template <typename TOBJECT>class ICGPoolable :

public SCGLIST_ENTRY<TOBJECT>,virtual public ICGReferenceCount

{protected:

ICGPoolable() {}};

template<typename TTYPE>struct SCGLIST_ENTRY{public:

TTYPE* Next;};

template <typename TOBJECT>class ICGPoolable :

public SCGLIST_ENTRY<TOBJECT>,virtual public ICGReferenceCount

{protected:

ICGPoolable() {}};

다음 객체를 가리키기 위한 변수 !!!

Page 9: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 객체풀의 구현

Pool 은 쉽게 말해서 Lock-free Singly Linked List 다 .

template<typename TOBJECT>class CGPool::CObject{public:

TOBJECT* Alloc();void Free(TOBJECT* p_pObject);

private:lockfree_self_stack<TCREATE*> m_stackObject;

};

template<typename TOBJECT>class CGPool::CObject{public:

TOBJECT* Alloc();void Free(TOBJECT* p_pObject);

private:lockfree_self_stack<TCREATE*> m_stackObject;

};1. Lock-free singly linked list stack

2. Alloc()/Free()

Page 10: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 객체풀의 구현

template<typename TOBJECT>TOBJECT* CGPool:: CObject<TOBJECT>::Alloc(){

TCREATE* pObject;

pObject = m_stackObject.pop();

if(pObject ==nullptr){

pObject = new TOBJECT;}

return pObject;}

template<typename TOBJECT>TOBJECT* CGPool:: CObject<TOBJECT>::Alloc(){

TCREATE* pObject;

pObject = m_stackObject.pop();

if(pObject ==nullptr){

pObject = new TOBJECT;}

return pObject;}

template<typename TOBJECT>void CGPool:: CObject<TOBJECT>::Free(TOBJECT* p_pObject){

m_stackObject.push(p_pObject);}

template<typename TOBJECT>void CGPool:: CObject<TOBJECT>::Free(TOBJECT* p_pObject){

m_stackObject.push(p_pObject);}

1. 먼저 [ 스택 ] 에서 Pop 을 해본다 .

2. [ 스택 ] 에 없으면 new 로 새로 생성한다 .

3. 객체 리턴 ~

1. [ 스택 ] 에 Push 한다 .

Page 11: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 TLS 를 이용한 풀

아무리 Lock-free 를 사용한다 해도 Thread Safe 를 위해 Interlocked 함수만 해도 성능을 많이 떨어뜨릴 수 있다 .

또 Lock-free 알고리즘 상 Thread 가 많아지거나 부하가 많이 걸리면 쓰레드 간 경쟁으로 성능이 더 떨어진다 .

TLS (Thread Local Storage) 를 사용하면 이 역시도 최소화할 수 있다 .

TLS 는 tcmalloc 나 jemalloc 등에서 성능 향상의 핵심 역할을 하고 있다 !

TLS (Thread Local Storage) 는 특성상 다중 쓰레드 환경 하에 많은 부하가 걸리는 상태에서 더 엄청난 성능 향상을 준다 .

Page 12: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 TLS 를 이용한 풀

각 Thread마다 Thread cache 를 두고 중앙은 [Lock-free 로 구현된 스택 ]버퍼를 둔다 !

[ 중앙 스택 ]

Thread A

Thread B

Thread C

[TLS 캐시 스택 ]

1. 할당해줘 !

2. 일단 TLS Cache stack 을 확인한다 . 어 없네 !!3. [ 중앙 스택 ] 에서 Chunk 를 가져와 TLS

Cache stack 을 채운다 !! (Lock처리해서 가져온다 .)

Chunk(Bundle)

4. 그리고 TLS Cache Stack 에서 할당해준다 .

5. 또 할당해줘 !!6. TLS Cache stack 에 있으니 TLS Cache 에서

꺼내온다 !!! ( 이때는 Lock처리가 전혀 없다 !)

Chunk 크기가 커지면 커질 수록 Lock 의 빈도가 낮아져 성능이 그만큼 향상된다 .

하지만 Chunk 크기가 커지면 커질 수록 잔여 객체수가 많아져 그만큼 메모리 낭비가 커지게 된다.

Lock처리 전혀 없음Lock처리 전혀 없음

Lock-free 스택Lock-free 스택

Page 13: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

Pool 의 대상이 되려면 ICGPoolable<T> 을 상속받아야 한다 .

class foo: virtual public ICGPoolable<foo>{

…public

virtual void OnFinalRelease() { /*delete this 하면 클난다 !*/}};

class foo: virtual public ICGPoolable<foo>{

…public

virtual void OnFinalRelease() { /*delete this 하면 클난다 !*/}};

CGOBJ<CGPool::CObject<foo>> g_pool;

void main(){

foo* pObject = g_pool.Alloc();

g_pool = g_pool.Free(pObject);}

CGOBJ<CGPool::CObject<foo>> g_pool;

void main(){

foo* pObject = g_pool.Alloc();

g_pool = g_pool.Free(pObject);}

CGCIICho sanghyun’s Game Classes II

풀시스템 객체 풀 사용하기 (1)

1. ICGPoolable<T> 를 상속받는다 .

Pool 은 풀의 대상이 되는 클래스를 템플릿 인자로만 넣으면 된다 .

CGPool::CObject<foo> poolTEST;대상클래스 풀객체 변수명

2. 풀 객체 선언

3. 풀에서 객체 할당하기 ~

4. 다 사용하고 풀에 객체 반환하기 ~

Page 14: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

매번 Pool 을 정의하고 또 객체의 OnFinalRelease() 도 정의하려면 귀찮다 !!!또 할당 해제할 때 생성된 Pool 을 찾아 Free() 를 해줘야 한다 !!!

template <typename TOBJECT>class NCGPoolable : virtual public ICGPoolable<TOBJECT>{protected:

NCGPoolable();

public:staticTOBJECT* Alloc() { return m_pPoolObject ->Alloc();}

void Free(TOBJECT* p_pItem) { m_pPool ->Free(p_pItem);}private:

virtual void OnFinalRelease() { Free((TOBJECT*)this);}

private:staticCGPTR<CGPool::CObject<TOBJECT>> m_pPoolObject;

CGPTR<CGPool::CObject<TOBJECT>> m_pPool;};

template <typename TOBJECT>class NCGPoolable : virtual public ICGPoolable<TOBJECT>{protected:

NCGPoolable();

public:staticTOBJECT* Alloc() { return m_pPoolObject ->Alloc();}

void Free(TOBJECT* p_pItem) { m_pPool ->Free(p_pItem);}private:

virtual void OnFinalRelease() { Free((TOBJECT*)this);}

private:staticCGPTR<CGPool::CObject<TOBJECT>> m_pPoolObject;

CGPTR<CGPool::CObject<TOBJECT>> m_pPool;};

그래서 NCGPoolable<T> 클래스가 있다 .

CGCIICho sanghyun’s Game Classes II

풀시스템 객체 풀 사용하기 (2)

Page 15: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

class foo: public NCGPoolable<foo>{

…};

class foo: public NCGPoolable<foo>{

…};

void main(){

CGPTR<foo> pObject = foo ::Alloc();

…}

void main(){

CGPTR<foo> pObject = foo ::Alloc();

…}

ICGPoolable<T> 대신 NCGPoolable<T> 을 상속받으면 아주 간단해진다 !!!OnFinalRelease() 를 정의할 필요도 없다 !!

따로 전역으로 풀 객체 (CGPool::CObject<T>) 를 선언할 필요가 없다 .

CGCIICho sanghyun’s Game Classes II

풀시스템 객체 풀 사용하기 (3) NCGPoolable<T>

자동 참조계수 처리된다 .

Page 16: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

template <class TOBJECT>CGPTR<TOBJECT> NEW(){

__if_exists(TOBJECT::NCGPoolable){

return TOBJECT::Alloc();}

__if_not_exists(TOBJECT::NCGPoolable){

class OBJECT_CREATE : public TOBJECT, public CGReleaser::NDelete{};

return CGPTR<TOBJECT>(new OBJECT_CREATE());}

};

template <class TOBJECT>CGPTR<TOBJECT> NEW(){

__if_exists(TOBJECT::NCGPoolable){

return TOBJECT::Alloc();}

__if_not_exists(TOBJECT::NCGPoolable){

class OBJECT_CREATE : public TOBJECT, public CGReleaser::NDelete{};

return CGPTR<TOBJECT>(new OBJECT_CREATE());}

};

매번 Pool 을 정의하고 OnFinalRelease() 하려면 귀찮다 !!!그래서 NEW<T>() 함수에 기능 추가 !

CGCIICho sanghyun’s Game Classes II

풀시스템

NCGPoolable<T> 을 상속받았으면 T::Alloc() 함수를 호출하여 [Pool 에서 할당 ] 받는다 .

NCGPoolable<T> 을 상속받지 않았으면 [new 로 생성 ] 한다 .

객체 풀 사용하기 (4) NCGPoolable<T> & NEW<T>

Page 17: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

class foo: virtual public ICGReferenceCount{

…};

class gee: virtual public NCGPoolable<gee>{

…};

void main(){

CGPTR<foo> p1 = NEW<foo>(); // new 를 사용해 동적 할당

CGPTR<gee> p2 = NEW<gee>(); // pool 에서 할당

…}

class foo: virtual public ICGReferenceCount{

…};

class gee: virtual public NCGPoolable<gee>{

…};

void main(){

CGPTR<foo> p1 = NEW<foo>(); // new 를 사용해 동적 할당

CGPTR<gee> p2 = NEW<gee>(); // pool 에서 할당

…}

NEW<T>() 함수를 사용하면

• NCGPoolable<T> 를 상속받으면 Pool 에서 할당된다 .

• ICGReferenceCoount 만 상속받았다면 new 를 사용하여 동적 할당한다 .

CGCIICho sanghyun’s Game Classes II

풀시스템 객체 풀 사용하기 (5) NCGPoolable<T> & NEW<T>

1. 그냥 일반 참조계수

2. 풀을 사용한 객체

Page 18: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 메모리 블록 풀 (1)

메모리를 할당 받아야 할 경우에는 임의의 크기를 할당 받아야 하기 때문에 CGPool::CObject<T> 로 힘들다 .

이를 위해 여러 개의 [ 메모리 블록 풀 ] 을 연결하여 사용한다 .

1k Memory Pool1k Memory Pool

2k Memory Pool2k Memory Pool

3k Memory Pool3k Memory Pool

64k Memory Pool64k Memory Pool

.

.

.

1~1K Byte 할당 요구 시

1K~2K Byte 할당 요구 시

2K~3K Byte 할당 요구 시

63K~64K Byte 할당 요구 시

.

.

.

여러 크기의 메모리 블록 풀을마련함 .(Buckets)

64K Byte 이상 할당 요구 시 일반적인 할당 사용

버킷들 (Buckets)

Page 19: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 메모리 블록 풀 (2)

메모리 블록 풀은 처리는 간단히 해당 메모리 풀을 선택하는 처리가 전부 !

class CGPool::CMemoryBlock{

…vector<CGPTR<CGPool::CObjectTLS>> m_vectorMemoryBlockPool;…

};

class CGPool::CMemoryBlock{

…vector<CGPTR<CGPool::CObjectTLS>> m_vectorMemoryBlockPool;…

};

#define MEM_POOL_ALLOC(size) CGPool::CMemoryBlock::GetInstance().Alloc(SELECT_BLOCK(size))#define MEM_POOL_ALLOC(size) CGPool::CMemoryBlock::GetInstance().Alloc(SELECT_BLOCK(size))

#define SELECT_BLOCK(size) ((size<=8192) ? ((size<=1024) ? ((size-1)>>7) : (((size-1)>>10)+7)) :

((size<=65536) ? (((size-1)>>12)+13) : (((size-1)>>14)+25)))

#define SELECT_BLOCK(size) ((size<=8192) ? ((size<=1024) ? ((size-1)>>7) : (((size-1)>>10)+7)) :

((size<=65536) ? (((size-1)>>12)+13) : (((size-1)>>14)+25)))

ICGMemory* CGPool::CMemoryBlock::Alloc(int p_iBlock){

return m_vectorMemoryBlockPool[p_iBlock]->Alloc();}

ICGMemory* CGPool::CMemoryBlock::Alloc(int p_iBlock){

return m_vectorMemoryBlockPool[p_iBlock]->Alloc();}

할당을 요구하는 메모리의 크기에 따라 어떤 풀을 선택할 것인가는 매크로로 정의

Page 20: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 버퍼 시스템 (1)

할당된 메모리는 메모리 포인터가 아니라 객체를 리턴한다 .

class ICGMemory : public CGBUF,virtual public ICGReferenceCount // 참조 계수 ..

{…

};

class ICGMemory : public CGBUF,virtual public ICGReferenceCount // 참조 계수 ..

{…

};

1. WSABUF(CGBUF==WSABUF) 를 상속받는다 .

2. 참조계수

ICGMemory* MEM_POOL_ALLOC(int SIZE);

void* MEM_POOL_ALLOC(int SIZE);

ICGMemory 는 참조계수로 동작하기 위해 정의 되었다 .

풀에서 할당을 요구할 때는 MEM_POOL_ALLOC 를 호출하면 된다 .

MEM_POOL_ALLOC(30);

MEM_POOL_ALLOC(1000);

30 Byte 를 할당요구하려면…

1000 Byte 를 할당요구하려면…

Page 21: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 버퍼 시스템 (2)

CGCII 에서는 풀에서 할당된 메모리를 사용하기 위해 CCGBuffer 를 제공한다 .

CCGBuffer = WSABUF • buf → 메모리 버퍼 포인터• len → 메모리 버퍼 크기

• 참조계수로 할당해제하기 위해

• 버퍼로 사용을 위해 각종 편리한 함수들을 포함한다 .

• 버퍼에 값 쓰기 (Append, <<, …)• 버퍼에서 값 읽기 (Head, Extract, >>, …)

+ 스마트포인터

+ 편리한 버퍼 사용

• 실시간 바운드 체크+ 편리한 디버깅

Page 22: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 버퍼 시스템 (3)

CGCII 에서는 풀에서 할당된 메모리를 사용하기 위해 CCGBuffer 를 제공한다 .

class CCGBuffer :public CGBUF

{public:

CCGBuffer() {}CCGBuffer(ICGMemory* p_ref) : CGBUF(p_ref->buf, 0), m_reference(p_ref) {}CCGBuffer(ICGMemory* p_ref, ULONG p_iP) : CGBUF(p_ref->buf, p_iP), m_reference(p_ref){}…

public:Buffer 의 편한 사용을 위한 각종 함수들…

private:CGPTR<ICGMemory> m_reference;

};

class CCGBuffer :public CGBUF

{public:

CCGBuffer() {}CCGBuffer(ICGMemory* p_ref) : CGBUF(p_ref->buf, 0), m_reference(p_ref) {}CCGBuffer(ICGMemory* p_ref, ULONG p_iP) : CGBUF(p_ref->buf, p_iP), m_reference(p_ref){}…

public:Buffer 의 편한 사용을 위한 각종 함수들…

private:CGPTR<ICGMemory> m_reference;

};

1. WSABUF(CGBUF==WSABUF) 를 상속받는다 .

2. ICGMemory 를 멤버로 가진다 .

Page 23: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 버퍼 시스템 (4)

메모리 풀에서 할당 받아 사용하고 참조계수로 자동 할당 해제된다 .

{

CCGBuffer bufferTEMP = MEM_POOL_ALLOC(1000);

};

{

CCGBuffer bufferTEMP = MEM_POOL_ALLOC(1000);

};

1. Pool 에서 Memory 를 할당 받는다 .

3. 블록이 끝나면 bufferTEMP 가 소멸되며 자동으로 할당 해제한다 .

CCGBuffer bufferCOPY = bufferTEMP;CCGBuffer bufferCOPY = bufferTEMP; 4. 대입하면 참조계수 1 이 증가한다 .

2. 할당 받은 Memory 를 bufferTEMP 에 들어가며 참조계수가 1 이 된다 .

CCGBuffer 는 ‘스마트 포인터’역할도 수행함 .

CCGBuffer 는 편리한 버퍼 사용을 위한 매우 다양한 기능을 제공해 준다 .( 추후 설명 )

CCGBuffer bufferTEMP = MEM_POOL_ALLOC(1000);

bufferTEMP.Append<int>(1); bufferTEMP.Append<DWORD>(30);…bufferTEMP.Head<int>(4);

CCGBuffer bufferTEMP = MEM_POOL_ALLOC(1000);

bufferTEMP.Append<int>(1); bufferTEMP.Append<DWORD>(30);…bufferTEMP.Head<int>(4);

Page 24: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 메모리 블록 풀 (2)

이런 형태의 메모리 블록 풀은 LFH, tcmalloc, jemalloc 모두 사용하고 있음 .

LFH jemalloc

16K Byte

tcmalloc

32K Byte 선택적

CGCIICGPool

8K/64K/256K/1M Byte

블록 크기(Granularity)

최대 적용 크기(Max Range)

8/16/32/64/128/258/512

총 128 개 Buckets선택적 (*)

선택적 (**)

최대 53 개 풀

Google 뿐 아니라 게임 클라이언트 / 서버 등 널리 사용 .

특징 듣보잡

할당 오버헤드(Overhead) 3~7% 수준4% 수준 4%+alpha 수준

8/16/32/~/256 Byte

총 170 Buckets

TLS 적용 No Yes Yes Yes

Slow-start Aver-Convergence- ?Garbage CollectionAlgorithm

Windows XP 이상 기본 동작

Page 25: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 풀 관리 – 재고 관리 (1)

풀 시스템에서 재고 관리를 제대로 하지 않으면 시스템의 불안정을 야기할 수 있다 ! 그래서 매우 중요하다 !

재고가 너무 많이 저장해 놓으면 !재고가 너무 많이 저장해 놓으면 !

재고가 너무 적게 저장해 놓으면 !!재고가 너무 적게 저장해 놓으면 !!

메모리 많이 잡아 먹어 시스템에 문제 야기 !

풀을 하나 마나 !!! 오히려 풀 처리로 성능만 떨어뜨릴 뿐 !

풀 시스템의 핵심은 적절한 재고를 유지하는 것이 풀 시스템 안정화의 핵심 !

적절한 재고처리 시스템이 없다면 사실상 서버에서 풀 시스템을 운용하기에는 위험부담이 너무 크다 !

Page 26: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 풀 관리 – 재고 관리 (2)

tcmalloc 의 경우 TLS cache 크기를 결정 짓는 데 TCP 의 “슬로우 스타트 (Slow-Start)” 알고리즘을 사용한다 한다 .

TCP 의 슬로우 스타트 (Slow Start)

총 TLS cache 크기가 2M 가 넘게 되면 재고 처리를 시작한다 .

Page 27: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 풀 관리 – 재고 관리 (3)

CGPool 은 “평균 수렴 시스템”을 사용한다 .

평균과 표준편차를 구해 최대 저장 재고량을 결정하고 그 이상이 넘으면 재고를 처분하는 알고리즘이다 .

사용량

시간

Page 28: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 풀 관리 – 재고 관리 (3)

CGPool 은 기본적 재고관리뿐만 아니라 4 단계의 위기 관리 시스템이 있다 .

Level 1

Level 2

Level 3

Level 4

→ 일반적인 상황 앞에서 설명한 대로 동작한다 .

→ 한계 재고 처리량으로 보다 빨리 수렴하도록 한다 .

→ 재고를 전혀 쌓지 않는다 . (Pool 을 사용하지 않는 것과 동일하다 .)

→ 즉시 한계 재고량으로 수렴시킨다 .

Page 29: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 풀 관리 – 통계 처리

CGPool 은 사용량 , 할당량 , 해제량 , 생성량 , 소멸량 등 다양한 통계정보를 실시간으로 제공한다 .

그 외에 수동 Prepare 기능 , Shrink 기능 등 다양한 관리 기능을 제공해 준다 .

저장된 객체 수

할당 중인 객체 수현재 존재하는 총 객체 수

총 할당 요구 횟수

최대 객체 수 새로 생성된 객체 수지워버린 객체 수

재사용률생성 / 소멸 비율

객체 명

Page 30: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

풀시스템 Performance Time

CGPool과 tcmalloc 그리고 기존의 LFH 의 성능을 비교한다 .

특정 크기의 메모리를 [1000] 번 할당 후 [1000] 번 할당 해제하는 것을 [1000] 번 반복하는 테스트를 수행한다 .

Page 31: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

All Right Reserved ©CGLabs

Socket PerformanceCGCIICho sanghyun’s Game Classes II

Page 32: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

Windows Socket 에서 성능의 핵심은 뭐니 뭐니해도…

• 송수신 중 복사의 최소화 ![ 무복사 송수신 ]

• Gathering!

• 쓰레드 풀• 효율적인 다중 쓰레드 처리

[ProAct 패턴 지원 ]

Windows Socket 에서 바로 이 기능들을 최대로 지원하고 최적화를 하는 것이 고성능의 서버 엔진의 위한 관건 !

Overlapped I/O IOCP와

CGCIICho sanghyun’s Game Classes II

소켓성능 TCP 의 성능

Page 33: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

링 버퍼 ( 혹은 Circular) 사용

CGCIICho sanghyun’s Game Classes II

소켓성능 TCP 수신 (Receive) 방법

선형 버퍼 사용 (Head/Tail Ptr 만 사용 )

CGCII 의 방법

선형 버퍼 사용 (Tail Ptr 만 사용 )

일반적이고 전통적인 방법

대규모 메모리 사용하는 선형 버퍼

가장 간단한 방법

메모리 풀을 사용하여 한번 사용한 메모리를 버리고 새로운 버퍼를 할당 받는 방법

Overlapped I/O 를 사용하여 수신 처리하는 방법은 다양할 수 있다 .

Page 34: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

전형적인 Ring Buffer 를 사용한 방법 .

Receive

Message Buffer

CGCIICho sanghyun’s Game Classes II

소켓성능

Head PtrTail Ptr

Receive Receive

처리가 매우 복잡다 . 메시지의 처리를 위해 일반적으로 다른 버

퍼를 생성해 복사를 수행해야 한다 . 송수신량이 적을 때는 오히려 필요 이상의

부하와 메모리를 사용하게 된다 .

1. Head 포인터 앞부분과 Tail 포인터 뒷부분을 [Receive] 건다 .

Overlapped I/O 걸 메모리 효율이 좋다 . 수신과 메시지 처리를 각각 다른 쓰레드에서

처리가 가능하다 .

2. 수신이 완료 되면 메시지 버퍼에 받은 데이터를 복사한다 .3. 메시지버퍼는 Work Thread 에 걸어주고 다시 1 번부터 무한반복 ~

장점 단점

TCP 수신 (Receive) 처리 방법 (1)

Head/Tail Ptr

Receive Buffer(Ring Buffer)

Overlapped I/O 를 걸 버퍼

메시지 처리

Page 35: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

아주 간단하게 Receive 버퍼를 만들 수도 있다 .

Receive

1. Tail 포인터 ( 혹은 len) 뒤쪽 메모리를 모두 [Receive] 건다 .

3. 다시 1 번부터 무한반복 .

CGCIICho sanghyun’s Game Classes II

소켓성능

Tail Ptr

2. 수신이 완료되면 메시지를 처리하고 남은 것은 앞으로 복사해서 땡긴다 .

처리 구조가 매우 간단하다 . 이렇게 되면 메시지가 끊겨서 복사해야 하

는 일은 역시 없다 !!

I/O 와 메시지 처리를 각각 다른 쓰레드에서 처리할 수는 역시 없다 .

매번 메시지를 처리할 때마다 메모리 복사를 할 수도 있고 부하가 늘어나면 그 빈도가 증가할 수 있다 .

장점 단점

TCP 수신 (Receive) 처리 방법 (2)

Receive Buffer

Receive

Page 36: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

매번 받을 때마다 복사가 될 수 있는 위험이 있기 때문에 크게 버퍼를 잡고 Head 포인터와 Tail 포인터를 사용하여 수신처리를 한다 !

Receive

처리 구조는 매우 간단한 편이다 . 이렇게 되면 메시지가 끊겨서 복사해야 하

는 일은 없다 !!

CGCIICho sanghyun’s Game Classes II

소켓성능

Head PtrTail Ptr

I/O 와 메시지 처리를 각각 다른 쓰레드에서 처리할 수는 없다 .

잦은 메모리 복사를 막을 수 있다 . 효율대비 고정적 메모리 사용이 많다 .

1. Tail 포인터 뒷부분에 대해서 [Receive] 를 건다 .2. 수신이 완료되면 Head 포인터부터 메시지를 처리하고 Head 포인터를 옮긴다 .3. Head 포인터 ( 혹은 Tail 포인터 ) 가 특정 위치를 넘어서면 앞으로 땡겨서 복사한 후 1 번부터 무한반복

장점 단점

TCP 수신 (Receive) 처리 방법 (3)

Head/Tail Ptr

Receive

Receive Buffer

Receive

Page 37: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

앞의 방법들과 유사하나 한번 사용한 메모리는 버리고 항상 새로 할당 받는다는 것이 다르다 .

Receive

2. 수신이 완료되면 Head 포인터부터 메시지를 처리하고 Head 포인터를 옮긴다 .

1. Tail 포인터 뒷부분에 대해서 [Receive] 를 건다 .

CGCIICho sanghyun’s Game Classes II

소켓성능

3. Head 포인터 ( 혹은 Tail 포인터 ) 가 특정 위치를 넘어서면 새로 버퍼를 할당 받아 남은 데이터를 복사한 후 1 번부터 무한반복

처리 구조는 비교적 간단하다 . 이렇게 되면 불연속적 메시지 버퍼로 인해 복사를 해야 하는 일은 역시 없다 !! I/O 와 메시지 처리를 각각 다른 쓰레드에서 처리하는 것도 문제없다 . 받은 메모리를 저장 후 사용하거나 재송신에 사용해 추가적으로 메모리 복사량

을 줄일 수 있다 . 버퍼 크기를 수신 상황에 따라 바꾼다면 더 금상첨화 .

?장점 단점

CGCII TCP 수신 (Receive) 처리 방법

Receive Buffer

Head/Tail PtrHead PtrTail Ptr참조계수

00

11

Page 38: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

수신 받는 량에 따라 Buffer 의 크기를 변화시키는 것을 말한다 .

너무 크게 잡으면

CGCIICho sanghyun’s Game Classes II

소켓성능 CGCII 가변 수신 버퍼

고정된 크기로 버퍼를 잡을 경우 .

너무 작게 잡으면

더 많은 메모리가 소모된다 .성능이 떨어지거나 큰 메시지를 받지 못할 수 있다 .

WSAIoctl() 함수 혹은 ioctlsocket() 함수와 FIONREAD 플래스를 사용하여 수신한 데이터량을 확인하여 Receive Buffer 의 크기를 변경시킨다 .

→ 큰 크기의 버퍼를 새로 할당 받아야 하므로 반드시 풀 시스템이 기반이 되어야 한다 !

가변 수신 버퍼를 사용하면 성능과 메모리 사용량 모두를 해결할 수 있다 .

CGCII 에서는 당연히 적용되어 있다 . ( 옵션으로 MIN SIZE 와 MAX SIZE 를 설정할 수 있다 .)

Page 39: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

소켓성능 메시지 처리 (1)

데이터 수신과 메시지 처리의 방법적 문제에서도 성능의 차이가 있을 수 있다 .

Overlapped I/O 로 데이터 수신 후 핵심 처리 과정은 대략 아래와 같은 3 가지 단계이다 .

• 받은 데이터를 메시지별로 분리하는 작업이 필요하다 .• 일반적으로 메시지 헤드 (Head) 에서 메시지 크기를 확인하고 메

시지 별로 분리작업을 한다 .

• 분리된 각 메시지가 어떤 메시지인지를 확인하여 메시지에 맞는 처리를 하는 과정이다 .

• 가장 부하가 많이 걸릴 수 있는 부분이다 .• 일반적으로 switch-case 문을 사용하여 처리한다 .

• 데이터를 받기 위해 데이터를 받을 버퍼를 준비하고 정리한 후 다시 Recv() 를 걸어주는 과정이다 .

Page 40: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

수신 받은 데이터를 메시지 별로 분리해 처리하는 방법은 대략 아래 3 가지 정도로 정리

CGCIICho sanghyun’s Game Classes II

소켓성능 메시지 처리 (2)

• 수신 받는 즉시 메시지 분리와 처리 그리고 Recv() 걸기까지 모두 처리한다 .

• 수신 받은 후 메시지를 분리하여 큐잉하고 바로 Recv() 를 걸어준다 .• 메시지의 처리는 다른 쓰레드에서 큐잉된 메시지를 꺼내 처리한다 .

• 수신 받은 후 데이터를 즉시 큐잉만 하고 Recv() 를 건다 .• 메시지 분리와 처리는 다른 Thread 에서 한다 .

메시지 분리 메시지 처리 Recv() 걸기+ +

메시지 분리 큐잉 Recv() 걸기+ +

큐잉 Recv() 걸기+

메시지 처리디큐잉 +

메시지 처리디큐잉 + 메시지 분리 +

I/O Thread

I/O Thread

I/O Thread

Work Thread

Work Thread

Page 41: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

데이터를 받은 후 메시지 분리 , 메시지 처리를 모두 수행하고 Recv() 를 거는 방식이다 .

CGCIICho sanghyun’s Game Classes II

소켓성능 메시지 처리 (2) – 방법 1

Recv 다시 걸기Recv 다시 걸기

메시지 수신메시지 수신

종료종료

메시지 처리메시지 처리

메시지 분리메시지 분리

처리 구조는 매우 간단한 편이다 . 데이터 수신이 많지 않을 경우 효율적이다

.

메시지분리와 처리를 쓰레드에 분산해서 처리할 수 없다 .

단일 소켓으로 많은 데이터를 수신할 경우 다중 코어에 분산처리할 수 없어 비효율적 .

따라서 모든 데이터를 처리한 후 Recv() 를 걸어야 하기 때문에 데이터를 빨리 비울 수 없어 그만큼 수신 성능이 떨어진다 .

장점 단점

I/O Thread

Page 42: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

데이터를 받은 후 메시지 분리하여 분리된 메시지 정보를 큐잉한 후 Recv() 를 거는 방식이다 . 링버퍼를 사용할 경우 이런 방법을 많이 사용한다 .

CGCIICho sanghyun’s Game Classes II

소켓성능 메시지 처리 (2) – 방법 2

Recv 다시 걸기Recv 다시 걸기

메시지 수신메시지 수신

종료종료

메시지 큐잉

메시지 분리메시지 분리

메시지 처리메시지 처리

메시지 수신메시지 수신

종료종료

메시지 디큐잉

I/O Thread Work Thread

처리 과정이 좀 복잡하다 I/O 처리와 메시지처리를 분리해서 처리할

수 있다 . 빠르게 Recv() 를 다시 걸어 데이터 수신

성능을 향상시킬 수 있다 .

메시지 수신량이 적을 때는 오히려 추가적인 부담만 늘어날 수 있다 .

추가적인 메모리 부담이 있을 수 있다 .

장점 단점

Page 43: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

데이터를 받은 후 즉시 큐잉만 하고 Recv() 를 거는 방식이다 .

CGCIICho sanghyun’s Game Classes II

소켓성능 메시지 처리 (2) – 방법 3

Recv 다시 걸기Recv 다시 걸기

메시지 수신메시지 수신

종료종료

데이터 큐잉

메시지 처리메시지 처리

메시지 수신메시지 수신

종료종료

데이터 디큐잉

I/O Thread Work Thread

처리 과정이 좀 복잡하다 I/O 처리와 메시지처리를 분리해서 처리할

수 있다 . 가장 빠르게 Recv() 를 다시 걸어 가장 뛰

어난 데이터 수신 성능을 제공한다 .

메시지 수신량이 적을 때는 오히려 추가적인 부담만 늘어날 수 있다 .

장점 단점

메시지 분리메시지 분리

Page 44: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

Overlapped I/O 를 사용하여 송신 (Send) 처리를 하는 방법은 크게 아래와 같이 방법이 있다 .

CGCIICho sanghyun’s Game Classes II

소켓성능 TCP 송신 (Send) 처리 방법

Gathering (A)

Gathering (B)

• 전송 요청 즉시 Overlapped I/O 를 걸어 Send 를 수행함• 전송 빈도가 적을 때 효율적인 반면 많으면 전송 효율 급하락 .

Gathering 버퍼를 두어 데이터를 이곳에 복사하여 전송하는 방법

Gathering 버퍼를 사용하지 않고 WSASend 의 Gathering 기능을 사용하여 전송하는 방법CGCII 에서 사용하는 방법

• 하나의 소켓당 한번에 하나의 Overlapped I/O 만 걸어 Send 를 수행하는 방법 .• Send 중이면 Queuing 하였다가 Send 가 완료 후 한꺼번에 전송한다 .• 전송 빈도가 많으면 효율 좋음 .

단순 Gathering전송을 요청하면 무조건 Queuing 하고 일정 시간 마다 Queuing 된 데이터를 전송하는 방법( 반응속도 느림 , 전송용 쓰레드 추가적으로 필요 )

Page 45: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

Overlapped Send

Overlapped Send

전송 요청전송 요청

전송 끝전송 끝

전송 완료전송 완료

완료 처리 끝완료 처리 끝

Buffer AddRef()Buffer

AddRef()

Buffer Release()

Buffer Release()

CGCIICho sanghyun’s Game Classes II

소켓성능 TCP 송신 (Send) 처리 방법 (1) – 단순

가장 일반적이고 간단한 Overlapped I/O 를 사용한 송신 처리 방법 한번의 Send 요청에 한번의 Overlapped I/O 를 거는 방법

처리 구조가 매우 간단한다 . 크기가 큰 메시지를 띄엄띄엄 전송한다면

효과가 매우 좋다 .

작은 메시지를 매우 빈번히 전송할 경우 CPU를 매우 많이 사용하고 성능이 급격히 떨어진다 .( 그냥 Send()보다 더 떨어진다 .)

장점 단점

Page 46: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

전송 요청전송 요청

자체 송신 버퍼에 memcpy()

자체 송신 버퍼에 memcpy()

전송 끝전송 끝

시작시작

전송할 것이 있나 ?

전송할 것이 있나 ?

완료 처리 끝완료 처리 끝

No

Yes

Overlapped SendOverlapped Send

LOCK

CGCIICho sanghyun’s Game Classes II

소켓성능 TCP 송신 (Send) 처리 방법 (2) – 단순 Gathering

전송을 요청하면 무조건 Queuing 하고 Send 용 Thread 가 일정 시간 마다 Queuing 된 데이터를 확인하여 전송하는 방법

반응속도 느리고 전송용 쓰레드가 추가적으로 필요함 . 구시대의 유물로 비추 방법 !!!

Send 용 Thread

전송 완료전송 완료

완료 처리 끝완료 처리 끝

완료처리( 버퍼 정리 )완료처리

( 버퍼 정리 )

일정시간 Sleep일정시간 Sleep

LOCK

Page 47: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

전송 요청전송 요청

자체 송신 버퍼에 memcpy()

자체 송신 버퍼에 memcpy()

bSending?bSending?

false

true

전송 끝전송 끝

bSending=truebSending=true

전송 완료전송 완료

완료 처리완료 처리

전송할 것이 있나 ?

전송할 것이 있나 ?

완료 처리 끝완료 처리 끝

버퍼 내용전송

버퍼 내용전송

No

Yes

bSending=falsebSending=false

LOCK

LOCK

CGCIICho sanghyun’s Game Classes II

소켓성능 TCP 송신 (Send) 처리 방법 (3) – Gathering (A)

내부 송신 버퍼에 송신 내용을 복사한 후 모아서 Overlapped I/O 를 거는 방법 하나의 소켓에서 동시에 한번의 Overlapped Send 만 걸리도록 한다 .

Overlapped SendOverlapped Send

Page 48: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

1:N 전송은 왜 ?

• 같은 버퍼를 여러 곳에 전송하면 각 소켓의 버퍼에 복사를 해 넣어야 한다 !!!• 특히 Server 에서 이런 전송은 매우 빈번하다 !

SocketSocket

SocketSocket

SocketSocket

SocketSocket

.

.

.Message Buffer

Send BufferSend Buffer

Send BufferSend Buffer

Send BufferSend Buffer

Send BufferSend Buffer

복사

복사복사

복사

CGCIICho sanghyun’s Game Classes II

소켓성능

이렇게 되면 결론적으로 Overlapped I/O 의 가장 큰 장점이 무력화된다 !!!!!

TCP 송신 (Send) 처리 방법 (4)

처리 구조가 좀 복잡다 . 한꺼번에 모아 전송하므로 Overlapped I/O

량을 줄일 수 있다 . 작은 메시지를 빈번히 전송할 경우 성능이

우수하다 .

전송 때 반드시 ! memcpy() 를 동반한다 . 따라서 큰 메시지 전송이나 1:N 전송을 수

행하면 잦은 메모리 복사로 인해 성능이 급격히 떨어질 수 있다 .

장점 단점

Page 49: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

int WSASend (SOCKET s,LPWSABUF lpBuffers,DWORD dwBufferCount,LPDWORD lpNumberOfBytesSent,DWORD dwFlags,LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE

);

int WSASend (SOCKET s,LPWSABUF lpBuffers,DWORD dwBufferCount,LPDWORD lpNumberOfBytesSent,DWORD dwFlags,LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE

);

어떻게 IOCP 의 장점을 가진체 I/O 를 최소화할 수 있는 방법은 없을까 ?

→ 하지만 !! WSASend 에 Gathering 기능을 사용하면 어떨까 ?

Gathering 전송을 지원한다 !

→ 일반적으로 한꺼번에 모으려면 복사를 해야 한다 !!!

CGCIICho sanghyun’s Game Classes II

소켓성능 TCP 송신 (Send) 처리 방법 (5)

Page 50: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

포인터만 큐잉포인터만 큐잉

bSending?bSending?

Overlapped Send

Overlapped Send

false

true

전송 요청전송 요청

전송 끝전송 끝

완료완료

Buffers Release()Buffers Release()

큐잉된 것이 있나 ?

큐잉된 것이 있나 ?

완료 처리 끝완료 처리 끝

큐잉된 것 모두 꺼내서 ~

큐잉된 것 모두 꺼내서 ~

No

Yes

bSending=truebSending=true bSending=falsebSending=false

LOCK

LOCKBuffer

AddRef()Buffer

AddRef()

Overlapped Send

Overlapped Send

Gathering Send

CGCIICho sanghyun’s Game Classes II

소켓성능 TCP 송신 (Send) 처리 방법 (6) - Gathering (B)

Page 51: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

소켓성능

Message Buffer

Head Ptr Tail Ptr

Send Buffer

자 그렇다면 에코 송수신을 처리할 때 기존의 링 - 버퍼를 사용하고 그냥 모아 전송을 사용하면 어떻게 될까 ?

복사

복사

복사 복

사 복사

Receive

Send

메시지 생성기

복사

메아리 (Echo) 송수신 (1)

Page 52: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

소켓성능

Message Buffer

Head Ptr Tail Ptr

Send Buffer

CGCII 의 송수신 방법을 사용하여 Echo 전송을 수행한다고 하면…

Receive

Send

메아리 (Echo) 송수신 (2)

아무런 복사를 수행하지 않고 단지 참조계수만으로 처리됨 !

112233445566

Page 53: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

소켓성능 Performance Time

Socket 의 Echo 송수신 테스트를 수행한다 .

Page 54: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCII 의 Socket 의 특성에 따라 다른 튜닝을 할 수 있도록 해준다 .

CGCIICho sanghyun’s Game Classes II

소켓성능 CGCII Socket Tunning

일반 Overlapped 전송

Gathering 전송을 지원하는 전송

받는 즉시 메시지 처리받은 데이터 큐잉 후 다른 쓰레드가 처리

(I/O 와 Work 의 분리 )

TCP Socket Send 버퍼 크기

Gather 큐 최대 깊이

TCP Socket Receive 버퍼 크기

수신 버퍼 크기 ( 최소 / 최대 )

수신 큐 최대 깊이

Page 55: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

메신저나 채팅을 위한 소켓

CGCIICho sanghyun’s Game Classes II

소켓성능 CGCII Socket Tunning

Send/Receive 가 비교적 적다대규모 접속이 필요하다 .

다운로드 서버를 위한 소켓Send 가 엄청나게 많다 .같은 데이터를 여러 군데 전송한다 .

서버간 연결을 위한 소켓단일 연결로 많은 Send/Receive 수행접속 안정성 최대화 필요

일반 게임 서버에 접속하는 유저용 소켓Send/Receive 가 중간 정도 ~

다운로드 클라이언트를 위한 소켓Receive 량이 엄청나게 많다 .Receive 후 처리 과정이 매우 길다 .

Send-Simple, Receive-Simple모든 버퍼 최소 크기 설정하여 Socket 당 사용 메모리 최소화

Send-Gather 혹은 전용모든 버퍼 최소 크기 설정하여 Socket 당 사용 메모리 최소화

Send-Simple, Receive-Queued수신버퍼 크기 최대화하여 수신효율 극대화 .

Send-Gather, Receive-Queued송수신버퍼 크기 최대화하여 송수신효율 모두 극대화 .

Send-Gather, Receive-Simple송수신 버퍼 크기 중간 정도 .

Socket 의 특성에 따라 최적의 성능을 내기 위해서 다양한 튜닝이 있을 수 있다 .

Page 56: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

소켓성능

매번 전송 때마다 메시지를 작성하는 것이 아니라 한번 작성해 놓고 지속적으로 사용하는 메시지를 의미한다 .

메시지의 많은 부분이 정적 메시지일 수 있다 .

정적 메시지 버퍼 (1)

한번 작성하면 끝날 때까지 메시지를 계속 쓰는 것이 아니라 일정 시간마다 갱신해야 하는 경우 한번 작성한 메시지를 여러 번 사용할 수도 있다 .

어떠한 메시지는 대부분의 값은 동일하고 일부 값만 살짝 틀린 메시지들이 있을 수 있다 . 그럴 경우 나머지 설정된 값들은 그대로 사용하고 수정된 부분만 바꾸어 전송하는 것을 의미한다 .

이것을 위한 ‘메시지 버퍼 풀 (CGPool::CBuffer<I>)’을 제공한다 .

메시지 버퍼 풀

정적 메시지 버퍼 (2)

정적 메시지 버퍼 (1)

Page 57: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

소켓성능 접속종료와 소켓 재사용

접속종료 처리시 DisconnectEx를 사용하여 소켓 재사용 할 수 있다 .따라서 매번 socket 을 생성 / 소멸 시킬 필요가 없다 !

하지만 DisconnectEx가 간혹 완료가 되지 않는 경우를 대비해야 한다 .(보통 0.000001% 정도의 낮은 확률로 DisconnectEx가 완료되지 않는 경우가 있고 CGCII 는 이를 대비해 DisconnectEx를 건 Socket 의 List 를 보관 후 일정 시간 동안 접속 종료처리가 되지 않으면 강제 완료 처리한다 .)

AcceptEx는 접속 받기 성능을 높여준다 .

AcceptEx와 ConnectEx를 사용하면 접속 처리를 비동기함수를 사용하여 처리가 가능하다 .

AcceptEx와 ConnectEx는 접속받기와 접속하기를 IOCP 에 통합할 수 있도록 해준다 .

DisconnectEx와 Socket Reuse 의 사용

AcceptEx와 ConnectEx의 사용

Page 58: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

소켓성능 Lock-free 의 광범위한 적용

Lock-free Stack 이나 Queue말고도 많은 곳에 Lock-free 처리를 적용하여 Critical-section 의 사용을 최소화하였다 .

bool foo::Start(){

long preState;

preState = InterlockedExchange(&m_bStart, 1);

RETURN_IF(preState==0, false);

bool foo::Start(){

long preState;

preState = InterlockedExchange(&m_bStart, 1);

RETURN_IF(preState==0, false);

bool foo::Start(){

long preState;

preState = InterlockedCompareExchange(&m_bState, STATE_SYN, STATE_CLOSE);

RETURN_IF(preState==STATE_CLOSE, false);

bool foo::Start(){

long preState;

preState = InterlockedCompareExchange(&m_bState, STATE_SYN, STATE_CLOSE);

RETURN_IF(preState==STATE_CLOSE, false);

Page 59: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

소켓성능 최적 자료 구조 사용

빈번한 삽입이나 삭제되는 큐 같은 것의 처리를 위해 CGD::circular_list<T> 등을 사용한다 . ( 대표적으로 각종 Manager들… )

std::list

CGD::circular_list

Head Tail

Head

Tail

Page 60: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

소켓성능 반복 처리 최소화

최대한 검색하거나 찾는 과정에서 Loop 처리 과정을 제거했다 .

void Manager::Remove(foo* p_pobject){

list<CGPTR<foo>>::iterator iter = m_objects.begin();list<CGPTR<foo>>::iterator iterEnd = m_objects.end();

for(; iter!=iterEnd; ++iter){

if(*iter==p_pObject){

m_objects.erase(iter);break;

}}

}

void Manager::Remove(foo* p_pobject){

list<CGPTR<foo>>::iterator iter = m_objects.begin();list<CGPTR<foo>>::iterator iterEnd = m_objects.end();

for(; iter!=iterEnd; ++iter){

if(*iter==p_pObject){

m_objects.erase(iter);break;

}}

}

제거를 위해 Iterator 을 돌아야 한다 !

Page 61: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

소켓성능 반복 처리 최소화

최대한 검색하거나 찾는 과정에서 Loop 처리 과정을 제거했다 .

// Error & Exception 처리 생략 !!void Manager::Add(foo* p_pobject){

m_objects.push_front(p_pobject);

p_pobject->m_iter = m_objects.begin();}

// Error & Exception 처리 생략 !!void Manager::Add(foo* p_pobject){

m_objects.push_front(p_pobject);

p_pobject->m_iter = m_objects.begin();}

1. 생성 할 때 iterator 을 저장해 놓는다 .

void Manager::Remove(foo* p_pobject){

m_objects.erase(m_objects->m_iter);}

void Manager::Remove(foo* p_pobject){

m_objects.erase(m_objects->m_iter);}

2. 한 번에 지운다 !

당연히 std::map보다도 훨씬 빠르다 !!(std::map 도 O(log n) 의 부하가 필요하고 한 스텝 자체의 크기와 Balancing 을 위한 부하도 무시할 수 없다 .)

Page 62: GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance

CGCIICho sanghyun’s Game Classes II

질문 ?

중간 질문 ?

소켓성능