内存池¶
1. 非线程安全内存池(对象池),实际场景也是线程私有的
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include "macros.h"
namespace Common {
template <typename T>
class MemPool final {
public:
explicit MemPool(std::size_t num_elems)
: store_(num_elems, {T(), true}) /* pre-allocation of vector storage. */ {
ASSERT(reinterpret_cast<const ObjectBlock*>(&(store_[0].object_)) ==
&(store_[0]),
"T object should be first member of ObjectBlock.");
}
/// Allocate a new object of type T, use placement new to initialize the
/// object, mark the block as in-use and return the object.
template <typename... Args>
T* allocate(Args... args) noexcept {
auto obj_block = &(store_[next_free_index_]);
#if !defined(NDEBUG)
ASSERT(obj_block->is_free_, "Expected free ObjectBlock at index:" +
std::to_string(next_free_index_));
#endif
T* ret = &(obj_block->object_);
ret = new (ret) T(args...); // placement new.
obj_block->is_free_ = false;
updateNextFreeIndex();
return ret;
}
/// Return the object back to the pool by marking the block as free again.
/// Destructor is not called for the object.
auto deallocate(const T* elem) noexcept {
const auto elem_index =
(reinterpret_cast<const ObjectBlock*>(elem) - &store_[0]);
#if !defined(NDEBUG)
ASSERT(elem_index >= 0 && static_cast<size_t>(elem_index) < store_.size(),
"Element being deallocated does not belong to this Memory pool.");
ASSERT(
!store_[elem_index].is_free_,
"Expected in-use ObjectBlock at index:" + std::to_string(elem_index));
#endif
store_[elem_index].is_free_ = true;
}
// Deleted default, copy & move constructors and assignment-operators.
MemPool() = delete;
MemPool(const MemPool&) = delete;
MemPool(const MemPool&&) = delete;
MemPool& operator=(const MemPool&) = delete;
MemPool& operator=(const MemPool&&) = delete;
private:
/// Find the next available free block to be used for the next allocation.
auto updateNextFreeIndex() noexcept {
const auto initial_free_index = next_free_index_;
while (!store_[next_free_index_].is_free_) {
++next_free_index_;
if (UNLIKELY(
next_free_index_ ==
store_.size())) { // hardware branch predictor should almost
// always predict this to be false any ways.
next_free_index_ = 0;
}
if (UNLIKELY(initial_free_index == next_free_index_)) {
#if !defined(NDEBUG)
ASSERT(initial_free_index != next_free_index_,
"Memory Pool out of space.");
#endif
}
}
}
/// It is better to have one vector of structs with two objects than two
/// vectors of one object. Consider how these are accessed and cache
/// performance.
struct ObjectBlock {
T object_;
bool is_free_ = true;
};
/// We could've chosen to use a std::array that would allocate the memory on
/// the stack instead of the heap. We would have to measure to see which one
/// yields better performance. It is good to have objects on the stack but
/// performance starts getting worse as the size of the pool increases.
std::vector<ObjectBlock> store_;
size_t next_free_index_ = 0;
};
} // namespace Common
优化版本如下
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include "macros.h"
namespace Common {
template <typename T>
class MemPool final {
public:
explicit MemPool(std::size_t num_elems)
: objects_(nullptr), next_free_index_(0), capacity_(num_elems) {
// Pre-allocate memory using standard allocation (avoiding new/delete)
objects_ =
static_cast<T*>(std::aligned_alloc(alignof(T), sizeof(T) * capacity_));
free_list_ = static_cast<std::size_t*>(std::aligned_alloc(
alignof(std::size_t), sizeof(std::size_t) * capacity_));
// Initialize free list - each slot points to the next one
for (std::size_t i = 0; i < capacity_; ++i) {
free_list_[i] = i + 1;
}
free_list_[capacity_ - 1] =
kInvalidIndex; // Last element points to invalid
}
~MemPool() {
// Explicitly call destructors for any non-freed objects
for (std::size_t i = 0; i < capacity_; ++i) {
if (free_list_[i] == kInvalidIndex) { // Object is allocated
objects_[i].~T();
}
}
// Free pre-allocated memory
std::free(objects_);
std::free(free_list_);
}
/// Allocate a new object of type T, use placement new to initialize the
/// object, mark the block as in-use and return the object.
template <typename... Args>
T* allocate(Args... args) noexcept {
if (UNLIKELY(next_free_index_ == kInvalidIndex)) {
return nullptr; // Pool exhausted
}
const auto index = next_free_index_;
next_free_index_ = free_list_[index]; // Move to next free slot
T* ret = &(objects_[index]);
new (ret) T(std::forward<Args>(args)...); // placement new
free_list_[index] = kInvalidIndex; // Mark as allocated
return ret;
}
/// Return the object back to the pool by marking the block as free again.
/// Destructor is not called for the object - lazy deallocation for
/// performance.
auto deallocate(const T* elem) noexcept {
const auto elem_index = static_cast<std::size_t>(elem - objects_);
if (LIKELY(elem_index < capacity_)) {
// Instead of calling destructor, just add back to free list
free_list_[elem_index] = next_free_index_;
next_free_index_ = elem_index;
}
}
// Deleted default, copy & move constructors and assignment-operators.
MemPool() = delete;
MemPool(const MemPool&) = delete;
MemPool(const MemPool&&) = delete;
MemPool& operator=(const MemPool&) = delete;
MemPool& operator=(const MemPool&&) = delete;
private:
static constexpr std::size_t kInvalidIndex = static_cast<std::size_t>(-1);
T* objects_; // Pre-allocated array of objects
std::size_t* free_list_; // Free list - each slot points to the next free
// slot, or kInvalidIndex if allocated
std::size_t next_free_index_; // Head of free list
std::size_t capacity_; // Total capacity of the pool
};
} // namespace Common
2. 内存调优
#include <malloc.h>
#include <sys/mman.h>
#include <unistd.h>
#include <iostream>
void configure_memory_behavior() {
// 禁用大块内存的 mmap 分配:所有 malloc 请求通过堆(sbrk)满足
if (mallopt(M_MMAP_MAX, 0) != 1) {
std::cerr << "Warning: Failed to set M_MMAP_MAX. Error: "
<< std::strerror(errno) << "\n";
}
// 禁用堆自动修剪:防止 free 后调用 sbrk 归还内存,避免系统调用开销和延迟波动
if (mallopt(M_TRIM_THRESHOLD, -1) != 1) {
std::cerr << "Warning: Failed to set M_TRIM_THRESHOLD. Error: "
<< std::strerror(errno) << "\n";
}
// 强制单arena:所有线程共享主堆,避免thread arena的mmap扩展和带来的不确定性
if (mallopt(M_ARENA_MAX, 1) != 1) {
std::cerr << "Warning: Failed to set M_ARENA_MAX. Error: "
<< std::strerror(errno) << "\n";
}
// 固定mmap阈值:确保大小块划分策略稳定(即使已禁用mmap)
if (mallopt(M_MMAP_THRESHOLD, 131072) != 1) {
std::cerr << "Warning: Failed to set M_MMAP_THRESHOLD. Error: "
<< std::strerror(errno) << "\n";
}
// 锁定当前进程中所有已映射的页面 和 锁定未来所有将要映射的页面
if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
std::cerr << "Warning: mlockall failed. Check permissions (CAP_IPC_LOCK) "
"and ulimit.\n";
}
}
# 临时禁用
sudo swapoff -a
#永久禁用,注释掉所有包含 swap 的行
sudo vim /etc/fstab
# 临时设置
sudo sysctl vm.swappiness=0
# 永久设置
echo "vm.swappiness=0" | sudo tee /etc/sysctl.d/99-disable-swap.conf
sudo sysctl -p
# 程序中使用mlockall()也是强制禁用,是针对整个进程的
# 预防系统的 memlock 限制过低,以下命令检查。
ulimit -l
# 临时提升限制 (例如到1GB)
sudo ulimit -l 1048576
# 注意: sudo提升的ulimit只对该sudo命令内的程序生效,需要以一种能传递给子进程的方式运行
# 更可靠的方式是修改配置文件或以root身份运行
# 永久修改
编辑 /etc/security/limits.conf 文件,为用户添加 memlock 的 unlimited 权限,然后重新登录。
# Allow the 'trader' user to lock an unlimited amount of memory
trader soft memlock unlimited
trader hard memlock unlimited
# 设置 vm.swappiness=0 并由应用调用 mlockall()。
# 临时设置
sudo sysctl vm.swappiness=0
# 永久设置,在 /etc/sysctl.d/ 目录下创建一个新的配置文件。
sudo vim /etc/sysctl.d/custom-latency.conf
添加vm.swappiness=0
sudo sysctl -p
# 通过以上设置可以无需下面获取权限来执行了
# mlock 和 mlockall 调用需要sudo权限
# 仅授予程序锁定内存的权限
sudo setcap cap_ipc_lock=+ep ./arena_test
# 完整权限
sudo ./arena_test
// 调整glibc分配器以锁定内存中的页,并防止将它们释放到操作系统,这些技术中的一些或全部可能已经集成到
// jemalloc、tcmalloc 或 mimalloc 等内存分配库中
// 将 M_MMAP_MAX 设置为 0 会禁用底层 mmap 系统调用用于大分配 -
// 这是必要的,因为当库尝试将 mmap 过的段释放回操作系统时,mlockall 可能会被
// munmap 撤销,从而挫败我们的努力。 将 M_TRIM_THRESHOLD 设置为 -1 可以防止
// glibc 在调用 free 后将内存返回给操作系统。正如之前所说,此选项对 mmap
// 过的段没有影响。 最后,将 M_ARENA_MAX 设置为 1 可以防止 glibc 通过 mmap
// 分配多个 arena 以容纳多个内核。请记住,后者会妨碍 glibc
// 分配器多线程可扩展性特性。 结合起来,这些设置将 glibc
// 强制转换为堆分配,直到应用程序结束才会将内存释放回操作系统。该进程生成的任何线程的堆栈也将被预处理并锁定。这种技术的缺点是它减少了系统上其他进程可用的内存量。
#include <malloc.h>
#include <sys/mman.h>
mallopt(M_MMAP_MAX, 0);
mallopt(M_TRIM_THRESHOLD, -1);
mallopt(M_ARENA_MAX, 1);
mlockall(MCL_CURRENT | MCL_FUTURE);
char* mem = malloc(size);
for (int i = 0; i < size; i += sysconf(_SC_PAGESIZE)) mem[i] = 0;
//...
free(mem);
// 如何检测多线程应用程序中的 TLB 驱逐?一种简单的方法是检查 /proc/interrupts 中的 TLB 行。
// 一种检测运行时连续 TLB 中断的有用方法是在查看此文件时使用 watch 命令
// 运行 watch -n5 -d 'grep TLB /proc/interrupts', 其中 -n 5 选项每 5 秒刷新视图,而 -d 则突出显示每次刷新输出之间的差异。
CPU0 CPU1 CPU2 CPU3
...
NMI: 0 0 0 0 Non-maskable interrupts
LOC: 552219 1010298 2272333 3179890 Local timer interrupts
SPU: 0 0 0 0 Spurious interrupts
...
IWI: 0 0 0 0 IRQ work interrupts
RTR: 7 0 0 0 APIC ICR read retries
RES: 18708 9550 771 528 Rescheduling interrupts
CAL: 711 934 1312 1261 Function call interrupts
TLB: 4493 6108 73789 5014 TLB shootdowns
// 注意其他内核数量级上的差异。在这种情况下,这种行为的罪魁祸首是 Linux 内核的一个名为自动 NUMA 平衡的功能,可以通过 sysctl -w numa_balancing=0 轻松禁用。
// 防止 TLB 驱逐需要限制对共享进程地址空间进行的更新次数。
// 在源代码层面,应该避免运行时执行这些系统调用,即 munmap、mprotect 和 madvise。
// 在操作系统层面,禁用内核功能,这些功能会因其功能而导致 TLB 驱逐,例如透明大页和自动 NUMA 平衡。
3. 合理选择数据结构
根据实际需求选择合适的数据结构,避免使用会导致频繁内存分配和释放的数据结构。例如,如果需要频繁插入和删除元素,可以考虑使用std::list而不是std::vector。
栈分配:对于大小固定的、生命周期短暂的数据,直接在栈上创建 std::array。大小必须在编译时确定。
4.pinned_arena+allocator(STL动态内存容器内存分配器)
// pinned_arena.h
#pragma once
#include <malloc.h>
#include <sys/mman.h>
#include <unistd.h>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
// 分支预测和优化宏
#if defined(__GNUC__) || defined(__clang__)
#define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
#define ALWAYS_INLINE inline __attribute__((always_inline))
#define HOT_PATH [[gnu::hot]]
#define COLD_PATH [[gnu::cold]]
#else
#define LIKELY(x) (x)
#define UNLIKELY(x) (x)
#define ALWAYS_INLINE inline
#define HOT_PATH
#define COLD_PATH
#endif
#define CACHELINE_ALIGN alignas(64)
#define CACHELINE_SIZE 64
namespace Common {
// 编译期内存对齐计算
template <size_t N>
constexpr size_t next_power_of_two() {
static_assert(N > 0, "Size must be positive");
size_t value = 1;
while (value < N) value <<= 1;
return value;
}
// 固定内存竞技场
class PinnedArena {
private:
// 内存块结构
struct CACHELINE_ALIGN MemoryBlock {
size_t used;
size_t capacity;
bool is_active;
uint32_t next_block; // 使用索引而非指针,提高缓存局部性
char padding[40]; // 填充到64字节避免伪共享
ALWAYS_INLINE char* data() { return reinterpret_cast<char*>(this + 1); }
ALWAYS_INLINE const char* data() const {
return reinterpret_cast<const char*>(this + 1);
}
static constexpr size_t header_size = sizeof(MemoryBlock);
static constexpr size_t min_capacity = 4096 - header_size;
};
// 预分配的连续内存区域
struct CACHELINE_ALIGN ArenaMemory {
void* raw_memory;
size_t total_size;
size_t block_size;
uint32_t num_blocks;
bool is_locked;
MemoryBlock* blocks; // 连续的内存块数组
char* data_start; // 连续的数据区域
};
ArenaMemory arena_mem_;
uint32_t current_block_idx_;
uint32_t active_blocks_;
const bool allow_fallback_;
const bool use_mlock_;
// 禁止拷贝
PinnedArena(const PinnedArena&) = delete;
PinnedArena& operator=(const PinnedArena&) = delete;
public:
explicit PinnedArena(size_t block_size = 64 * 1024,
bool allow_fallback = false, bool use_mlock = true,
uint32_t prealloc_blocks = 16) // 预分配块数
: allow_fallback_(allow_fallback),
use_mlock_(use_mlock),
current_block_idx_(0),
active_blocks_(1) {
// 初始化连续内存区域
initialize_arena_memory(block_size, prealloc_blocks);
// 预取第一个内存块到缓存
if (LIKELY(arena_mem_.blocks != nullptr)) {
prefetch_memory(arena_mem_.blocks[0].data(),
std::min(block_size, size_t(4096)));
}
}
~PinnedArena() { destroy_arena_memory(); }
// 移动语义支持
PinnedArena(PinnedArena&& other) noexcept
: arena_mem_(other.arena_mem_),
current_block_idx_(other.current_block_idx_),
active_blocks_(other.active_blocks_),
allow_fallback_(other.allow_fallback_),
use_mlock_(other.use_mlock_) {
other.arena_mem_.raw_memory = nullptr;
other.arena_mem_.blocks = nullptr;
other.arena_mem_.data_start = nullptr;
}
PinnedArena& operator=(PinnedArena&& other) noexcept {
if (LIKELY(this != &other)) {
destroy_arena_memory();
arena_mem_ = other.arena_mem_;
current_block_idx_ = other.current_block_idx_;
active_blocks_ = other.active_blocks_;
other.arena_mem_.raw_memory = nullptr;
other.arena_mem_.blocks = nullptr;
other.arena_mem_.data_start = nullptr;
}
return *this;
}
// 核心分配函数 - 热路径优化
HOT_PATH ALWAYS_INLINE void* allocate(
size_t size, size_t alignment = alignof(std::max_align_t)) {
// 快速路径:在当前活跃块分配
MemoryBlock* current_block = &arena_mem_.blocks[current_block_idx_];
if (LIKELY(current_block->is_active && size <= arena_mem_.block_size / 4)) {
char* ptr = current_block->data() + current_block->used;
uintptr_t aligned_ptr =
(reinterpret_cast<uintptr_t>(ptr) + alignment - 1) & ~(alignment - 1);
size_t adjustment = aligned_ptr - reinterpret_cast<uintptr_t>(ptr);
size_t total_size = size + adjustment;
if (LIKELY(current_block->used + total_size <= current_block->capacity)) {
current_block->used += total_size;
// 预取即将使用的内存
prefetch_memory(reinterpret_cast<void*>(aligned_ptr), size);
return reinterpret_cast<void*>(aligned_ptr);
}
}
// 慢速路径
return allocate_slow_path(size, alignment);
}
// 批量数组分配 - 缓存友好
template <typename T>
HOT_PATH ALWAYS_INLINE T* allocate_array(size_t count) {
static_assert(std::is_trivial_v<T>,
"Only trivial types supported for array allocation");
constexpr size_t alignment = alignof(T);
const size_t total_size = count * sizeof(T);
MemoryBlock* current_block = &arena_mem_.blocks[current_block_idx_];
if (LIKELY(current_block->is_active)) {
char* ptr = current_block->data() + current_block->used;
uintptr_t aligned_ptr =
(reinterpret_cast<uintptr_t>(ptr) + alignment - 1) & ~(alignment - 1);
size_t adjustment = aligned_ptr - reinterpret_cast<uintptr_t>(ptr);
size_t total_needed = total_size + adjustment;
if (LIKELY(current_block->used + total_needed <=
current_block->capacity)) {
current_block->used += total_needed;
T* result = reinterpret_cast<T*>(aligned_ptr);
// 多级预取优化
prefetch_array(result, count);
return result;
}
}
return reinterpret_cast<T*>(allocate_slow_path(total_size, alignment));
}
// 重置竞技场
HOT_PATH ALWAYS_INLINE void reset() noexcept {
// 重置所有活跃块 - 连续内存访问,缓存友好
for (uint32_t i = 0; i < active_blocks_; ++i) {
arena_mem_.blocks[i].used = 0;
// 预取每个块的数据区域
if (LIKELY(arena_mem_.blocks[i].is_active &&
arena_mem_.blocks[i].capacity > 0)) {
prefetch_memory(arena_mem_.blocks[i].data(),
std::min(arena_mem_.blocks[i].capacity, size_t(4096)));
}
}
current_block_idx_ = 0;
}
// 清空所有内存块
void clear() noexcept {
reset();
// 不清除预分配的内存区域,保持mlock状态
}
// 统计信息
size_t memory_usage() const noexcept { return arena_mem_.total_size; }
size_t block_count() const noexcept { return active_blocks_; }
private:
// 初始化连续内存区域
void initialize_arena_memory(size_t block_size, uint32_t num_blocks) {
if (UNLIKELY(num_blocks == 0)) {
return;
}
// 计算总内存大小
size_t block_total_size = MemoryBlock::header_size + block_size;
size_t total_memory_size = block_total_size * num_blocks;
// 页对齐
long page_size = sysconf(_SC_PAGESIZE);
if (UNLIKELY(page_size == -1)) {
page_size = 4096;
}
size_t aligned_total_size =
((total_memory_size + page_size - 1) / page_size) * page_size;
// 分配连续内存
void* raw_memory = nullptr;
if (UNLIKELY(posix_memalign(&raw_memory, page_size, aligned_total_size) !=
0)) {
return;
}
// 内存锁定
if (LIKELY(use_mlock_)) {
if (UNLIKELY(mlock(raw_memory, aligned_total_size) == -1)) {
// 简化错误处理以减少系统调用
}
}
// Pre-faulting: 触发所有缺页中断
memset(raw_memory, 0, aligned_total_size);
// 设置内存区域信息
arena_mem_.raw_memory = raw_memory;
arena_mem_.total_size = aligned_total_size;
arena_mem_.block_size = block_size;
arena_mem_.num_blocks = num_blocks;
arena_mem_.is_locked = use_mlock_;
// 初始化内存块数组
arena_mem_.blocks = static_cast<MemoryBlock*>(raw_memory);
arena_mem_.data_start = reinterpret_cast<char*>(arena_mem_.blocks) +
(MemoryBlock::header_size * num_blocks);
// 初始化每个内存块
for (uint32_t i = 0; i < num_blocks; ++i) {
MemoryBlock* block = &arena_mem_.blocks[i];
block->used = 0;
block->capacity = block_size;
block->is_active = (i == 0); // 只有第一个块初始激活
block->next_block = (i + 1 < num_blocks) ? (i + 1) : 0;
}
active_blocks_ = 1;
}
// 销毁内存区域
void destroy_arena_memory() noexcept {
if (UNLIKELY(arena_mem_.raw_memory == nullptr)) return;
if (arena_mem_.is_locked) {
munlock(arena_mem_.raw_memory, arena_mem_.total_size);
}
free(arena_mem_.raw_memory);
arena_mem_.raw_memory = nullptr;
arena_mem_.blocks = nullptr;
arena_mem_.data_start = nullptr;
}
// 慢速分配路径
COLD_PATH
void* allocate_slow_path(size_t size, size_t alignment) {
// 尝试在当前块分配(可能由于对齐要求失败)
MemoryBlock* current_block = &arena_mem_.blocks[current_block_idx_];
if (LIKELY(current_block->is_active)) {
char* ptr = current_block->data() + current_block->used;
uintptr_t aligned_ptr =
(reinterpret_cast<uintptr_t>(ptr) + alignment - 1) & ~(alignment - 1);
size_t adjustment = aligned_ptr - reinterpret_cast<uintptr_t>(ptr);
size_t total_size = size + adjustment;
if (current_block->used + total_size <= current_block->capacity) {
current_block->used += total_size;
prefetch_memory(reinterpret_cast<void*>(aligned_ptr), size);
return reinterpret_cast<void*>(aligned_ptr);
}
}
// 尝试激活新块
if (LIKELY(size <= arena_mem_.block_size &&
active_blocks_ < arena_mem_.num_blocks)) {
// 激活下一个块
uint32_t next_idx = current_block_idx_ + 1;
if (LIKELY(next_idx < arena_mem_.num_blocks)) {
MemoryBlock* next_block = &arena_mem_.blocks[next_idx];
next_block->is_active = true;
next_block->used = 0;
current_block_idx_ = next_idx;
active_blocks_++;
char* ptr = next_block->data();
uintptr_t aligned_ptr =
(reinterpret_cast<uintptr_t>(ptr) + alignment - 1) &
~(alignment - 1);
size_t adjustment = aligned_ptr - reinterpret_cast<uintptr_t>(ptr);
if (LIKELY(size + adjustment <= next_block->capacity)) {
next_block->used = size + adjustment;
prefetch_memory(reinterpret_cast<void*>(aligned_ptr), size);
return reinterpret_cast<void*>(aligned_ptr);
}
}
}
// 大对象或回退分配
if (UNLIKELY(allow_fallback_)) {
// 使用更轻量级的分配方式
void* ptr = malloc(size);
if (UNLIKELY(ptr == nullptr)) {
return nullptr;
}
prefetch_memory(ptr, size);
return ptr;
}
return nullptr;
}
// 内存预取优化
ALWAYS_INLINE void prefetch_memory(void* addr, size_t size) const {
#if defined(__GNUC__) || defined(__clang__)
char* ptr = static_cast<char*>(addr);
// 预取多个缓存行,限制范围避免过度预取
for (size_t i = 0; i < size && i < 1024; i += CACHELINE_SIZE) {
__builtin_prefetch(ptr + i, 1, 3); // 预写,高局部性
}
#endif
}
// 数组预取优化
template <typename T>
ALWAYS_INLINE void prefetch_array(T* array, size_t count) const {
if (UNLIKELY(count == 0)) return;
#if defined(__GNUC__) || defined(__clang__)
// 预取前几个元素
__builtin_prefetch(array, 1, 3);
// 对于大数组,预取更多元素
if (LIKELY(count > 8)) {
__builtin_prefetch(array + 8, 1, 2);
if (count > 16) {
__builtin_prefetch(array + 16, 1, 1);
}
}
#endif
}
};
// STL兼容的低延迟分配器
template <typename T>
class LowLatencyAllocator {
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using propagate_on_container_copy_assignment = std::false_type;
using propagate_on_container_move_assignment = std::true_type;
using propagate_on_container_swap = std::true_type;
using is_always_equal = std::false_type;
template <typename U>
struct rebind {
using other = LowLatencyAllocator<U>;
};
explicit LowLatencyAllocator(PinnedArena& arena) noexcept : arena_(&arena) {}
LowLatencyAllocator(const LowLatencyAllocator& other) noexcept = default;
template <typename U>
LowLatencyAllocator(const LowLatencyAllocator<U>& other) noexcept
: arena_(other.arena_) {}
// 内存分配
HOT_PATH ALWAYS_INLINE pointer allocate(size_type n) {
// 小数组快速路径
if (LIKELY(n <= 16)) {
return arena_->allocate_array<T>(n);
}
// 大数组路径
void* ptr = arena_->allocate(n * sizeof(T), alignof(T));
return static_cast<pointer>(ptr);
}
// 内存释放 - 无操作(由arena统一管理)
void deallocate(pointer p, size_type n) noexcept {
(void)p;
(void)n;
}
// 构造对象
template <typename U, typename... Args>
void construct(U* p, Args&&... args) {
::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
}
// 销毁对象
template <typename U>
void destroy(U* p) {
p->~U();
}
PinnedArena* arena() const noexcept { return arena_; }
template <typename U>
bool operator==(const LowLatencyAllocator<U>& other) const noexcept {
return arena_ == other.arena_;
}
template <typename U>
bool operator!=(const LowLatencyAllocator<U>& other) const noexcept {
return arena_ != other.arena_;
}
private:
PinnedArena* arena_;
};
// 便捷容器创建函数
template <typename T, typename... Args>
auto make_pinned_vector(PinnedArena& arena, Args&&... args) {
return std::vector<T, LowLatencyAllocator<T>>(std::forward<Args>(args)...,
LowLatencyAllocator<T>(arena));
}
} // namespace Common
#include <iostream>
#include <vector>
#include "pinned_arena.h"
using namespace Common;
int main() {
PinnedArena arena(64 * 1024, false, true, 4);
std::vector<int, LowLatencyAllocator<int>> int_vec(
LowLatencyAllocator<int>(arena));
for (int i = 0; i < 5; ++i) {
int_vec.push_back(i * 10); // 0, 10, 20, 30, 40
}
std::cout << "int_vec 内容: ";
for (const auto& val : int_vec) {
std::cout << val << " ";
}
std::cout << "\nint_vec 内存地址: " << static_cast<void*>(int_vec.data())
<< std::endl;
auto str_vec = make_pinned_vector<std::string>(arena);
str_vec.emplace_back("hello");
str_vec.emplace_back("pinned");
str_vec.emplace_back("arena");
std::cout << "str_vec 内容: ";
for (const auto& s : str_vec) {
std::cout << s << " ";
}
std::cout << "\nstr_vec 内存地址: " << static_cast<void*>(str_vec.data())
<< std::endl;
arena.reset();
std::vector<double, LowLatencyAllocator<double>> double_vec(
LowLatencyAllocator<double>(arena));
double_vec.push_back(3.14);
double_vec.push_back(6.28);
std::cout << "新double_vec 内存地址: "
<< static_cast<void*>(double_vec.data()) << std::endl;
std::cout << "double_vec 内容: " << double_vec[0] << ", " << double_vec[1]
<< std::endl;
std::cout << "\n=== 最终内存统计 ===" << std::endl;
std::cout << "竞技场总内存: " << arena.memory_usage() << " 字节" << std::endl;
std::cout << "活跃内存块数量: " << arena.block_count() << std::endl;
return 0;
}
5. 使用多态内存分配器解决 std::vector 类型不匹配问题
std::vector<int>和std::vector<int, MyAlloc>是完全不同的类型,因为分配器是容器类型的一部分。C++标准库中的容器将分配器作为模板参数,因此不同的分配器会导致不同的类型。
C++17引入了std::pmr(polymorphic memory resource)命名空间,提供了一种多态分配器机制,可以解决这个问题。
std::pmr::polymorphic_allocator:一个通用分配器类型,不将分配器作为类型的一部分std::pmr::memory_resource:所有内存资源的基类- 所有
std::pmr容器(如std::pmr::vector)都使用polymorphic_allocator,通过内存资源指针管理内存
#include <array>
#include <memory_resource>
#include <vector>
// 使用std::pmr::vector和多态分配器
void process(std::pmr::vector<int>& buffer) {
// 处理buffer
}
auto some_func() {
// 创建栈上缓冲区
auto buffer = std::array<std::byte, 512>{};
// 创建内存资源,使用monotonic_buffer_resource(类似您的Arena)
auto resource = std::pmr::monotonic_buffer_resource{
buffer.data(), buffer.size(), std::pmr::new_delete_resource()};
// 创建使用该内存资源的vector
auto vec = std::pmr::vector<int>{&resource};
// 添加元素
vec.push_back(42);
vec.push_back(7);
// 传递给process函数(现在类型匹配)
process(vec);
}
std::pmr::vector<int>使用std::pmr::polymorphic_allocator,它不将分配器作为类型的一部分std::pmr::vector通过内存资源指针(std::pmr::memory_resource*)来管理内存- 所有
std::pmr容器都使用相同的分配器类型,因此std::pmr::vector<int>和std::pmr::vector<int>是兼容的
常用内存资源
C++标准库提供了几种内置内存资源,可以满足不同场景需求:
-
std::pmr::monotonic_buffer_resource: -
适用于生命周期短的对象
- 内存在资源销毁时才释放
-
非常快速的分配
-
std::pmr::unsynchronized_pool_resource: -
使用内存池(slabs)管理内存
- 避免内存碎片
- 适用于多种大小的对象
-
非线程安全
-
std::pmr::synchronized_pool_resource: -
unsynchronized_pool_resource的线程安全版本
实现自定义内存资源
#include <memory_resource>
class PrintingResource : public std::pmr::memory_resource {
public:
PrintingResource() : res_{std::pmr::get_default_resource()} {}
private:
void* do_allocate(std::size_t bytes, std::size_t alignment) override {
std::cout << "allocate: " << bytes << '\n';
return res_->allocate(bytes, alignment);
}
void do_deallocate(void* p, std::size_t bytes,
std::size_t alignment) override {
std::cout << "deallocate: " << bytes << '\n';
res_->deallocate(p, bytes, alignment);
}
bool do_is_equal(
const std::pmr::memory_resource& other) const noexcept override {
return this == &other;
}
std::pmr::memory_resource* res_;
};
auto res = PrintingResource{};
auto vec = std::pmr::vector<int>{&res};
vec.emplace_back(1);
vec.emplace_back(2);
注意事项
- 内存资源生命周期:确保内存资源的生命周期足够长,以覆盖所有使用它的容器。以下代码会导致未定义行为:
auto create_vec() -> std::pmr::vector<int> {
auto resource = PrintingResource{};
auto vec = std::pmr::vector<int>{&resource}; // 传递原始指针
return vec; // 资源在函数结束时销毁,vec变成无效
}
auto vec = create_vec();
vec.emplace_back(1); // 未定义行为
- 默认内存资源:可以使用
std::pmr::get_default_resource()获取默认内存资源,或通过std::pmr::set_default_resource()设置。