[2026] C++ std::pmr Complete Guide: Boost Performance 10x with Polymorphic Memory Resources [#39-2]

[2026] C++ std::pmr Complete Guide: Boost Performance 10x with Polymorphic Memory Resources [#39-2]

이 글의 핵심

Master C++ std::pmr to achieve 10x memory allocation performance. Complete guide with polymorphic_allocator, monotonic_buffer_resource, memory pools, benchmarks, and production patterns for solving malloc bottlenecks.

Introduction: When malloc/new Becomes the Bottleneck

”Too many allocations/deallocations”

When small objects are frequently allocated and deallocated, heap fragmentation (scattered small free spaces making large contiguous allocations difficult) and allocator overhead accumulate. Profilers often show malloc/free consuming 20-30% of execution time. Memory pools pre-allocate large blocks and distribute slices from them, reducing allocation count. C++17 std::pmr (polymorphic memory resources) provides injectable allocators based on std::memory_resource, allowing containers of the same type to use different pools. What this guide covers:

  • Problem scenarios: When malloc dominates your profile
  • std::pmr complete examples: monotonic_buffer_resource, pool_resource, custom memory_resource, pmr containers
  • Common errors and solutions: Lifetime management, resource mixing, alignment
  • Best practices: Pool selection guide, incremental adoption
  • Production patterns: Frame pools, request-scoped arenas, game entities

Conceptual analogy

Memory pools and PMR are like pre-dividing warehouse bins and taking items only when needed. When allocation/deallocation patterns are predictable, this approach is more cache-friendly than global new.

Table of Contents

  1. Problem Scenarios: When malloc is the Bottleneck
  2. Memory Pool Concepts
  3. std::pmr Overview
  4. monotonic_buffer_resource Complete Examples
  5. pool_resource Complete Examples
  6. Custom memory_resource Implementation
  7. pmr Containers in Practice
  8. Common Errors and Solutions
  9. Best Practices
  10. Performance Benchmarks
  11. Production Patterns
  12. Summary

1. Problem Scenarios: When malloc is the Bottleneck

Real-world situations

아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

"Profiler shows malloc/free taking 30% of total execution time."
"Allocating tens of thousands of small objects causes heap fragmentation and OOM."
"Creating/deleting entities every game frame causes severe frame drops."
"Parsing HTTP requests with many allocations causes latency spikes under load."
"Parser repeatedly creates std::vector, std::map causing allocation explosion."

Root causes

  1. Excessive allocation count: Repeated alloc/free of small objects → malloc overhead accumulation
  2. Heap fragmentation: Scattered small free spaces → large contiguous allocation failures
  3. Cache misses: Physically scattered allocations → poor cache efficiency
  4. Lock contention: Global heap lock contention in multithreaded scenarios Memory pools mitigate issues 1-3; thread-local pools mitigate issue 4.

Solution by scenario

ScenarioCharacteristicsRecommended Resource
Game frameCreate/destroy per frame, short lifetimemonotonic_buffer_resource + release()
HTTP requestAllocate per request, free at request endmonotonic_buffer_resource
Node-based structuresRepeated fixed-size node alloc/freesynchronized_pool_resource
Long-lived objectsLong lifetime, varying sizesDefault heap or pool_resource
Parsing/serializationMany temporary buffers, free at scope endmonotonic_buffer_resource

Before/After: HTTP Parser Example

Before (default allocation): Every request allocates vector, map, string from heap repeatedly. 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ Default allocation — many malloc calls
// 실행 예제
void handleRequest(const std::string& raw) {
    std::vector<std::string> path_segments;
    std::map<std::string, std::string> headers;
    std::string body;
    parsePath(raw, path_segments);
    parseHeaders(raw, headers);
    parseBody(raw, body);
    process(path_segments, headers, body);
}

After (pmr applied): Allocate from request-scoped pool, deallocate all at once when function exits. 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ✅ pmr — reduced allocation count & fragmentation
void handleRequest(const std::string& raw) {
    std::pmr::monotonic_buffer_resource request_pool;
    std::pmr::vector<std::pmr::string> path_segments(&request_pool);
    std::pmr::map<std::pmr::string, std::pmr::string, std::less<>>
        headers(&request_pool);
    std::pmr::string body(&request_pool);
    parsePath(raw, path_segments);
    parseHeaders(raw, headers);
    parseBody(raw, body);
    process(path_segments, headers, body);
}  // request_pool destroyed → all allocations freed at once

2. Memory Pool Concepts

Reducing allocation count

  • Pools allocate large memory blocks once (or a few times), then distribute fixed-size or variable-size slots from them. On deallocation, blocks are returned to the pool, and actual free happens only once when the pool is destroyed.
  • Fixed-size pools: For same-sized objects (node pools, event pools) — simple and minimal fragmentation.
  • Variable-size: Supporting multiple sizes complicates slot management. monotonic_buffer_resource uses a “cut from front only, no individual frees” approach — simple to implement and perfect for frame/request-scoped resets.

Memory pool operation flow

아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

flowchart TB
    subgraph Heap[Global Heap]
        H[Allocate large block once]
    end
    subgraph Pool[Memory Pool]
        B[Block]
        B --> S1[Slot 1]
        B --> S2[Slot 2]
        B --> S3[Slot 3]
    end
    H --> B
    S1 --> A1[Object A]
    S2 --> A2[Object B]
    S3 --> A3[Object C]

monotonic vs pool comparison

아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

flowchart LR
    subgraph Mono[monotonic_buffer_resource]
        M1[Alloc 1] --> M2[Alloc 2] --> M3[Alloc 3]
        M3 -.->|release returns all| M1
    end
    subgraph Pool[pool_resource]
        P1[Slot] <--> P2[Slot]
        P2 <--> P3[Slot]
    end

HTTP request processing sequence (with pmr)

아래 코드는 mermaid를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

sequenceDiagram
    participant Req as handleRequest
    participant Pool as monotonic_buffer_resource
    participant Vec as pmr::vector
    participant Map as pmr::map
    Req->>Pool: Create (scope entry)
    Req->>Vec: path_segments(&pool)
    Req->>Map: headers(&pool)
    Req->>Vec: push_back, parsePath...
    Req->>Map: insert, parseHeaders...
    Req->>Req: process()
    Req->>Pool: Destroy (scope exit)
    Note over Pool: All allocations freed at once

3. std::pmr Overview

Why std::pmr?

Traditional std::allocator is a template type fixed at compile time. std::vector<int, MyAllocator<int>> and std::vector<int, OtherAllocator<int>> are different types, making it difficult to pass them to the same function. In contrast, std::pmr::polymorphic_allocator points to a memory_resource* at runtime, so the same std::pmr::vector<int> type can use different pools. This allows APIs to standardize on std::pmr::vector while callers simply switch pools.

Polymorphic Allocator

  • std::pmr::memory_resource is a pure virtual interface defining only: allocate, deallocate, is_equal. Implementations (pool_resource, monotonic_buffer_resource, etc.) can be selected at runtime.
  • std::pmr::polymorphic_allocator<T> is an allocator pointing to this memory_resource. Pass it to containers like std::vector<T, std::pmr::polymorphic_allocator<T>> to allocate from that resource.
  • std::pmr::vector is an alias for std::vector<T, std::pmr::polymorphic_allocator<T>>. Its constructor accepts a memory_resource* to use that pool.

std::pmr architecture diagram

다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

flowchart TB
    subgraph Container[pmr containers]
        V[std::pmr::vector]
        M[std::pmr::map]
        S[std::pmr::string]
    end
    subgraph Alloc[polymorphic_allocator]
        PA[memory_resource*]
    end
    subgraph Resources[memory_resource implementations]
        MONO[monotonic_buffer_resource]
        POOL[synchronized_pool_resource]
        CUSTOM[Custom resource]
    end
    subgraph Backend[Backend]
        HEAP[Global heap]
        BUF[Stack/static buffer]
    end
    V --> PA
    M --> PA
    S --> PA
    PA --> MONO
    PA --> POOL
    PA --> CUSTOM
    MONO --> BUF
    POOL --> HEAP
    CUSTOM --> HEAP

Build and run

# Requires C++17 or later
g++ -std=c++17 -O2 -o pmr_demo pmr_demo.cpp
./pmr_demo

Basic usage example

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory_resource>
#include <vector>
int main() {
    char buffer[1024];
    std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)};
    std::pmr::vector<int> v(&pool);
    v.push_back(1);
    v.push_back(2);
    // v's allocations come from buffer
}

4. monotonic_buffer_resource Complete Examples

Concept: Forward-only allocation

  • std::pmr::monotonic_buffer_resource: Sequentially slices memory from a given buffer (or upstream resource). deallocate is a no-op; individual frees don’t happen. Use release() to reset everything. Perfect for frame buffers and request scopes.

Example 1: Stack buffer + monotonic (zero heap allocations)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory_resource>
#include <vector>
#include <array>
void processRequest() {
    // Allocate 64KB buffer on stack — no heap usage
    std::array<std::byte, 65536> stack_buffer;
    std::pmr::monotonic_buffer_resource pool{
        stack_buffer.data(), stack_buffer.size(),
        std::pmr::new_delete_resource()  // Use heap on overflow
    };
    std::pmr::vector<int> ids(&pool);
    std::pmr::vector<std::pmr::string> tokens(&pool);
    for (int i = 0; i < 1000; ++i) {
        ids.push_back(i);
        tokens.push_back("token");
    }
    // Function exit → stack_buffer and pool automatically freed
}

Example 2: Frame pool + release()

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory_resource>
#include <vector>
struct Entity { int id; float x, y; };
struct Component { int type; void* data; };
void gameLoop() {
    std::array<std::byte, 1024*1024> frame_buffer;
    std::pmr::monotonic_buffer_resource frame_pool{
        frame_buffer.data(), frame_buffer.size(),
        std::pmr::new_delete_resource()
    };
    while (running) {
        frame_pool.release();  // Reset previous frame memory
        std::pmr::vector<Entity> entities(&frame_pool);
        std::pmr::vector<Component> components(&frame_pool);
        // ....create entities, frame logic ...
    }
}

Example 3: Multiple pmr containers sharing same pool

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory_resource>
#include <vector>
#include <string>
#include <map>
int main() {
    std::pmr::monotonic_buffer_resource pool;
    std::pmr::vector<int> nums(&pool);
    std::pmr::vector<std::pmr::string> names(&pool);
    std::pmr::map<std::pmr::string, int, std::less<>> scores(&pool);
    nums.push_back(42);
    names.push_back("Alice");
    scores[Bob] = 100;
    // All allocations from pool
}

5. pool_resource Complete Examples

Concept: Reusing fixed-size blocks

  • std::pmr::synchronized_pool_resource / unsynchronized_pool_resource: Manage fixed-size block pools and reuse freed blocks. Use pool_options to configure block size, max block count, etc.

Example 1: Configuring block size with pool_options

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory_resource>
int main() {
    std::pmr::pool_options opts;
    opts.max_blocks_per_chunk = 32;   // Max blocks per chunk
    opts.largest_required_pool_block = 256;  // Max block size
    std::pmr::synchronized_pool_resource pool{opts};
    // Suitable for objects ≤256 bytes, thread-safe
}

Example 2: Applying pool to node-based data structures

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory_resource>
#include <list>
struct TreeNode {
    int value;
    TreeNode* left = nullptr;
    TreeNode* right = nullptr;
};
void buildTree(std::pmr::memory_resource* mr) {
    std::pmr::list<TreeNode> nodes(mr);
    for (int i = 0; i < 1000; ++i) {
        nodes.push_back(TreeNode{i});
    }
    // Nodes allocated from pool, returned to pool on list destruction
}
int main() {
    std::pmr::synchronized_pool_resource pool;
    buildTree(&pool);
}

Example 3: unsynchronized_pool (single-thread only)

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory_resource>
void singleThreadWork() {
    // Single thread only — no lock overhead
    std::pmr::unsynchronized_pool_resource pool;
    std::pmr::vector<int> v(&pool);
    for (int i = 0; i < 10000; ++i) {
        v.push_back(i);
    }
}

6. Custom memory_resource Implementation

Example 1: Logging memory_resource

A debugging resource that logs allocations/deallocations. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory_resource>
#include <iostream>
#include <cstddef>
class logging_memory_resource : public std::pmr::memory_resource {
public:
    explicit logging_memory_resource(std::pmr::memory_resource* upstream
        = std::pmr::get_default_resource())
        : upstream_(upstream) {}
private:
    void* do_allocate(std::size_t bytes, std::size_t alignment) override {
        void* p = upstream_->allocate(bytes, alignment);
        std::cout << "[alloc] " << bytes << " bytes, align " << alignment
                  << " -> " << p << "\n";
        return p;
    }
    void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
        std::cout << "[dealloc] " << bytes << " bytes @ " << p << "\n";
        upstream_->deallocate(p, bytes, alignment);
    }
    [[nodiscard]] bool do_is_equal(
        const std::pmr::memory_resource& other) const noexcept override {
        return this == &other;
    }
    std::pmr::memory_resource* upstream_;
};

Example 2: Statistics-collecting memory_resource

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory_resource>
#include <atomic>
#include <cstddef>
class stats_memory_resource : public std::pmr::memory_resource {
public:
    explicit stats_memory_resource(std::pmr::memory_resource* upstream
        = std::pmr::get_default_resource())
        : upstream_(upstream) {}
    std::size_t allocation_count() const noexcept { return alloc_count_.load(); }
    std::size_t total_allocated() const noexcept { return total_allocated_.load(); }
    std::size_t peak_allocated() const noexcept { return peak_allocated_.load(); }
private:
    void* do_allocate(std::size_t bytes, std::size_t alignment) override {
        void* p = upstream_->allocate(bytes, alignment);
        alloc_count_.fetch_add(1);
        std::size_t prev = total_allocated_.fetch_add(bytes);
        std::size_t current = prev + bytes;
        for (std::size_t peak = peak_allocated_.load();
             current > peak && !peak_allocated_.compare_exchange_weak(peak, current);
             peak = peak_allocated_.load()) {}
        return p;
    }
    void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
        total_allocated_.fetch_sub(bytes);
        upstream_->deallocate(p, bytes, alignment);
    }
    [[nodiscard]] bool do_is_equal(
        const std::pmr::memory_resource& other) const noexcept override {
        return this == &other;
    }
    std::pmr::memory_resource* upstream_;
    std::atomic<std::size_t> alloc_count_{0};
    std::atomic<std::size_t> total_allocated_{0};
    std::atomic<std::size_t> peak_allocated_{0};
};

Example 3: Thread-local monotonic pool

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory_resource>
#include <vector>
#include <thread>
thread_local std::pmr::monotonic_buffer_resource* tls_pool = nullptr;
void initThreadPool() {
    tls_pool = new std::pmr::monotonic_buffer_resource(
        std::pmr::new_delete_resource());
}
void cleanupThreadPool() {
    delete tls_pool;
    tls_pool = nullptr;
}
std::pmr::memory_resource* getThreadPool() {
    if (!tls_pool) initThreadPool();
    return tls_pool;
}
void worker(int id) {
    std::pmr::vector<int> local_data(getThreadPool());
    for (int i = 0; i < 1000; ++i) {
        local_data.push_back(i * id);
    }
}

Example 4: Fixed-block pool (simple implementation)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory_resource>
#include <vector>
#include <cstddef>
class fixed_block_pool : public std::pmr::memory_resource {
public:
    explicit fixed_block_pool(std::size_t block_size, std::size_t block_count = 64)
        : block_size_(block_size), blocks_(block_count) {
        storage_.resize(block_size * block_count);
        for (std::size_t i = 0; i < block_count; ++i) {
            free_list_.push_back(storage_.data() + i * block_size);
        }
    }
private:
    void* do_allocate(std::size_t bytes, std::size_t alignment) override {
        if (bytes > block_size_) return nullptr;
        if (free_list_.empty()) return nullptr;
        void* p = free_list_.back();
        free_list_.pop_back();
        return p;
    }
    void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
        if (p >= storage_.data() && p < storage_.data() + storage_.size()) {
            free_list_.push_back(static_cast<std::byte*>(p));
        }
    }
    [[nodiscard]] bool do_is_equal(
        const std::pmr::memory_resource& other) const noexcept override {
        return this == &other;
    }
    std::size_t block_size_;
    std::vector<std::byte> storage_;
    std::vector<std::byte*> blocks_;
    std::vector<std::byte*> free_list_;
};

7. pmr Containers in Practice

Supported pmr containers

ContainerAliasUse Case
std::pmr::stringvector<char, polymorphic_allocator<char>>Strings
std::pmr::vectorvector<T, polymorphic_allocator<T>>Dynamic arrays
std::pmr::mapmap<K,V,…,polymorphic_allocator<pair<…>>>Sorted maps
std::pmr::setset<T,…,polymorphic_allocator<T>>Sorted sets
std::pmr::unordered_mapunordered_map with pmr allocatorHash maps
std::pmr::listlist<T, polymorphic_allocator<T>>Doubly-linked lists

Nested pmr containers: map<string, vector<string>>

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory_resource>
#include <vector>
#include <string>
#include <map>
void parseConfig(const char* raw) {
    std::pmr::monotonic_buffer_resource pool;
    // Both map keys and values are pmr::string
    // If value is vector<pmr::string>, strings inside also use pool
    std::pmr::map<std::pmr::string, std::pmr::vector<std::pmr::string>, std::less<>>
        config(&pool);
    config[sections].push_back("a");
    config[sections].push_back("b");
    config[keys].push_back("x");
    // All allocations from pool
}

pmr string caution: Don’t mix with std::string

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ Dangerous: mixing std::string and std::pmr::string
std::pmr::map<std::pmr::string, std::string, std::less<>> m(&pool);
// value is std::string → uses default allocator, not pool
// ✅ Correct: all pmr
std::pmr::map<std::pmr::string, std::pmr::string, std::less<>> m(&pool);

8. Common Errors and Solutions

Error 1: Container outlives pool (Use-After-Free)

Symptoms: Crash, undefined behavior, heap corruption. Cause: memory_resource destroyed before containers using it. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ Wrong code
std::pmr::vector<int>* createVector() {
    std::pmr::monotonic_buffer_resource pool;
    return new std::pmr::vector<int>(&pool);  // pool destroyed on function exit!
}
// Returned vector points to freed pool → UB

Solution: 다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ Correct: pool lifetime > container lifetime
std::pmr::monotonic_buffer_resource* pool = new std::pmr::monotonic_buffer_resource;
std::pmr::vector<int>* vec = new std::pmr::vector<int>(pool);
// Manage lifetimes to ensure pool outlives vec

Or keep pool and container in same scope: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ Pool and container lifetimes match
void process() {
    std::pmr::monotonic_buffer_resource pool;
    std::pmr::vector<int> vec(&pool);
    // ....use ...
}  // vec, pool destroyed in order

Error 2: Expecting deallocate from monotonic

Symptoms: Memory not freed, keeps accumulating. Cause: monotonic_buffer_resource’s deallocate is a no-op. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ Wrong expectation
std::pmr::monotonic_buffer_resource pool;
std::pmr::vector<int> v(&pool);
v.push_back(1);
v.pop_back();  // Internally calls deallocate, but pool doesn't free
// Memory remains in pool until release() called

Solution: Use monotonic only for release() full reset. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ monotonic: release at scope end
{
    std::pmr::monotonic_buffer_resource pool;
    std::pmr::vector<int> v(&pool);
    v.push_back(1);
    v.pop_back();
    pool.release();  // Full pool reset at this point
}

Error 3: Copying/moving containers from different resources

Symptoms: Crash or memory corruption. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ Dangerous code
std::pmr::monotonic_buffer_resource pool1, pool2;
std::pmr::vector<int> a(&pool1);
a.push_back(42);
std::pmr::vector<int> b(&pool2);
b = a;  // a's allocator copied to b. b uses pool2 but data from pool1
// b destruction passes pool1 pointer to pool2.deallocate → UB

Solution: Only copy/move containers using same resource. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ Same pool
std::pmr::monotonic_buffer_resource pool;
std::pmr::vector<int> a(&pool);
std::pmr::vector<int> b(&pool);
a.push_back(42);
b = a;  // Both use pool → safe

Error 4: Insufficient stack buffer size

Symptoms: monotonic allocates additional memory from upstream (heap). 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ Buffer may be too small
char buffer[256];
std::pmr::monotonic_buffer_resource pool{buffer, sizeof(buffer)};
std::pmr::vector<int> v(&pool);
for (int i = 0; i < 1000; ++i) v.push_back(i);  // Exceeds 256 bytes → uses heap

Solution: Allocate generous buffer size. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ Generous buffer
std::array<std::byte, 65536> buffer;
std::pmr::monotonic_buffer_resource pool{
    buffer.data(), buffer.size(),
    std::pmr::new_delete_resource()
};

Error 5: Inappropriate pool_options settings

Symptoms: Memory waste or allocation failures with synchronized_pool_resource. Solution: Configure based on actual maximum block size used. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ Settings matching usage pattern
std::pmr::pool_options opts;
opts.largest_required_pool_block = 64;   // Mostly ≤64-byte objects
opts.max_blocks_per_chunk = 128;
std::pmr::synchronized_pool_resource pool{opts};

Error 6: Ignoring alignment

Symptoms: Crash or SIGBUS on certain platforms. 다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ Wrong implementation (ignoring alignment)
void* do_allocate(std::size_t bytes, std::size_t alignment) override {
    return upstream_->allocate(bytes, 1);  // Ignoring alignment!
}

Solution: Always pass requested alignment. 다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ Respecting alignment
void* do_allocate(std::size_t bytes, std::size_t alignment) override {
    return upstream_->allocate(bytes, alignment);
}

Error 7: Thread safety misconceptions

Symptoms: Crash or data corruption in multithreaded scenarios. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ Dangerous: multiple threads sharing same unsynchronized_pool
// 실행 예제
std::pmr::unsynchronized_pool_resource pool;
std::thread t1([&] { std::pmr::vector<int> v(&pool); /* ....*/ });
std::thread t2([&] { std::pmr::vector<int> v(&pool); /* ....*/ });

Solution: Use synchronized_pool_resource for shared pools in multithreaded code.

// ✅ synchronized_pool_resource (thread-safe)
std::pmr::synchronized_pool_resource pool;

9. Best Practices

1. Pool selection guide

PatternRecommendedReason
Frame/request unit, bulk freemonotonicNo deallocate, reset with release()
Individual object repeated alloc/freepool_resourceBlock reuse
Thread-local independent allocationThread-local monotonicEliminates lock contention
Debugging/profilingWrap with stats_memory_resourceTrack allocation count & peak

2. Pool lifetime rules

Always: pool lifetime ≥ all containers using it

3. Use pmr for nested container elements too

// ✅ Both map keys and values are pmr types
std::pmr::map<std::pmr::string, std::pmr::vector<int>, std::less<>> m(&pool);

4. Apply after profiling

// Step 1: Identify bottleneck with profiling
// Step 2: Introduce pool only in that function/scope
// Step 3: Measure performance, then expand application

5. Use statistics resource in debug builds

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#ifdef NDEBUG
    std::pmr::memory_resource* resource = std::pmr::get_default_resource();
#else
    static stats_memory_resource stats{std::pmr::get_default_resource()};
    std::pmr::memory_resource* resource = &stats;
#endif
std::pmr::vector<int> data(resource);
// ....processing ...
#ifndef NDEBUG
    std::cout << "Allocations: " << stats.allocation_count()
              << ", Peak: " << stats.peak_allocated() << " bytes\n";
#endif

10. Performance Benchmarks

Benchmark 1: Default allocation vs monotonic vs pool

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <chrono>
#include <memory_resource>
#include <vector>
#include <iostream>
void benchmarkAllocators() {
    constexpr size_t N = 100000;
    constexpr size_t elem_size = 32;
    // 1. Default heap allocation
    auto t1 = std::chrono::high_resolution_clock::now();
    {
        std::vector<int> v;
        v.reserve(N);
        for (size_t i = 0; i < N; ++i) v.push_back(static_cast<int>(i));
    }
    auto t2 = std::chrono::high_resolution_clock::now();
    // 2. monotonic_buffer_resource
    std::vector<std::byte> buffer(N * elem_size * 2);
    std::pmr::monotonic_buffer_resource mono{
        buffer.data(), buffer.size(),
        std::pmr::new_delete_resource()
    };
    auto t3 = std::chrono::high_resolution_clock::now();
    {
        std::pmr::vector<int> v(&mono);
        v.reserve(N);
        for (size_t i = 0; i < N; ++i) v.push_back(static_cast<int>(i));
    }
    auto t4 = std::chrono::high_resolution_clock::now();
    // 3. synchronized_pool_resource
    std::pmr::synchronized_pool_resource pool;
    auto t5 = std::chrono::high_resolution_clock::now();
    {
        std::pmr::vector<int> v(&pool);
        v.reserve(N);
        for (size_t i = 0; i < N; ++i) v.push_back(static_cast<int>(i));
    }
    auto t6 = std::chrono::high_resolution_clock::now();
    using namespace std::chrono;
    auto d_default = duration_cast<microseconds>(t2 - t1).count();
    auto d_mono = duration_cast<microseconds>(t4 - t3).count();
    auto d_pool = duration_cast<microseconds>(t6 - t5).count();
    std::cout << "Default:   " << d_default << " μs\n";
    std::cout << "Monotonic: " << d_mono << " μs (" << (double)d_default/d_mono << "x)\n";
    std::cout << "Pool:      " << d_pool << " μs (" << (double)d_default/d_pool << "x)\n";
}

Expected results:

Allocator100k push_back (μs)Relative Speed
Default vector2000~50001x
monotonic500~15002~4x
synchronized_pool800~20001.5~3x

Benchmark summary table

ScenarioDefault Heapmonotonicpool_resourceNotes
Sequential push_back (no frees)1x2~4x1.5~3xmonotonic wins
Repeated alloc/free (fixed size)1xNot suitable2~5xpool wins
High thread contention1xGood with per-threadSync overheadThread-local monotonic

11. Production Patterns

Pattern 1: Game frame pool

아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

void gameLoop() {
    std::array<std::byte, 1024*1024> frame_buffer;
    while (running) {
        std::pmr::monotonic_buffer_resource frame_pool{
            frame_buffer.data(), frame_buffer.size(),
            std::pmr::new_delete_resource()
        };
        std::pmr::vector<Entity> entities(&frame_pool);
        std::pmr::vector<Component> components(&frame_pool);
        // ....create entities, frame logic ...
    }  // frame_pool, entities destroyed → same buffer reused next frame
}

Pattern 2: HTTP request scope

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

void handleRequest(const Request& req) {
    std::pmr::monotonic_buffer_resource request_pool(
        std::pmr::new_delete_resource());
    std::pmr::vector<std::pmr::string> path_segments(&request_pool);
    std::pmr::map<std::pmr::string, std::pmr::string, std::less<>>
        headers(&request_pool);
    parsePath(req.uri, path_segments);
    parseHeaders(req.raw_headers, headers);
    Response response = processRequest(path_segments, headers);
    sendResponse(response);
}  // request_pool destroyed → all allocations freed

Pattern 3: Per-worker pool in thread pool

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Worker {
    std::pmr::monotonic_buffer_resource worker_pool_;
    std::pmr::vector<Task> local_queue_{&worker_pool_};
public:
    Worker() : worker_pool_(std::pmr::new_delete_resource()) {}
    void process(Task t) {
        worker_pool_.release();
        local_queue_.clear();
        // Process t using local_queue_ allocations
    }
};

Pattern 4: Hierarchical resources (upstream)

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

std::pmr::synchronized_pool_resource global_pool;
std::pmr::monotonic_buffer_resource thread_pool{&global_pool};
std::pmr::monotonic_buffer_resource frame_pool{&thread_pool};
std::pmr::vector<int> frame_data(&frame_pool);
// frame_pool exhausted → thread_pool → global_pool

Pattern 5: Implementation checklist

  • Pool lifetime exceeds all containers using it
  • Clear release() timing for monotonic (frame/request end)
  • No copy/assignment between pmr containers using different pools
  • Generous stack buffer size when used
  • pool_options match actual usage patterns
  • Use synchronized_pool_resource or per-thread pools for multithreading

12. Summary

TopicSummary
Memory poolsAllocate large block once, distribute slots — reduces allocation count & fragmentation
std::pmrmemory_resource + polymorphic_allocator for injecting pools into containers
monotonicSequential allocation, reset only — perfect for frame/request scopes
pool_resourceFixed-size block reuse — suitable for general object pools
Custom resourceInherit memory_resource for logging, statistics, thread-local pools
Core principles:
  1. Pool lifetime > container lifetime
  2. monotonic frees only via release()
  3. Beware copy/assignment between containers from different pools
  4. Apply after profiling confirms actual benefits

Practical Checklist

Items to verify when applying these concepts in production.

Before writing code

  • Is this technique the best solution for the current problem?
  • Can team members understand and maintain this code?
  • Does it meet performance requirements?

While writing code

  • Have all compiler warnings been resolved?
  • Have edge cases been considered?
  • Is error handling appropriate?

During code review

  • Is the code’s intent clear?
  • Are test cases sufficient?
  • Is it documented? Use this checklist to reduce mistakes and improve code quality.

Keywords

std::pmr, polymorphic memory resource, memory pool, monotonic_buffer_resource, pool_resource, polymorphic_allocator, pmr containers, performance optimization

Frequently Asked Questions (FAQ)

Q. When do I use this in production?

A. When profiling shows malloc/free dominating execution time, when allocating many short-lived objects per frame/request (games, HTTP servers), or when node-based data structures repeatedly allocate fixed-size blocks.

Q. monotonic vs pool_resource?

A. Use monotonic for “allocate many, free all at once” patterns (frames/requests). Use pool_resource for repeated individual alloc/free patterns with fixed sizes.

Q. What should I read first?

A. Check the C++ Series Index for the complete flow. Reading #39-1 Cache & Data-Oriented Design first is recommended.

Q. How to study deeper?

A. Refer to cppreference std::pmr and P0339R6 specification. One-line summary: std::pmr enables control over memory pools and fragmentation. Next, read SIMD & Intrinsics (#39-3). Previous: High-Performance C++ #39-1: Cache & Data-Oriented Design Next: High-Performance C++ #39-3: SIMD & Parallelization

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3