자동 풀링(auto pooling)을 구현해보자.

CSpvLoader를 구현하다가 간만에 자동풀링을 사용하게 되었습니다. 그런 김에 간단히 자동 풀링의 개념과 사용법을 다뤄볼까 합니다.

이제부터 Bitmap객체에 대한 자동 풀링을 차근차근 구현해보겠습니다.

일반적으로 풀링은 미리 메모리를 확보한 후 new를 통해 인스턴스를 생성한 후 재활용하는 기술입니다. 따라서 컨테이너에 일정 수만큼 인스턴스를 생성하는 것으로부터 시작합니다.

이는 코드로 표현하면 다음과 같이 표현할 수 있습니다.

class BitmapAutoPool{

	private var _pool:Vector.<Bitmap>;

	public function BitmapAutoPool(){
		//적당히 수로 초기화한다.
		_pool = new Vector.<Bitmap>();
	}
}

가장 핵심적인 로직은 getBitmap이겠죠? 이 인스턴스를 얻어갈 때 _pool을 뒤지게 될텐데,

  1. 이 때 아직 생성된 인스턴스가 없다면 생성해서 가져갈테고,
  2. 만약 이미 생성된 인스턴스에서 가져야한다면 그 인스턴스가 사용 중인지 아닌지를 판단해야합니다.

null인지 검사해서 new하는거야 새로울것도 없는 내용이니 그렇다치고 어떻게 Bitmap이 사용 중인지 아닌지 알 수 있을까요? 바로 이 부분을 자동으로 처리하는 풀링을 자동풀링이라 합니다. 자동풀링이란 개념일 뿐이고 실질적으로는 자연스러운 객체의 사용이 풀링에게 자원을 반납하게 되는 구조만 성립하면 자동풀링인 셈이죠. 이와 반대되는 수동풀링 시스템은 인스턴스를 얻어간 뒤엔 반드시 풀링시스템에게 반납하는 코드를 추가합니다.

pool.remove(bit);

이런식이죠. 저러한 코드가 필요없고 자동으로 자원이 회수되는 장치가 있다면 그것은 자동풀링인 셈입니다. 이러한 개념하에 Bitmap 객체가 사용 중인지 아닌지 판별할 방법이 필요합니다. 방법을 알아내기 위해 우리는 과연 Bitmap객체를 어떻게 쓰게 될지 예상되는 시나리오를 생각해볼 필요가 있습니다.

  1. 풀링에게 인스턴스를 받아온다.
  2. 인스턴스의 속성을 다양하게 지정한다.
  3. DisplayContainer에 addChild 한다.
  4. 사용이 끝나면 removeChild 한다.

가장 적합한 판단기준은 역시 DisplayObject 속성의 parent일 듯 싶습니다(다른 아이디어도 있으면 생각해보세요) parent속성이 null이면 사용가능한 Bitmap이라고 판단한다면 다음과 같은 형태로 getBitmap을 작성해볼 수 있습니다.

public function getBitmap():Bitmap{
	var key:*, result:Bitmap;

	//먼저 기존의 pool에서 찾아본다.
	for( key in _pool){

		//만약 해당 객체가 null이라면 new를 통해 생성한다.
		if( _pool[key] === null ){
			_pool[key] = new Bitmap;
			result = _pool[key];
			break;

		//null이 아니라면 parent를 조사하여 사용가능한지 판단한다.
		}else if( _pool[key].parent === null ){
			result = _pool[key];
			break;
		}
	}

	//기존의 pool안에서 쓸만한 걸 찾지 못했다면
	if( result === null ){
		//새로운 인스턴스를 만들고 풀에 추가한다.
		result = new Bitmap;
		_pool[_pool.length] = result;
	}

	//인스턴스를 반환한다.
	return result;
}

주석의 흐름을 그대로 따라서 읽으시면 되기 때문에 달리 설명드릴게 없습니다. 이제 BitmapAutoPool 의 호스트 코드를 살펴보죠.

var pool:BitmapAutoPool = new BitmapAutoPool;

var bitmap:Bitmap, i:uint, a:uint;

//풀링을 사용한 경우
a = getTimer();
for( i = 0 ; i < 100000 ; ++i ){
	this.addChild( pool.getBitmap() );
	this.removeChild( this.getChildAt(0) );
}
trace( getTimer() - a );

//풀링을 사용하지 않는 경우
a = getTimer();
for( i = 0 ; i < 100000 ; ++i ){
	this.addChild( new Bitmap );
	this.removeChild( this.getChildAt(0) );
}
trace( getTimer() - a );

위 아래의 속도 차이는 264 대 790 정도로 나옵니다. 세 배 정도의 속도 향상을 기대할 수 있군요. 하지만 속도 향상보다 더욱 중요한 점은 필요한 만큼 객체를 생성하기를 지킨다는 것이겠죠. 수많은 new를 하고 가비지컬렉팅을 욕하지 마시고 언제나 효율적으로 new를 하는 방법을 연구해봐야합니다.

아래 간단히 원더플에 코딩해뒀습니다. 박스를 클릭하세요(보이는건 수치뿐이겠지만 ^^)

Browser does not supports flash movie


관련된 글:

  1. Memory Pooling in as3
  2. 가비지컬렉터 와 메모리 관리
  3. DisplayObject에서 Selector 사용하기
  4. 정수 랜덤을 구해보자.
  5. new를 제거하고 GET을 사용하기

8 Comments

  1. 니케 says:

    와웅~ 역시 깔끔한 설명.
    이해가 쏙쏙 되네요~
    잘 봤습니다 형님~!

  2. 동강 says:

    CSpvLoader 가 이런 방식으로 구현이 되었던 거군요.ㅎ 저도 pooling 적용 방식으로 바꿔봐야 겠어요.

    • admin says:

      세가지 단계로 자주 쓰는 클래스를 리펙토링하면 그 클래스가 자주쓰이면 쓰일수록 퍼포먼스가 크게 향상되지.

      1. new를 할 수 없게 생성자에게 internal 클래스의 인스턴스를 인자로 설정해버린다.
      2. static function factory 를 도입하여 반드시 이 factory메서드를 통해서만 인스턴스를 생성하게 강제한다.
      3. factory메서드가 static pool에서 인스턴스를 반환한다.

  3. 지돌스타 says:

    설겆이군요. 잘 닦아서 다시 쓰는~~ ^^

  4. 뿌꾸 says:

    ㅋㅋ 오늘도 형 블러그에서 좋은글 또 보구 갑니다

Leave a Reply