본문 바로가기
C++

[C++] 커스텀 STL Allocator로 메모리 최적화하기 🛠️

by 다다면체 2025. 1. 8.
728x90
반응형

안녕하세요, C++의 깊은 매력에 빠져있는 여러분! 오늘은 C++의 강력한 기능 중 하나인 STL Allocator를 집중적으로 파헤쳐 보겠습니다. STL Allocator를 활용하면 애플리케이션에 최적화된 메모리 할당 전략을 구현하여 성능을 극적으로 향상시킬 수 있습니다. 지금부터 커스텀 Allocator를 통해 메모리 관리의 효율성을 끌어올리고, 성능 병목을 시원하게 날려버리는 방법을 함께 알아볼까요?😊


반응형

1. STL Allocator란? 🤔

STL Allocator는 C++ Standard Template Library(STL)에서 컨테이너가 메모리를 어떻게 관리할지를 결정하는 추상화된 도구입니다. 마치 레스토랑의 주방장이 식재료를 어떻게 사용할지 결정하는 것과 같습니다. 기본적으로 STL 컨테이너는 std::allocator라는 기본 할당자를 사용하지만, 우리 입맛(특수한 요구 사항)에 맞춰 커스텀 Allocator를 만들어 사용할 수 있습니다.


2. 커스텀 Allocator 작성하기 🛠️

커스텀 Allocator를 만들기 위해서는 다음 네 가지 핵심 기능을 구현해야 합니다. 마치 네 가지 요리 도구를 갖춰야 맛있는 요리를 만들 수 있는 것과 같습니다.

    • allocate: 필요한 크기의 메모리를 할당합니다. 마치 식재료를 준비하는 과정과 같습니다.
    • deallocate: 더 이상 사용하지 않는 메모리를 해제합니다. 사용한 조리 도구를 깨끗이 씻는 것과 같습니다.
    • construct: 할당된 메모리에 객체를 생성합니다. 준비된 식재료로 요리를 만드는 과정입니다.
    • destroy: 객체를 파괴합니다. 다 먹은 접시를 치우는 것과 같습니다. ️

예제: 간단한 커스텀 Allocator 구현

#include <iostream>
#include <memory>
#include <vector>

template <typename T>
class CustomAllocator {
public:
    using value_type = T;

    CustomAllocator() = default;
    template<typename U> CustomAllocator(const CustomAllocator<U>&) noexcept {}

    // 메모리 할당
    T* allocate(std::size_t n) {
        std::cout << "Allocating " << n << " elements." << std::endl;
        T* ptr = static_cast<T*>(::operator new(n * sizeof(T)));
        if (ptr == nullptr) {
          throw std::bad_alloc();
        }
        return ptr;
    }

    // 메모리 해제
    void deallocate(T* p, std::size_t n) {
        std::cout << "Deallocating " << n << " elements." << std::endl;
        ::operator delete(p);
    }

    // 객체 생성
    template <typename... Args>
    void construct(T* p, Args&&... args) {
        new (p) T(std::forward<Args>(args)...);
    }

    // 객체 파괴
    void destroy(T* p) {
        p->~T();
    }
    template <class U>
    struct rebind { using other = CustomAllocator<U>; };
};

int main() {
    std::vector<int, CustomAllocator<int>> vec;
    vec.push_back(42);
    vec.push_back(7);
    vec.push_back(21);

    for (int n : vec) {
        std::cout << n << " ";
    }
    std::cout << std::endl;

    return 0;
}

핵심 포인트:

    • allocate와 deallocate는 메모리 할당과 해제의 핵심 역할을 합니다.
    • 커스텀 Allocator는 STL 컨테이너의 두 번째 템플릿 인자로 전달됩니다. 마치 레스토랑에서 특별 메뉴를 주문하는 것과 같습니다.
    • 출력 메시지를 통해 메모리 할당 및 해제 과정을 눈으로 확인할 수 있습니다.

3. 커스텀 Allocator의 응용 사례 🚀

특정 메모리 풀 활용

가장 흔하게 사용되는 커스텀 Allocator의 활용 사례는 바로 메모리 풀입니다. 메모리 풀은 미리 큰 덩어리의 메모리를 할당해 놓고, 필요할 때마다 작은 조각으로 나눠서 사용하는 방식입니다. 마치 수영장에서 여러 사람이 동시에 물을 사용하는 것과 같습니다. 이를 통해 동적 할당/해제의 오버헤드를 줄여 성능을 크게 향상시킬 수 있습니다 .

#include <iostream>
#include <memory>
#include <vector>

class MemoryPool {
private:
    static constexpr std::size_t POOL_SIZE = 1024; // 풀 크기
    char pool[POOL_SIZE];                         // 메모리 풀
    char* free_ptr;                             // 사용 가능한 메모리 위치

public:
    MemoryPool() : free_ptr(pool) {}

    void* allocate(std::size_t size) {
        if (free_ptr + size > pool + POOL_SIZE) {
            throw std::bad_alloc(); // 메모리 부족 예외 발생
        }
        void* result = free_ptr;
        free_ptr += size;
        std::cout << "Allocating from Pool" << std::endl; //출력 추가
        return result;
    }

    void deallocate(void* ptr, std::size_t size) {
        // 메모리 풀에서는 실제로 해제하지 않습니다. (풀이 소멸될 때 함께 해제)
        std::cout << "Deallocating to Pool(Not actually freeing)" << std::endl; //출력 추가
    }
};

template <typename T>
class PoolAllocator {
private:
    MemoryPool& pool;

public:
    using value_type = T;
    PoolAllocator(MemoryPool& p) : pool(p) {}
    template<typename U> PoolAllocator(const PoolAllocator<U>& other) noexcept : pool(other.pool){}

    T* allocate(std::size_t n) {
        return static_cast<T*>(pool.allocate(n * sizeof(T)));
    }

    void deallocate(T* p, std::size_t n) {
        pool.deallocate(p, n * sizeof(T));
    }
    template <class U>
    struct rebind { using other = PoolAllocator<U>; };
};

int main() {
    MemoryPool pool;
    std::vector<int, PoolAllocator<int>> vec(PoolAllocator<int>(pool));
    vec.push_back(42);
    vec.push_back(7);

    for (int n : vec) {
        std::cout << n << " ";
    }
    std::cout << std::endl;

    return 0;
}

커스텀 Allocator의 장점

  1. 성능 향상: 동적 할당/해제 오버헤드를 줄여줍니다. 마치 고속도로를 달리는 것처럼 속도가 빨라집니다.
  2. 특화된 메모리 관리: 특정 패턴이나 요구 사항에 딱 맞는 메모리 관리가 가능합니다. 마치 맞춤 정장을 입는 것과 같습니다.
  3. 메모리 누수 방지: 메모리 풀 기반으로 안정적인 메모리 사용을 보장합니다. 마치 튼튼한 댐처럼 메모리를 안전하게 관리합니다. ️

4. 주의할 점 ⚡

STL 컨테이너의 요구 사항 준수: 커스텀 Allocator는 STL 컨테이너의 엄격한 인터페이스를 정확히 구현해야 합니다. 마치 규칙을 잘 지켜야 안전하게 게임을 즐길 수 있는 것과 같습니다.

 

디버깅의 어려움: 잘못 구현된 커스텀 Allocator는 찾기 어려운 메모리 오류를 유발할 수 있습니다. 마치 숨은 그림 찾기처럼 어려울 수 있습니다. ️‍♀️

 

범용성 고려: 너무 특수한 목적에 맞춰진 Allocator는 코드의 범용성을 떨어뜨릴 수 있습니다. 마치 한 가지 용도로만 만들어진 도구처럼 활용도가 낮을 수 있습니다.


5. 마무리 🎉

커스텀 STL Allocator는 메모리 관리를 세밀하게 조절하고 최적화할 수 있는 강력한 무기입니다. 메모리 풀, 특수한 할당 패턴 등 다양한 활용을 통해 프로그램의 성능을 한 단계 끌어올릴 수 있습니다. 여러분도 프로젝트에 적합한 Allocator를 직접 설계하여 효율적인 메모리 관리의 즐거움을 경험해 보세요! 궁금한 점이 있다면 언제든지 질문해주세요!  🚀

728x90
반응형