CodeNebula
C++17stladvanced

std::pmr::monotonic_buffer_resource

C++17 · Practical Modern · Compiler flag: -std=c++17

Explanation

A fast memory resource that allocates from a contiguous buffer and never frees individual allocations. Memory is released all at once when the resource is destroyed. This is ideal for short-lived, bump-allocator workloads.

Real-World Scenario

Allocate all temporary objects for a single frame in a game engine, then discard them all at frame end.

Quick Snippet

snippet.cpp
#include <memory_resource>
#include <vector>

char buffer[4096];
std::pmr::monotonic_buffer_resource pool(buffer, sizeof(buffer));
std::pmr::vector<std::string> names(&pool);
names.emplace_back("Alice");
names.emplace_back("Bob");
// All memory freed when 'pool' goes out of scope

Full Example

monotonic_buffer_resource.cpp
// CodeNebula - C++17 Example: std::pmr::monotonic_buffer_resource
// Compile: g++ -std=c++17 -Wall -Wextra -o example monotonic_buffer_resource.cpp

// Demonstrates std::pmr::monotonic_buffer_resource, a fast memory resource
// that allocates by bumping a pointer forward. It never reuses freed memory
// until the entire resource is destroyed or release() is called. Ideal for
// short-lived, allocation-heavy workloads.

#include <iostream>
#include <memory_resource>
#include <vector>
#include <string>
#include <array>
#include <chrono>

int main()
{
    // ------------------------------------------------------------------
    // 1. Stack-based monotonic buffer (no heap at all)
    // ------------------------------------------------------------------
    std::cout << "=== Stack-Based Buffer ===" << std::endl;
    std::array<std::byte, 512> stack_buf{};
    std::pmr::monotonic_buffer_resource stack_pool(
        stack_buf.data(), stack_buf.size(),
        std::pmr::null_memory_resource()  // No fallback: fail if exhausted
    );

    std::pmr::vector<int> v1(&stack_pool);
    for (int i = 1; i <= 10; ++i) v1.push_back(i);

    std::cout << "  Elements: ";
    for (int x : v1) std::cout << x << " ";
    std::cout << std::endl;
    std::cout << "  All from stack buffer (no heap allocation)" << std::endl;

    // ------------------------------------------------------------------
    // 2. Heap-backed with upstream fallback
    // ------------------------------------------------------------------
    std::cout << "\n=== Heap-Backed with Fallback ===" << std::endl;
    std::array<std::byte, 32> tiny_buf{};
    std::pmr::monotonic_buffer_resource fallback_pool(
        tiny_buf.data(), tiny_buf.size(),
        std::pmr::get_default_resource()  // Falls back to heap
    );

    std::pmr::vector<int> v2(&fallback_pool);
    for (int i = 0; i < 100; ++i) v2.push_back(i);

    std::cout << "  Stored " << v2.size() << " elements" << std::endl;
    std::cout << "  Started in 32-byte buffer, overflowed to heap" << std::endl;

    // ------------------------------------------------------------------
    // 3. release() resets the buffer for reuse
    // ------------------------------------------------------------------
    std::cout << "\n=== release() ===" << std::endl;
    std::array<std::byte, 256> buf{};
    std::pmr::monotonic_buffer_resource reusable(
        buf.data(), buf.size(), std::pmr::null_memory_resource()
    );

    for (int round = 1; round <= 3; ++round) {
        std::pmr::vector<int> temp(&reusable);
        for (int i = 0; i < 5; ++i) temp.push_back(round * 10 + i);

        std::cout << "  Round " << round << ": ";
        for (int x : temp) std::cout << x << " ";
        std::cout << std::endl;

        // temp destructor runs, but memory not reclaimed until release()
        reusable.release();  // Reset: buffer available again
    }

    // ------------------------------------------------------------------
    // 4. Performance comparison: monotonic vs default
    // ------------------------------------------------------------------
    std::cout << "\n=== Performance Comparison ===" << std::endl;
    constexpr int N = 100000;

    auto bench = [&](const char* label, std::pmr::memory_resource* mr) {
        auto start = std::chrono::steady_clock::now();
        for (int trial = 0; trial < 10; ++trial) {
            std::pmr::vector<int> v(mr);
            for (int i = 0; i < N; ++i) v.push_back(i);
        }
        auto end = std::chrono::steady_clock::now();
        auto ms = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "  " << label << ": " << ms << " us" << std::endl;
    };

    bench("default (heap)", std::pmr::get_default_resource());

    std::pmr::monotonic_buffer_resource fast_pool(N * sizeof(int) * 2);
    bench("monotonic     ", &fast_pool);

    return 0;
}

/*
Expected output (timings vary):

=== Stack-Based Buffer ===
  Elements: 1 2 3 4 5 6 7 8 9 10
  All from stack buffer (no heap allocation)

=== Heap-Backed with Fallback ===
  Stored 100 elements
  Started in 32-byte buffer, overflowed to heap

=== release() ===
  Round 1: 10 11 12 13 14
  Round 2: 20 21 22 23 24
  Round 3: 30 31 32 33 34

=== Performance Comparison ===
  default (heap): 12345 us
  monotonic     : 6789 us
*/
Run command
g++ -std=c++17 -Wall -Wextra -o example monotonic_buffer_resource.cpp