January 6, 2009, Tuesday, 5

Article:MemPoolShare

From IdeA thinKING

Jump to: navigation, search

Contents

MemPoolShare - 여러 개의 클래스들을 위한 MemPool

사례 연구

이번에는 먼저 구현할 MemPoolShare가 어떤 경우에 필요할지를 알아보겠습니다.

먼저 만들어야 하는 embedded 시스템은 3개의 프로토콜을 지원합니다. 각 프로토콜은 하나의 연결당 하나의 Session object를 필요로 합니다. 이 시스템의 요구 사항은 어떤 프로토콜의 조합이든 최대 9,000개의 동시 연결을 지원하는 것입니다. 이 세개의 프로토콜을 Alpha, Bravo, Chari 라고 부르겠습니다.

최대 9,000개의 session은 다음과 같이 어떤 조합이든 가능합니다.

  • Alpha (3,000), Bravo (3,000), Chari (3,000)
  • Alpha (9,000), Bravo (0), Chari (0)
  • Alpha (0), Bravo (3,000), Chari (6,000)
  • ...

그리고 다음과 같은 제한 사항이 존재합니다.

  1. 각 Session을 할당, 해제할때의 성능은 deterministic해야 합니다.
  2. 메모리는 세개의 프로토콜 중 가장 큰 것 9,000개 정도를 할당할 수 있는 정도밖에 없습니다.

먼저 첫번째 제한 사항은 MemPool을 사용하여 미리 9,000개만큼의 메모리를 할당받아 놓는 방법으로 가능합니다. (이전의 MemPoolClass의 reserve() 와 같은 함수를 사용하면 됩니다.)

하지만 두번째 제한 사항은 MemPoolClass를 사용하여 해결할 수 있는 방법이 없습니다. 왜냐하면 9,000개의 object는 어떤 조합이 될지 미리 알 수 없기 때문에 각 프로토콜을 모두 9,000개씩 잡아놓아야 하기 때문입니다. 물론 이렇게 되면 두번째 제한 사항에 벗어나게 됩니다.

상세 구현

먼저 MemPoolShare클래스는 Alpha, Bravo, Chari 클래스에서 모두 base class로 사용하여야 함으로 컴파일 시간에 자동으로 사용할 chunk의 크기를 알기란 어렵습니다. 왜냐하면 MemPoolShare 클래스는 max( sizeof(Alpha), sizeof(Bravo), sizeof(Chari) )를 알아야 하는데 MemPoolShare 클래스 자체가 이 세 클래스의 base class가 되어야 하기 때문입니다.

따라서 사용할 chunk의 크기를 사용자가 할당하도록 하고 발생할 수 있는 에러를 찾아주는 방법을 사용하겠습니다.

먼저 MemPoolClass의 선언은 다음과 같습니다.

template <
  int N,
  int OBJS_IN_A_BLOCK = 50,
  class LockModel = NoLock
>
class MemPoolShare : public LockModel

여기서 LockModel 클래스는 MemPoolShare에서 사용하는 클래스들과 같습니다.

다음으로 template parameter N 에 대해 생각해봅시다. 이전의 MemPoolClass는 sizeof(T)로 필요한 크기를 구하므로 컴파일러가 적당히 필요한 padding을 하여 멤버들의 byte alignment를 맞춰줄거라고 생각할 수 있었습니다. 하지만 MemPoolShare의 N 은 사용자가 임의로 할당하는 값이기 때문에 이를 적당히 보정하는 작업이 필요합니다.

여기서는 pointer의 바이트수를 사용하여 보정을 하겠습니다. 예를 들어 sizeof(pointer) 의 값이 4바이트라면 N 이 99일때는 100으로, 101일때는 104등으로 보정합니다. 물론 100일땐 100이 그대로 사용됩니다. 사용한 코드는 아래와 같습니다.

public:
  enum {
    MOD = N % sizeof(unsigned char*),
    PAD = (MOD ? sizeof(unsigned char*) - MOD : 0),
 
    CHUNK_SIZE = N + PAD
  };

사용 예제

이쯤에서 MemPoolClass를 사용하는 방법을 알아 보겠습니다. 먼저 필요한 base class를 typedef와 MemPoolShare를 이용하여 선언합니다. 아래의 예제는 300바이트의 chunk 크기를 사용합니다.

typedef MemPoolShare<300> ProtocolBase;

그리고 MemPool을 공유할 클래스들은 아래와 같이 이 ProtocolBase 타입에서 상속받습니다.

class AlphaProtocol : public ProtocolBase
{
...
};
 
class BravoProtocol : public ProtocolBase
{
...
};
 
class ChariProtocol : public ProtocolBase
{
...
};

만약 이중 하나의 클래스의 크기가 300보다 크다면 어떻게 될까요? 물론 필요한 메모리보다 작은 메모리가 return 되므로 문제가 발생합니다. 따라서 operator new 에 다음과 같이 assertion 체크를 추가합니다.

static void* operator new (size_t size) {
  assert(CHUNK_SIZE >= size);

하지만 이 경우, 사용자가 아래와 같은 코드를 미리 작성해둔다면 runtime assertion 에러 대신 컴파일 에러로도 문제를 발견할 수 있습니다.

BOOST_STATIC_ASSERT(sizeof(AlphaProtocol) <= ProtocolBase::CHUNK_SIZE);
BOOST_STATIC_ASSERT(sizeof(BravoProtocol) <= ProtocolBase::CHUNK_SIZE);
BOOST_STATIC_ASSERT(sizeof(ChariProtocol) <= ProtocolBase::CHUNK_SIZE);

이는 MemPoolShare 코드에서 할 수 있는 것이 아니기 때문에 사용자가 코드를 작성해야 합니다. 물론 이를 작성하지 않는다 해도 DEBUG 모드에서 assertion 에러가 발생할 것이기 때문에 쉽게 문제를 발견할 수 있습니다만 역시 컴파일 에러를 발생시키는 것이 여러모로 좋지요.

마무리

MemPoolShare의 나머지 구현은 MemPoolClass에서 sizeof(T)로 구해서 사용하던 크기를 CHUNK_SIZE를 사용하는 것으로만 변경하면 됩니다. 따라서 나머지 구현 사항은 생략하도록 하겠습니다.

다음 시간에는 MemPool의 마지막 버전으로 적당한 크기의 object들끼리 같은 MemPool을 사용하도록 하여 가장 범용적으로 사용할 수 있는 MemPoolSize 클래스를 만들어 보겠습니다.