728x90

1. 객체 풀 패턴 정의

  • 객체를 매번 할당, 해제하지 않고 고정 크기 풀에 들어 있는 객체를 재사용해 메모리 사용 성능을 개선하는 패턴.

 

2. 객체 풀 패턴이 필요한 경우

  • 객체를 빈번하게 생성/삭제해야 할 때
  • 객체들의 크기가 비슷할 때
  • 객체를 힙에 생성하기가 느리거나 메모리 단편화가 우려될 때

 

3. 객체 풀 패턴 구현

class Particle
{
private:
	int FramesLeft;
	double X;
	double Y;
	double XVel;
	double YVel;

public:
	Particle() : FramesLeft( 0 ) {}

	void Init( double InX, double InY, double InXVel, double InYVel, int InLeftTime )
	{
		X = InX;
		Y = InY;
		XVel = InXVel;
		YVel = InYVel;
		FramesLeft = InLeftTime;
	}

	void Animate()
	{
		if ( !InUse() ) return;

		--FramesLeft;
		X += XVel;
		Y += YVel;
	}

	bool InUse() const { return FramesLeft > 0; }
};

// 객체 풀 클래스
class ParticlePool
{
private:
	static const int POOL_SIZE = 100;
	Particle Particles[ POOL_SIZE ];

public:
	void Create( double InX, double InY, double InXVel, double InYVel, int InLeftTime )
	{
		// 사용 가능한 파티클을 찾는다.
		for ( int index = 0; index < POOL_SIZE; ++index )
		{
			if ( !Particles[ index ].InUse() )
			{
				Particles[ index ].Init( InX, InY, InXVel, InYVel, InLeftTime );
				return;
			}
		}
	}

	void Animate()
	{
		for ( int index = 0; index < POOL_SIZE; ++index )
			Particles[ index ].Animate();
	}
};
  • 기본 생성자에서 파티클을 '사용 안 함'으로 초기화한다, Init() 함수가 호출되면 파티클이 사용 중 상태로 변경된다.
  • 사용 가능한 파티클을 찾을 때까지 풀을 순회한 뒤, 찾으면 초기화를 진행한다.

 

4. 빈칸 리스트를 사용한 객체 풀 구현

class Particle
{
private:
	int FramesLeft;

	union
	{
		// 사용 중일 때의 상태
		struct
		{
			double X, Y;
			double XVel, YVel;
		} Live;

		// 사용 중이 아닐 때의 상태
		Particle* Next;
	} State;

public:
	Particle() : FramesLeft( 0 ) {}

	void Init( double InX, double InY, double InXVel, double InYVel, int InLeftTime )
	{
		State.Live.X = InX;
		State.Live.Y = InY;
		State.Live.XVel = InXVel;
		State.Live.YVel = InYVel;
		FramesLeft = InLeftTime;
	}

	bool Animate()
	{
		if ( !InUse() ) return false;

		--FramesLeft;
		State.Live.X += State.Live.XVel;
		State.Live.Y += State.Live.YVel;

		return FramesLeft == 0;
	}

	bool InUse() const { return FramesLeft > 0; }

	Particle* GetNext() const { return State.Next; }

	void SetNext( Particle* InNext ) { State.Next = InNext; }
};

// 객체 풀 클래스
class ParticlePool
{
private:
	static const int POOL_SIZE = 100;
	Particle Particles[ POOL_SIZE ];
	Particle* FirstAvailable;

public:
	ParticlePool()
	{
		// 처음 파티클부터 사용 가능하다.
		FirstAvailable = &Particles[ 0 ];

		// 모든 파티클은 다음 파티클을 가리킨다.
		for ( int index = 0; index < POOL_SIZE - 1; ++index )
			Particles[ index ].SetNext( &Particles[ index + 1 ] );

		// 마지막 파티클에서 리스트를 종료한다.
		Particles[ POOL_SIZE - 1 ].SetNext( nullptr );	
	}

	void Create( double InX, double InY, double InXVel, double InYVel, int InLeftTime )
	{
		// 풀이 비어있지 않은지를 확인한다.
		if ( FirstAvailable == nullptr )
			return;

		// 얻은 파티클을 빈칸 목록에서 제거한다.
		Particle* newParticle = FirstAvailable;
		FirstAvailable = newParticle->GetNext();
		newParticle->Init( InX, InY, InXVel, InYVel, InLeftTime );
	}

	void Animate()
	{
		for ( int index = 0; index < POOL_SIZE; ++index )
		{
			if ( Particles[ index ].Animate() )
			{
				// 방금 죽은 파티클을 빈칸 앞에 추가한다.
				Particles[ index ].SetNext( FirstAvailable );
				FirstAvailable = &Particles[ index ];
			}
		}
	}
};
  • FramesLeft를 제외한 모든 멤버 변수를 State 공용체의 Live 구조체 안으로 옮겼다.
  • 파티클이 살아 있는 동안에는 Live 구조체를, 사용 중이 아닌 경우에는 공용체의 다른 변수인 Next가 사용된다.
  • Next는 이 파티클 다음에 사용 가능한 파티클 객체를 포인터로 가리킨다.
  • Next 포인터를 시용하면 풀에서 사용 가능한 파티클이 묶여 있는 연결 리스트를 만들 수 있다.
  • 추가 메모리 없이 죽어 있는 객체의 메모리를 재활용해, 자기 자신을 사용 가능한 파티클 리스트에 등록할 수 있다.

 

5. 객체가 풀과 커플링 된 경우

class Particle
{
	friend class ParticlePool;

private:
	Particle() : InUse ( false ) {}
	bool InUse;
};

class ParticlePool
{
	Particle Pool[ 100 ];
	//...
};
  • 풀에 들어가는 객체에 '사용 중' 플래그나 이런 역할을 하는 함수를 추가하여 사용한다.
  • 풀 클래스를 객체 클래스의 friend로 만든 뒤 객체 생성자를 private에 두어 만든다.

 

6. 객체가 풀과 커플링 되지 않은 경우

template < class TObject >
class GenericPool
{
private:
	static const int POOL_SIZE = 100;

	TObject Pool[ POOL_SIZE ];
	bool InUse[ POOL_SIZE ];
};
  • 객체와 풀을 디커플링함으로써, 어떤 객체라도 풀에 넣을 수 있는 재사용 가능한 풀 클래스 구현이 가능하다.

 

7. 객체를 풀 안에서 초기화 하는 방법

class Particle
{
	friend class ParticlePool;

private:
	// 다양한 초기화 방법
	void Init( double InX, double InY );
	void Init( double InX, double InY, double InAngle );
	void Init( double InX, double InY, double InXVel, double InYVel );
};

class ParticlePool
{
public:
	void Create( double InX, double InY )
	{
		// Particle 클래스로 포워딩...
		// Init() 호출
	}

	void Create( double InX, double InY, double InAngle )
	{
		// Particle 클래스로 포워딩...
		// Init() 호출
	}

	void Create( double InX, double InY, double InXVel, double InYVel )
	{
		// Particle 클래스로 포워딩...
		// Init() 호출
	}
};
  • 풀은 객체를 완전히 캡슐화할 수 있는 장점이 있다.
  • 풀 클래스는 객체가 초기화되는 방법과 결합하게 된다.

 

8. 객체를 풀 밖에서 초기화 하는 방법

class Particle
{
public:
	// 다양한 초기화 방법
	void Init( double InX, double InY );
	void Init( double InX, double InY, double InAngle );
	void Init( double InX, double InY, double InXVel, double InYVel );
};

class ParticlePool
{
private:
	static const int POOL_SIZE = 100;
	Particle Pool[ POOL_SIZE ];
public:
	Particle* Create()
	{
		// 사용 가능한 파티클에 대한 레퍼런스를 반환한다.
	}
};

int main()
{
	// 외부 코드에서 객체 생성이 실패할 때의 처리를 위한 nullptr 검사 필요
	ParticlePool pool;
	Particle* particle1 = pool.Create();
	if ( particle1 ) particle1->Init( 1, 2 );

	Particle* particle2 = pool.Create();
	if ( particle2 ) particle2->Init( 1, 2, 3 );

	Particle* particle3 = pool.Create();
	if ( particle3 ) particle3->Init( 1, 2, 3, 4 );
}
  • 풀은 객체 초기화 함수를 전부 제공할 필요가 없어진다.
  • 풀이 비어 있다면 NULL을 반환할 수 도 있다. 따라서 객체를 초기화하기 이전에 예외 검사를 진행해야 한다.
반응형

+ Recent posts