[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
- Problem Scenarios: When malloc is the Bottleneck
- Memory Pool Concepts
- std::pmr Overview
- monotonic_buffer_resource Complete Examples
- pool_resource Complete Examples
- Custom memory_resource Implementation
- pmr Containers in Practice
- Common Errors and Solutions
- Best Practices
- Performance Benchmarks
- Production Patterns
- 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
- Excessive allocation count: Repeated alloc/free of small objects → malloc overhead accumulation
- Heap fragmentation: Scattered small free spaces → large contiguous allocation failures
- Cache misses: Physically scattered allocations → poor cache efficiency
- Lock contention: Global heap lock contention in multithreaded scenarios Memory pools mitigate issues 1-3; thread-local pools mitigate issue 4.
Solution by scenario
| Scenario | Characteristics | Recommended Resource |
|---|---|---|
| Game frame | Create/destroy per frame, short lifetime | monotonic_buffer_resource + release() |
| HTTP request | Allocate per request, free at request end | monotonic_buffer_resource |
| Node-based structures | Repeated fixed-size node alloc/free | synchronized_pool_resource |
| Long-lived objects | Long lifetime, varying sizes | Default heap or pool_resource |
| Parsing/serialization | Many temporary buffers, free at scope end | monotonic_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
| Container | Alias | Use Case |
|---|---|---|
| std::pmr::string | vector<char, polymorphic_allocator<char>> | Strings |
| std::pmr::vector | vector<T, polymorphic_allocator<T>> | Dynamic arrays |
| std::pmr::map | map<K,V,…,polymorphic_allocator<pair<…>>> | Sorted maps |
| std::pmr::set | set<T,…,polymorphic_allocator<T>> | Sorted sets |
| std::pmr::unordered_map | unordered_map with pmr allocator | Hash maps |
| std::pmr::list | list<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
| Pattern | Recommended | Reason |
|---|---|---|
| Frame/request unit, bulk free | monotonic | No deallocate, reset with release() |
| Individual object repeated alloc/free | pool_resource | Block reuse |
| Thread-local independent allocation | Thread-local monotonic | Eliminates lock contention |
| Debugging/profiling | Wrap with stats_memory_resource | Track 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:
| Allocator | 100k push_back (μs) | Relative Speed |
|---|---|---|
| Default vector | 2000~5000 | 1x |
| monotonic | 500~1500 | 2~4x |
| synchronized_pool | 800~2000 | 1.5~3x |
Benchmark summary table
| Scenario | Default Heap | monotonic | pool_resource | Notes |
|---|---|---|---|---|
| Sequential push_back (no frees) | 1x | 2~4x | 1.5~3x | monotonic wins |
| Repeated alloc/free (fixed size) | 1x | Not suitable | 2~5x | pool wins |
| High thread contention | 1x | Good with per-thread | Sync overhead | Thread-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_resourceor per-thread pools for multithreading
12. Summary
| Topic | Summary |
|---|---|
| Memory pools | Allocate large block once, distribute slots — reduces allocation count & fragmentation |
| std::pmr | memory_resource + polymorphic_allocator for injecting pools into containers |
| monotonic | Sequential allocation, reset only — perfect for frame/request scopes |
| pool_resource | Fixed-size block reuse — suitable for general object pools |
| Custom resource | Inherit memory_resource for logging, statistics, thread-local pools |
| Core principles: |
- Pool lifetime > container lifetime
- monotonic frees only via
release() - Beware copy/assignment between containers from different pools
- Apply after profiling confirms actual benefits
Related Articles
- C++ Modern Memory Management: Custom Allocators & std::pmr Guide
- C++ Cache-Efficient Code: Data-Oriented Design Guide
- C++ std::chrono Complete Guide | duration, time_point, clocks, time measurement
- C++ SIMD & Parallelization: std::execution & Intrinsics Guide
- C++ Memory Management Complete Guide | Allocators, Pools, Arenas, Production Patterns [#55-5]
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