gcgc- cgcii 서버 엔진에 적용된 기술 (2) - perfornance
TRANSCRIPT
Pool SystemCGCIICho sanghyun’s Game Classes II
멀티테스크 환경하에서 제한된 메모리를 효율적 사용을 위한 메모리 시스템 .필요할 경우 필요할 만큼 할당요구하고 다 사용하면 반납하는 시스템 .
풀 (Pool) 시스템
CGCIICho sanghyun’s Game Classes II
풀시스템
성능에 초점을 둔 풀 시스템의 필요성 엄청 대두 !!
힙 (Heap) 즉 “동적 할당기”=
하지만 , 메모리 용량이 커지고 빈번한 사용에 따라 성능 문제도 이슈화 !우리가 인식하지 못하는 사이 수도 없이 많은 동적 할당과 해제를 사용하고 있다 !(std::list 나 std::map 에 push/pop 을 할 때도 매번 동적 할당과 해제가 일어난다 .)
풀의 기본적 개념
은 jemalloc 라는 힙을 사용해 속도 향상을 얻었다고 한다 .
동적 할당 성능의 중요성
의 tcmalloc 를 개발해 내놓았다 !!!( 어떤 곳에서 단지 tcmalloc 의 사용만으로 20% 의 성능향상을 얻었다고 한다 .)
완전 답게 !! 기본 힙 (heap) 은 그렇게 좋은 성능을 가지고 있지 못했다 !
그래서 는 성능을 업그레이드 시킨 힙 시스템인
LFH(Low Fragmentation Heap) 을 내놓았다 . (Windows XP 이후는 기본 적용된다 .)
CGCIICho sanghyun’s Game Classes II
풀시스템
CGCIICho sanghyun’s Game Classes II
풀시스템 객체 풀
객체 풀이 단순한 메모리만을 할당하는 풀에 비해 매우 유리한 장점이 존재한다 .
1. 메모리 할당 자체에 대한 성능 향상을 기대할 수 있다 . ( 이것은 공통 )
2. 객체의 생성자와 소멸자의 호출 및 초기화 에 드는 비용을 최소화할 수 있다 .
→ 최소한의 재생 (Recycle) 과정만을 거쳐 할당할 수 있다 . → 복합객체나 초기화 비용이 큰 객체일수록 엄청난 비용 차이가 난다 . → 이 효과는 단순한 메모리 할당자인 tcmalloc 나 jemalloc 로는 기대할 수 없다 .
3. 객체 풀을 기반으로 메모리 풀을 만든다 .
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 보다 빠르다는 전제하에서 ~~
CGCIICho sanghyun’s Game Classes II
풀시스템 객체풀 시스템의 기본 원리
일단은 성능 좋은 [ 스택 ] 을 구현하는 것이 핵심이닷 !!!단 , 조건이 있다 !
첫째 , new/delete 혹은 malloc()/free() 보다 넘사벽으로 빨라야 한다 !
둘째 , 쓰레드에 대해 안전 (Thread-safe) 해야 한다 !!!
[ 스택 ] 이라면 뭐 .. 간단히 ~~ 간단히 !!!STL 에 std::list<T> 나 std::vector<T> 를 쓰면 간단히 해결되겠네 !!!!!!!!역시 결론은 STL!!여기에다 쓰레드 안전을 위해 크리티컬 섹션 걸어서 쓰면 되겠네 !!!!
이건 뭐 엄청 쉽다 !!!! 초딩도 할 수 있을 것 같아 ~
라고 생각하다 보통 멘붕을 맞게 됨 ...
초간단데스네 !!
셋째 , 안정성이 확보되어야 한다 !!!
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 ]
가 등장함 !!!
이미 실패!
이미 실패!
역시 실패!
역시 실패!
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() {}};
다음 객체를 가리키기 위한 변수 !!!
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()
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 한다 .
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) 는 특성상 다중 쓰레드 환경 하에 많은 부하가 걸리는 상태에서 더 엄청난 성능 향상을 준다 .
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 스택
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. 다 사용하고 풀에 객체 반환하기 ~
매번 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)
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>
자동 참조계수 처리된다 .
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>
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. 풀을 사용한 객체
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)
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();}
할당을 요구하는 메모리의 크기에 따라 어떤 풀을 선택할 것인가는 매크로로 정의
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 를 할당요구하려면…
CGCIICho sanghyun’s Game Classes II
풀시스템 버퍼 시스템 (2)
CGCII 에서는 풀에서 할당된 메모리를 사용하기 위해 CCGBuffer 를 제공한다 .
CCGBuffer = WSABUF • buf → 메모리 버퍼 포인터• len → 메모리 버퍼 크기
• 참조계수로 할당해제하기 위해
• 버퍼로 사용을 위해 각종 편리한 함수들을 포함한다 .
• 버퍼에 값 쓰기 (Append, <<, …)• 버퍼에서 값 읽기 (Head, Extract, >>, …)
+ 스마트포인터
+ 편리한 버퍼 사용
• 실시간 바운드 체크+ 편리한 디버깅
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 를 멤버로 가진다 .
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);
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 이상 기본 동작
CGCIICho sanghyun’s Game Classes II
풀시스템 풀 관리 – 재고 관리 (1)
풀 시스템에서 재고 관리를 제대로 하지 않으면 시스템의 불안정을 야기할 수 있다 ! 그래서 매우 중요하다 !
재고가 너무 많이 저장해 놓으면 !재고가 너무 많이 저장해 놓으면 !
재고가 너무 적게 저장해 놓으면 !!재고가 너무 적게 저장해 놓으면 !!
메모리 많이 잡아 먹어 시스템에 문제 야기 !
풀을 하나 마나 !!! 오히려 풀 처리로 성능만 떨어뜨릴 뿐 !
풀 시스템의 핵심은 적절한 재고를 유지하는 것이 풀 시스템 안정화의 핵심 !
적절한 재고처리 시스템이 없다면 사실상 서버에서 풀 시스템을 운용하기에는 위험부담이 너무 크다 !
CGCIICho sanghyun’s Game Classes II
풀시스템 풀 관리 – 재고 관리 (2)
tcmalloc 의 경우 TLS cache 크기를 결정 짓는 데 TCP 의 “슬로우 스타트 (Slow-Start)” 알고리즘을 사용한다 한다 .
TCP 의 슬로우 스타트 (Slow Start)
총 TLS cache 크기가 2M 가 넘게 되면 재고 처리를 시작한다 .
CGCIICho sanghyun’s Game Classes II
풀시스템 풀 관리 – 재고 관리 (3)
CGPool 은 “평균 수렴 시스템”을 사용한다 .
평균과 표준편차를 구해 최대 저장 재고량을 결정하고 그 이상이 넘으면 재고를 처분하는 알고리즘이다 .
사용량
시간
CGCIICho sanghyun’s Game Classes II
풀시스템 풀 관리 – 재고 관리 (3)
CGPool 은 기본적 재고관리뿐만 아니라 4 단계의 위기 관리 시스템이 있다 .
Level 1
Level 2
Level 3
Level 4
→ 일반적인 상황 앞에서 설명한 대로 동작한다 .
→ 한계 재고 처리량으로 보다 빨리 수렴하도록 한다 .
→ 재고를 전혀 쌓지 않는다 . (Pool 을 사용하지 않는 것과 동일하다 .)
→ 즉시 한계 재고량으로 수렴시킨다 .
CGCIICho sanghyun’s Game Classes II
풀시스템 풀 관리 – 통계 처리
CGPool 은 사용량 , 할당량 , 해제량 , 생성량 , 소멸량 등 다양한 통계정보를 실시간으로 제공한다 .
그 외에 수동 Prepare 기능 , Shrink 기능 등 다양한 관리 기능을 제공해 준다 .
저장된 객체 수
할당 중인 객체 수현재 존재하는 총 객체 수
총 할당 요구 횟수
최대 객체 수 새로 생성된 객체 수지워버린 객체 수
재사용률생성 / 소멸 비율
객체 명
CGCIICho sanghyun’s Game Classes II
풀시스템 Performance Time
CGPool과 tcmalloc 그리고 기존의 LFH 의 성능을 비교한다 .
특정 크기의 메모리를 [1000] 번 할당 후 [1000] 번 할당 해제하는 것을 [1000] 번 반복하는 테스트를 수행한다 .
All Right Reserved ©CGLabs
Socket PerformanceCGCIICho sanghyun’s Game Classes II
Windows Socket 에서 성능의 핵심은 뭐니 뭐니해도…
• 송수신 중 복사의 최소화 ![ 무복사 송수신 ]
• Gathering!
• 쓰레드 풀• 효율적인 다중 쓰레드 처리
[ProAct 패턴 지원 ]
Windows Socket 에서 바로 이 기능들을 최대로 지원하고 최적화를 하는 것이 고성능의 서버 엔진의 위한 관건 !
Overlapped I/O IOCP와
CGCIICho sanghyun’s Game Classes II
소켓성능 TCP 의 성능
링 버퍼 ( 혹은 Circular) 사용
CGCIICho sanghyun’s Game Classes II
소켓성능 TCP 수신 (Receive) 방법
선형 버퍼 사용 (Head/Tail Ptr 만 사용 )
CGCII 의 방법
선형 버퍼 사용 (Tail Ptr 만 사용 )
일반적이고 전통적인 방법
대규모 메모리 사용하는 선형 버퍼
가장 간단한 방법
메모리 풀을 사용하여 한번 사용한 메모리를 버리고 새로운 버퍼를 할당 받는 방법
Overlapped I/O 를 사용하여 수신 처리하는 방법은 다양할 수 있다 .
전형적인 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 를 걸 버퍼
메시지 처리
아주 간단하게 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
매번 받을 때마다 복사가 될 수 있는 위험이 있기 때문에 크게 버퍼를 잡고 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
앞의 방법들과 유사하나 한번 사용한 메모리는 버리고 항상 새로 할당 받는다는 것이 다르다 .
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
수신 받는 량에 따라 Buffer 의 크기를 변화시키는 것을 말한다 .
너무 크게 잡으면
CGCIICho sanghyun’s Game Classes II
소켓성능 CGCII 가변 수신 버퍼
고정된 크기로 버퍼를 잡을 경우 .
너무 작게 잡으면
더 많은 메모리가 소모된다 .성능이 떨어지거나 큰 메시지를 받지 못할 수 있다 .
WSAIoctl() 함수 혹은 ioctlsocket() 함수와 FIONREAD 플래스를 사용하여 수신한 데이터량을 확인하여 Receive Buffer 의 크기를 변경시킨다 .
→ 큰 크기의 버퍼를 새로 할당 받아야 하므로 반드시 풀 시스템이 기반이 되어야 한다 !
가변 수신 버퍼를 사용하면 성능과 메모리 사용량 모두를 해결할 수 있다 .
CGCII 에서는 당연히 적용되어 있다 . ( 옵션으로 MIN SIZE 와 MAX SIZE 를 설정할 수 있다 .)
CGCIICho sanghyun’s Game Classes II
소켓성능 메시지 처리 (1)
데이터 수신과 메시지 처리의 방법적 문제에서도 성능의 차이가 있을 수 있다 .
Overlapped I/O 로 데이터 수신 후 핵심 처리 과정은 대략 아래와 같은 3 가지 단계이다 .
• 받은 데이터를 메시지별로 분리하는 작업이 필요하다 .• 일반적으로 메시지 헤드 (Head) 에서 메시지 크기를 확인하고 메
시지 별로 분리작업을 한다 .
• 분리된 각 메시지가 어떤 메시지인지를 확인하여 메시지에 맞는 처리를 하는 과정이다 .
• 가장 부하가 많이 걸릴 수 있는 부분이다 .• 일반적으로 switch-case 문을 사용하여 처리한다 .
• 데이터를 받기 위해 데이터를 받을 버퍼를 준비하고 정리한 후 다시 Recv() 를 걸어주는 과정이다 .
수신 받은 데이터를 메시지 별로 분리해 처리하는 방법은 대략 아래 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
데이터를 받은 후 메시지 분리 , 메시지 처리를 모두 수행하고 Recv() 를 거는 방식이다 .
CGCIICho sanghyun’s Game Classes II
소켓성능 메시지 처리 (2) – 방법 1
Recv 다시 걸기Recv 다시 걸기
메시지 수신메시지 수신
종료종료
메시지 처리메시지 처리
메시지 분리메시지 분리
처리 구조는 매우 간단한 편이다 . 데이터 수신이 많지 않을 경우 효율적이다
.
메시지분리와 처리를 쓰레드에 분산해서 처리할 수 없다 .
단일 소켓으로 많은 데이터를 수신할 경우 다중 코어에 분산처리할 수 없어 비효율적 .
따라서 모든 데이터를 처리한 후 Recv() 를 걸어야 하기 때문에 데이터를 빨리 비울 수 없어 그만큼 수신 성능이 떨어진다 .
장점 단점
I/O Thread
데이터를 받은 후 메시지 분리하여 분리된 메시지 정보를 큐잉한 후 Recv() 를 거는 방식이다 . 링버퍼를 사용할 경우 이런 방법을 많이 사용한다 .
CGCIICho sanghyun’s Game Classes II
소켓성능 메시지 처리 (2) – 방법 2
Recv 다시 걸기Recv 다시 걸기
메시지 수신메시지 수신
종료종료
메시지 큐잉
메시지 분리메시지 분리
메시지 처리메시지 처리
메시지 수신메시지 수신
종료종료
메시지 디큐잉
I/O Thread Work Thread
처리 과정이 좀 복잡하다 I/O 처리와 메시지처리를 분리해서 처리할
수 있다 . 빠르게 Recv() 를 다시 걸어 데이터 수신
성능을 향상시킬 수 있다 .
메시지 수신량이 적을 때는 오히려 추가적인 부담만 늘어날 수 있다 .
추가적인 메모리 부담이 있을 수 있다 .
장점 단점
데이터를 받은 후 즉시 큐잉만 하고 Recv() 를 거는 방식이다 .
CGCIICho sanghyun’s Game Classes II
소켓성능 메시지 처리 (2) – 방법 3
Recv 다시 걸기Recv 다시 걸기
메시지 수신메시지 수신
종료종료
데이터 큐잉
메시지 처리메시지 처리
메시지 수신메시지 수신
종료종료
데이터 디큐잉
I/O Thread Work Thread
처리 과정이 좀 복잡하다 I/O 처리와 메시지처리를 분리해서 처리할
수 있다 . 가장 빠르게 Recv() 를 다시 걸어 가장 뛰
어난 데이터 수신 성능을 제공한다 .
메시지 수신량이 적을 때는 오히려 추가적인 부담만 늘어날 수 있다 .
장점 단점
메시지 분리메시지 분리
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 된 데이터를 전송하는 방법( 반응속도 느림 , 전송용 쓰레드 추가적으로 필요 )
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()보다 더 떨어진다 .)
장점 단점
전송 요청전송 요청
자체 송신 버퍼에 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
전송 요청전송 요청
자체 송신 버퍼에 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
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 전송을 수
행하면 잦은 메모리 복사로 인해 성능이 급격히 떨어질 수 있다 .
장점 단점
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)
포인터만 큐잉포인터만 큐잉
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)
CGCIICho sanghyun’s Game Classes II
소켓성능
Message Buffer
Head Ptr Tail Ptr
Send Buffer
자 그렇다면 에코 송수신을 처리할 때 기존의 링 - 버퍼를 사용하고 그냥 모아 전송을 사용하면 어떻게 될까 ?
복사
복사
복사 복
사 복사
Receive
Send
메시지 생성기
복사
메아리 (Echo) 송수신 (1)
CGCIICho sanghyun’s Game Classes II
소켓성능
Message Buffer
Head Ptr Tail Ptr
Send Buffer
CGCII 의 송수신 방법을 사용하여 Echo 전송을 수행한다고 하면…
Receive
Send
메아리 (Echo) 송수신 (2)
아무런 복사를 수행하지 않고 단지 참조계수만으로 처리됨 !
112233445566
CGCIICho sanghyun’s Game Classes II
소켓성능 Performance Time
Socket 의 Echo 송수신 테스트를 수행한다 .
CGCII 의 Socket 의 특성에 따라 다른 튜닝을 할 수 있도록 해준다 .
CGCIICho sanghyun’s Game Classes II
소켓성능 CGCII Socket Tunning
일반 Overlapped 전송
Gathering 전송을 지원하는 전송
받는 즉시 메시지 처리받은 데이터 큐잉 후 다른 쓰레드가 처리
(I/O 와 Work 의 분리 )
TCP Socket Send 버퍼 크기
Gather 큐 최대 깊이
TCP Socket Receive 버퍼 크기
수신 버퍼 크기 ( 최소 / 최대 )
수신 큐 최대 깊이
메신저나 채팅을 위한 소켓
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 의 특성에 따라 최적의 성능을 내기 위해서 다양한 튜닝이 있을 수 있다 .
CGCIICho sanghyun’s Game Classes II
소켓성능
매번 전송 때마다 메시지를 작성하는 것이 아니라 한번 작성해 놓고 지속적으로 사용하는 메시지를 의미한다 .
메시지의 많은 부분이 정적 메시지일 수 있다 .
정적 메시지 버퍼 (1)
한번 작성하면 끝날 때까지 메시지를 계속 쓰는 것이 아니라 일정 시간마다 갱신해야 하는 경우 한번 작성한 메시지를 여러 번 사용할 수도 있다 .
어떠한 메시지는 대부분의 값은 동일하고 일부 값만 살짝 틀린 메시지들이 있을 수 있다 . 그럴 경우 나머지 설정된 값들은 그대로 사용하고 수정된 부분만 바꾸어 전송하는 것을 의미한다 .
이것을 위한 ‘메시지 버퍼 풀 (CGPool::CBuffer<I>)’을 제공한다 .
메시지 버퍼 풀
정적 메시지 버퍼 (2)
정적 메시지 버퍼 (1)
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의 사용
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);
…
CGCIICho sanghyun’s Game Classes II
소켓성능 최적 자료 구조 사용
빈번한 삽입이나 삭제되는 큐 같은 것의 처리를 위해 CGD::circular_list<T> 등을 사용한다 . ( 대표적으로 각종 Manager들… )
std::list
CGD::circular_list
Head Tail
Head
Tail
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 을 돌아야 한다 !
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 을 위한 부하도 무시할 수 없다 .)
CGCIICho sanghyun’s Game Classes II
질문 ?
중간 질문 ?
소켓성능