스토리텔링 개발자

[Effective STL] 11. 커스텀 할당자(Custom Allocator) 본문

개발/Effective STL

[Effective STL] 11. 커스텀 할당자(Custom Allocator)

김디트 2024. 11. 18. 11:03
728x90

항목 11. 커스텀 할당자를 제대로 사용하는 방법을 이해하자

 

 

 

커스텀 할당자를 쓰고 싶어지는 경우
  • 벤치마킹, 프로파일링, 각종 실험을 통해 디폴트 STL 메모리 관리자(allocator<T>)가 별로라는 결론에 이르렀을 때
    • 낮은 메모리 효율, 심각한 단편화(fragmentation) 등
  • allocator<T>가 쓰레드 안전성을 염두에 둔 것이 마음에 들지 않을 때
    • 단일 쓰레드 환경에서만 사용할텐데, 동기화 때문에 걸리는 필요 없는 오버헤드를 피하고 싶다.
  • 컨테이너에 들어 있는 객체들이 하나의 힙 안에서 관리되는 점 때문에 문제를 느낄 때
    • 같은 종류의 객체를 특정한 힙에 모여있게 하여 메모리 참조 위치의 근접성(locality of reference)를 극대화하는 효과를 얻기 부족하다.
  • 공유 메모리에 해당하는 특수한 힙을 만들어, 하나 이상의 컨테이너를 그 메모리에 두어 여러 프로세스들이 공유할 수 있게 하고 싶을 때

 

 

 

커스텀 할당자 구현 예시
  • 공유 메모리를 할당하는 커스텀 함수들을 사용하여 커스텀 할당자를 구현하는 방법
// malloc, free를 본뜬 함수
void* mallocShared(size_t bytesNeeded);
void freeShared(void* ptr);

// 커스텀 할당자 구현
template<typename T>
class SharedMemoryAllocator
{
public:
    ...
    // 항목 10 참조
    pointer allocate(size_type numObjects, const void* localityHint = 0)
    {
        return static_cast<pointer>(mallocShared(numObjects * sizeof(T)));
    }
    
    void deallocate(pointer ptrToMemory, size_type numObjects)
    {
        freeShared(ptrToMemory);
    }
    ...
};

typedef vector< double, SharedMemoryAllocator<double> > SharedDoubleVec;
...

{
    SharedDoubleVec v; // 요소들이 공유 메모리에 있게 되는 벡터
    // 허나, 벡터 v 및 내부의 데이터 멤버는 공유 메모리에 있지 않다.(스택에 있다.)
    ...
} // v 해제

 

  • 위 예제에서, 벡터의 요소는 공유 메모리에 있지만, 벡터 자체는 스택에 있다.
  • 벡터 자체를 공유 메모리에 두는 방법
// SharedDoubleVec 객체를 담을 충분한 공유 메모리 할당(allocate)
// mallocShared가 nullptr을 리턴하는 경우는 고려하지 않았다.
void* pVectorMemory = mallocShared(sizeof(SharedDoubleVec));

// '위치지정 new(placement new)'를 써서 SharedDoubleVec 객체를 메모리에 생성
SharedDoubleVec* pv = new (pVectorMemory) SharedDoubleVec;

... // 벡터 객체를 사용한다.

pv->~SharedDoubleVec(); // 공유 메모리의 이 객체를 소멸시킨다.

freeShared(pVectorMemory); // 공유 메모리의 메모리 단위를 해제(deallocator)한다.
  • 두 개의 힙 클래스에서 메모리를 관리하는 방법
class Heap1
{
public:
    ...
    static void* alloc(size_t numBytes, const void* memoryBlockToBeNear);
    static void dealloc(void* ptr);
    ...
};

class Heap2
{
public:
    ...
    static void* alloc(size_t numBytes, const void* memoryBlockToBeNear);
    static void dealloc(void* ptr);
    ...
};

// STL 컨테이너 몇 개를 종류에 맞춰 제각기 다른 힙에 모아두고 싶다.
// Heap1, Heap2의 인터페이스를 사용하는 커스텀 할당자 템플릿 클래스를 만든다.
template<typename T, typename Heap>
SpecificHeapAllocator
{
public:
    ...
    pointer allocate(size_t numObjects, const void* localityHint = 0)
    {
        return static_cast<pointer>(Heap::alloc(numObjects * sizeof(T), localityHint));
    }
    
    void deallocate(pointer ptrToMemory, size_type numObjects)
    {
        Heap::dealloc(ptrToMemory);
    }
    ...
};

// 벡터와 셋은 Heap1에 모은다.
vector< int, SpecificHeapAllocator<int, Heap1> > v;
set< int, SpecificHeapAllocator<int, Heap1> > s;

// 리스트와 맵은 Heap2에 모은다.
list< Widget, SpecificHeapAllocator<Widget, Heap2> > l;
map< int, string, less<int>, SpecificHeapAllocator< pair<const int, string, Heap2> > > m;
  • 위에서 SpecificHeapAllocator는 객체가 아니라 타입으로 사용된다.
    • STL에서는 같은 타입의 다른 할당자 '객체'를 사용하여 다른 STL 컨테이너를 초기화할 수 있지만, 하지 말자.
    • Heap1과 Heap2가 타입이 아니라 객체(object)라면 이 둘은 동등한 할당자가 아니고,
    • 그러므로 할당자의 등등성(equivalence) 제약을 어기게 되기 대문이다.(항목 10 참조)
  • 할당자의 동등성 제약만 잘 지키면 커스텀 할당자 사용에는 문제가 없을 것이다.
728x90
Comments