C++17 Polymorphic Memory Resource (pmr)
std::pmr(Polymorphic Memory Resource)은 C++17에서 도입된 런타임 교체 가능한 메모리 할당 추상화입니다. 컨테이너의 할당자(Allocator)를 컴파일 타임이 아닌 런타임에 결정할 수 있어 스택 버퍼, 풀, 모노토닉 할당자 등을 유연하게 적용할 수 있습니다.
1. pmr 컨테이너 기본
섹션 제목: “1. pmr 컨테이너 기본”#include <memory_resource>#include <vector>#include <string>
int main(){ // 스택 버퍼를 메모리 소스로 사용 std::array<std::byte, 1024> buf; std::pmr::monotonic_buffer_resource pool(buf.data(), buf.size());
// 힙 할당 없이 스택 버퍼에서 할당 std::pmr::vector<int> v(&pool); v.push_back(1); v.push_back(2); v.push_back(3);
std::pmr::string s(&pool); s = "hello world";}// 풀 소멸 시 모든 메모리 일괄 해제2. monotonic_buffer_resource
섹션 제목: “2. monotonic_buffer_resource”한 방향으로만 늘어나며 해제가 없는 가장 빠른 할당자입니다.
#include <memory_resource>
void process_frame(){ // 프레임당 128KB 스택 버퍼 std::array<std::byte, 128 * 1024> frame_buf; std::pmr::monotonic_buffer_resource arena( frame_buf.data(), frame_buf.size(), std::pmr::null_memory_resource()); // 오버플로 시 throw
std::pmr::vector<std::pmr::string> results(&arena);
for (int i = 0; i < 100; ++i) results.emplace_back(std::to_string(i), &arena);
// 프레임 끝: arena 소멸 → 일괄 해제 (O(1))}3. synchronized_pool_resource
섹션 제목: “3. synchronized_pool_resource”고정 크기 블록 풀로 단편화 없이 할당/해제를 반복합니다.
#include <memory_resource>
std::pmr::synchronized_pool_resource pool( std::pmr::pool_options{ .max_blocks_per_chunk = 20, .largest_required_pool_block = 1024 });
// 여러 스레드에서 안전하게 사용 가능std::pmr::vector<int> v1(&pool);std::pmr::vector<int> v2(&pool);unsynchronized_pool_resource는 단일 스레드 전용이지만 더 빠릅니다.
4. 커스텀 memory_resource
섹션 제목: “4. 커스텀 memory_resource”#include <memory_resource>#include <cstdlib>#include <iostream>
class LoggingResource : public std::pmr::memory_resource{ std::pmr::memory_resource* upstream_; std::size_t allocated_ = 0;
protected: void* do_allocate(std::size_t bytes, std::size_t align) override { allocated_ += bytes; std::cout << "[alloc] " << bytes << " bytes (total: " << allocated_ << ")\n"; return upstream_->allocate(bytes, align); }
void do_deallocate(void* p, std::size_t bytes, std::size_t align) override { allocated_ -= bytes; std::cout << "[free] " << bytes << " bytes (total: " << allocated_ << ")\n"; upstream_->deallocate(p, bytes, align); }
bool do_is_equal(const memory_resource& o) const noexcept override { return this == &o; }
public: explicit LoggingResource(std::pmr::memory_resource* up = std::pmr::get_default_resource()) : upstream_(up) {}
std::size_t total_allocated() const { return allocated_; }};
int main(){ LoggingResource logger; std::pmr::vector<std::pmr::string> v(&logger); v.emplace_back("hello", &logger); v.emplace_back("world", &logger);}5. 표준 제공 resource
섹션 제목: “5. 표준 제공 resource”| 리소스 | 특성 |
|---|---|
monotonic_buffer_resource | 단방향 할당, O(1) 해제, 가장 빠름 |
unsynchronized_pool_resource | 블록 풀, 단일 스레드 |
synchronized_pool_resource | 블록 풀, 멀티 스레드 안전 |
new_delete_resource() | 전역 new/delete 위임 |
null_memory_resource() | 항상 throw (오버플로 감지) |
6. 체인 구성
섹션 제목: “6. 체인 구성”// 버퍼 → 풀 → 글로벌 힙 체인std::array<std::byte, 4096> buf;std::pmr::monotonic_buffer_resource mono( buf.data(), buf.size(), std::pmr::new_delete_resource()); // 버퍼 고갈 시 힙으로 폴백
std::pmr::unsynchronized_pool_resource pool(&mono);
std::pmr::vector<int> v(&pool);7. pmr 컨테이너와 일반 컨테이너 호환
섹션 제목: “7. pmr 컨테이너와 일반 컨테이너 호환”#include <memory_resource>#include <vector>#include <string>
// pmr::vector<T>는 vector<T, pmr::polymorphic_allocator<T>>의 별칭// 타입이 다르므로 일반 vector와 직접 대입 불가
std::pmr::vector<int> pmr_v;std::vector<int> std_v;
// 변환: 범위 생성자 사용std_v = std::vector<int>(pmr_v.begin(), pmr_v.end()); // 복사
// 함수 파라미터: 둘 다 받으려면 템플릿 또는 span 사용template<typename Container>void process(const Container& c);
// C++20 std::span으로 통일 (읽기 전용)#include <span>void process_span(std::span<const int> data);process_span(pmr_v); // pmr::vector → spanprocess_span(std_v); // std::vector → span8. pmr 성능 특성 및 벤치마크 가이드
섹션 제목: “8. pmr 성능 특성 및 벤치마크 가이드”// 성능 측정 예시#include <chrono>#include <memory_resource>#include <vector>#include <string>
void benchmark_pmr_vs_heap(){ constexpr int ITERATIONS = 100'000;
// 표준 힙 할당 auto t1 = std::chrono::high_resolution_clock::now(); for (int i = 0; i < ITERATIONS; ++i) { std::vector<int> v; for (int j = 0; j < 100; ++j) v.push_back(j); } auto heap_time = std::chrono::high_resolution_clock::now() - t1;
// pmr monotonic (스택 버퍼) auto t2 = std::chrono::high_resolution_clock::now(); for (int i = 0; i < ITERATIONS; ++i) { std::array<std::byte, 1024> buf; std::pmr::monotonic_buffer_resource pool(buf.data(), buf.size(), std::pmr::null_memory_resource()); std::pmr::vector<int> v(&pool); for (int j = 0; j < 100; ++j) v.push_back(j); } auto pmr_time = std::chrono::high_resolution_clock::now() - t2;
// 일반적으로 pmr monotonic이 3~10배 빠름 (힙 할당 0회) std::cout << "heap: " << std::chrono::duration_cast<std::chrono::microseconds>(heap_time).count() << "us\n"; std::cout << "pmr: " << std::chrono::duration_cast<std::chrono::microseconds>(pmr_time).count() << "us\n";}std::pmr은 컨테이너를 바꾸지 않고 메모리 전략만 교체하는 Zero-overhead 추상화입니다. 프레임 단위 임시 할당에는 monotonic_buffer_resource, 빈번한 할당/해제 사이클에는 pool_resource를 사용하세요. 커스텀 memory_resource로 진단, 추적, 한도 제한 등 프로젝트 특화 할당 정책을 구현할 수 있습니다.
| 시나리오 | 권장 resource |
|---|---|
| 프레임 단위 임시 데이터 | monotonic_buffer_resource |
| 빈번한 할당/해제 반복 | unsynchronized_pool_resource |
| 멀티스레드 공유 풀 | synchronized_pool_resource |
| 할당 실패 즉시 감지 | null_memory_resource() as upstream |
| 메모리 추적/진단 | 커스텀 memory_resource 래퍼 |