LOGIN • JOININ

OBJECT POOL

[Tutorials/[Common] 1. Object System/Tutorial_Object02_ObjectPool]



빈번히 할당과 해제를 해야하는 객체의 경우 성능 향상을 위해 풀을 사용할 필요가 있습니다.

객체풀은 단순한 메모리풀보다 더욱 극적으로 성능을 향상시켜줍니다. 
이유는 객체 풀의 경우 객체의 생성자 호출과 소멸자 호출의 비용도 줄여주기 때문입니다.


1... NCGPoolable<T> 상속받기

풀을 사용한 할당과 해제를 하기 위해서는 NCGPoolable<T>만 상속 받으면 끝입니다.
그리고 NEW<T>를 사용해 동적 할당만 해주면 자동적으로 자동으로 풀에서 할당을 해줍니다.

또 참조가 끝나면 자동적으로 가비지 콜렉션을 통해 생성했던풀로 반환됩니다.


class foo : public NCGPoolable<foo>
{
public:
			foo()		{	printf("객체생성!\n");}
	virtual	~foo()		{	printf("객체파괴!\n");}
};

 foo객체에 ‘virtual public ICGReferenceable’ 대신 을 ‘public NCGPoolable<T>’만 상속받았습니다.
‘public NCGPoolable<T>’ 클래스의 T는 반드시 자기 자신의 클래스를 써야만 합니다. 즉 여기서는 'foo'가 될 것입니다.

(NCGPoolable<T>은 ICGReferenceable를 이미 상속받았기 때문에 다시 상속할 필요는 없습니다.)

이렇게 정의한 다음 간단히 NEW<T>를 사용해 할당만 해주면 됩니다.
NEW<T>는 T클래스가 NCGPoolable<T>를 상속받았으면 자동적으로 풀에서 할당해줍니다.


CGPTR<foo>	pObject	 = NEW<foo>();


즉, 풀로 할당을 하든 하지 않든 신경쓸 필요없이 CGPTR<T>와 NEW<T>를 사용해 동적 할당을 하면 됩니다.




[Tutorial_Object_ObjectPool.cpp]
#include "stdafx.h"

void TestPool1()
{
	// ------------------------------------------------------------------
	// 1. Pool을 통한 할당과 해제(ICGPoolable사용하기.)
	//
	// 1) Pool을 선언후 Pool에서 Alloc()함수를 통해 새로운 foo를 
	//    할당받을 수 있다.
	// 2) new문장으로 할당받는 것을 대신하여 Alloc() 함수로 할당을 받는다.
	// 3) 이때 foo은 반드시 ICGPoolable<t>을 상속받아야 한다.
	// ------------------------------------------------------------------

	// Definition) foo
	class foo : public ICGPoolable<foo>
	{
	public:
				foo()				{	printf("객체생성!\n");}
		virtual	~foo()				{	printf("객체파괴!\n");}

				void	OnAlloc()	{	printf("객체할당!\n");}
				void	OnFree()	{	printf("객체해제!\n");}

		virtual	void	ProcessDispose() {}

	private:
				int	m_Dummy[256];	// 아무거나
	};


	// Declare) foo을 할당하는 Pool을 선언한다.
	CGOBJ<cgpool::cobject <foo=>>	tempPool;

	{
		// 1) Pool에서 할당 받는다.(최초할 당에는 내부적으로 new를 통해 할당할 것이다.)
		//    - 신규 생성시 객체의 생성자가 호출된다.
		//    - 또 Alloc()함수로 할당을 할때 OnAlloc()함수가 호출될 것이다.
		CGPTR<foo>	pObject1	 = tempPool.Alloc();
		CGPTR<foo>	pObject2	 = tempPool.Alloc();
		CGPTR<foo>	pObject3	 = tempPool.Alloc();

		// 2) 원하는 조작을 한다.
		//    ..............

		// 3) Free함수로 할당된 객체를 할당해제한다.
		//    - 신규 생성시 객체의 소멸자는 호출되지 않는다.
		//    - 또 Free()함수로 할당해제할 경우 OnFree()함수가 호출될 것이다.
		tempPool.Free(pObject1);
		tempPool.Free(pObject2);
		tempPool.Free(pObject3);
	}

	{
		// 4) 다시 새로운 객체를 할당을 요구한다.
		//    - 여기서 할당할때는 new를 통해 생성하지 않고 이전 Free()함수로
		//      할당해제했던 객체를 되돌려 줄 것이다.
		//    - 따라서 객체의 생성자는 호출되지 않는다.
		//    - Alloc()함수로 할당을 할때 OnAlloc()함수는 호출될 것이다.
		CGPTR<foo>	pObject1	 = tempPool.Alloc();
		CGPTR<foo>	pObject2	 = tempPool.Alloc();
		CGPTR<foo>	pObject3	 = tempPool.Alloc();

		// 5) 역시 원하는 조작을 한다.
		// .........

		// 6) Free함수로 할당된 객체를 할당해제한다.
		//    - 신규 생성시 객체의 소멸자는 호출되지 않는다.
		//    - 또 Free()함수로 할당해제할 경우 OnFree()함수가 호출될 것이다.
		tempPool.Free(pObject1);
		tempPool.Free(pObject2);
		tempPool.Free(pObject3);
	}

	// 7) Pool을 소멸하기 전에 반드시 Destroy함수를 호출해 주어야 한다.
	tempPool.Destroy();
}

void TestPool2()
{
	// ------------------------------------------------------------------
	// 1. NCGPoolable사용하기.
	//
	// 1) 객체당 하나의 Pool을 가지는 것은 너무 당연한 것이기 때문에 편리를 
	//    위해 내부적으로 static 멤버변수로 Pool을 가지는 것이 NCGPoolable이다.
	// 2) 이것은 상속받기만 하면 자동적으로 Pool을 생성하므로 따로 Pool을
	//    선언할 필요 없다.
	// 3) 내부적으로 존재하는 Pool은 static이므로 아래와 같이 Pool을 직접
	//    얻을 수도 있다.
	//
	//    auto	pPool	 = foo::GetObjectPool();
	// 
	// 4) 할당과 해제는 역시 아래의 static 함수를 통해 수행할 수 있다.
	//
	//     foo::Alloc();
	//     foo::Free();
	// 
	// ------------------------------------------------------------------

	// Definition) 
	class foo : public NCGPoolable<foo>
	{
	public:
				foo()		{	printf("객체생성!\n");}
		virtual	~foo()		{	printf("객체파괴!\n");}

				void	OnAlloc()	{	printf("객체할당!\n");}
				void	OnFree()	{	printf("객체해제!\n");}
	};


	{
		// 1) 할당 받는다. 이때 foo::Alloc()함수를 사용하여 할당받는다.
		CGPTR<foo>	pObject	 = foo::Alloc();

		// 2) 원하는 조작을 한다.
		//    ..............

		// 3) 특별히 조작하지 않아도 자동적으로 참조계수가 0이 되면 할당해제되어
		//    Pool로 할당받은 객체가 되돌아간다.
	}


	// ------------------------------------------------------------------
	// 2. NEW<t>() 사용하기.
	//
	// 1) Alloc대신 NEW<>를 사용하면 편리하다.
	// 2) 생성하려는 객체가 NPoolable을 상속받았다면 자동적으로 Pool에서
	//    할당하고 그렇지 않을 경우 new를 통해 생성한다.
	// 
	// 	   CGPTR<foo> pObject	 = NEW<foo>();
	//
	// ------------------------------------------------------------------
	{
		// 1) foo::Alloc()대신 NEW를 사용할수 있다.
		//    이때 foo가 NCGPoolable을 상속받았다면 자동적으로 
		//    foo::Alloc()함수를 호출하여 Pool에서 객체를 항당받는다.
		CGPTR<foo>	pObject	 = NEW<foo>();

		// 2) 원하는 조작을 한다.
		//    ..............

		// 3) 특별히 조작하지 않아도 자동적으로 참조계수가 0이 되면 할당해제되어
		//    Pool로 할당받은 객체가 되돌아간다.
	}
}

void TestPool3()
{
	// ------------------------------------------------------------------
	// Pool Tutorial 3) 부가적인 기능들...
	// 
	// 1) CGPool::CObject는 객체를 할당하고 해제하는 기본적인 Pool의 기능 외에
	//    부수적인 기능들이 존재한다.
	// 2) 예를 들어 객체들을 미리 생성해 놓는 Prepare()함수나 저장된 여분 객체를
	//    소멸시키는 Shrink()와 같은 함수들이다.
	// 3) 기타 각종 Pool의 통계에 관련된 정보들도 얻을 수 있다.
	//
	// ------------------------------------------------------------------
	
	// Definition) 
	class foo : public NCGPoolable<foo>
	{
	public:
				foo()		{	printf("객체생성!\n");}
		virtual	~foo()		{	printf("객체파괴!\n");}

	private:
				int	m_Dummy[256];	// 아무거나
	};


	// ------------------------------------------------------------------
	// 1. Prepare하기.
	//
	//    1) 위에 예제에서 Pool을 제일처럼 만들었을 때는 해당 객체가 
	//       하나도 할당되어 있지 않는다.
	//
	//    2) 성능의 향상을 위해 생성 직후 생성과 할당해제를 하지 않더라도 
	//       미리 일정수의 재고 객체를 미리 만들어 쌓아두기 위해 Prepare() 
	//       함수를 사용할 수 있다.
	//
	//    3) Prepare()함수는 넘겨진 갯수만큼 추가로 재고 객체를 확보한다.
	//       만약 숫자보다 재고량이 많다면 특별히 더 할당하지 않는다.
	// ------------------------------------------------------------------
	foo::GetObjectPool()->Prepare(100);

	// 설명) Prepare(100)을 하게 되면 지금 pool안에는 할당이 안된 객체가
	//       100개가 마련되어 있다. 따라서 100개이상 연속적으로 Alloc
	//       하기전에는 새로 객체가 새로 할당되는 일은 없을 것이다.
	//    
	//       일반적으로 동작 중 사용되는 객체의 수가 일정하다면 사전에
	//       Prepare()함수를 통해 미리 객체를 만들어 놓은 다음 동작을
	//       시킨다면 동작 초기 많은 성능향상을 꽤할 수 있다.


	// ------------------------------------------------------------------
	// 2. Shrink하기.
	//
	//    1) Pool을 사용할 경우 객체를 한꺼번에 할당을 많이 했다가 해제될 
	//       경우 너무 많은 여분의 객체를 보관하고 있을 수 있다. 이는 과도한
	//       메모리를 사용하는 원인이 될수 있다.
	//
	//    2) 이때 인위적으로 보관되어 있는 여분 객체를 delete하는 것이 
	//       Shrink()함수이다.
	//
	//    참고) 따로 Shrink()를 하지 않아도 내부적으로 적절한 여분 량을 계산
	//          하여 필요 이상이 저장될 경우 delete하는 처리과정을 가지고 있다.
	//          따라서 일반적인 경우 Shrink()함수를 호출할 일은 없다.
	// ------------------------------------------------------------------
	foo::GetObjectPool()->Shrink(100);

	// 설명) Shrink(100)을 하게 저장되어 있는 여분의 객체 100개를 할당해제한다.
}


int _tmain(int /*argc*/, _TCHAR* /*argv*/[])
{
	// View) 
	printf("시작합니다.\n");


	// ------------------------------------------------------------------
	// 1. Test
	// ------------------------------------------------------------------
	// 1) Pool Tutorial 1
	TestPool1();

	// 2) Pool Tutorial 2
	TestPool2();

	// 3) Pool Tutorial 3
	TestPool3();


	// ------------------------------------------------------------------
	// 2. 뒷마무리 & 끝내기
	// ------------------------------------------------------------------
	// 1) Esc를 누를 때까지 기다린다.
	while(_getch()!=27);

	// View) 
	printf("종료합니다.\n");


	// Return) 끝.
	return 0;
}