整体架构分层
vbnet
┌────────────────────────────┐
│ Public API │ allocate<T>(), deallocate
├────────────────────────────┤
│ Pool Interface Layer │ 抽象分配语义
├────────────────────────────┤
│ Slab / Block Management │ 管理内存块
├────────────────────────────┤
│ Raw Memory Provider │ ::operator new / mmap
└────────────────────────────┘
第一层:Raw Memory Provider(最底层)
职责
-
向 OS / runtime 申请"大块内存"
-
不关心对象类型
-
不关心回收策略
代码实现
cpp
#include <cstddef>
#include <memory>
#include <new>
#include <span>
//提供原始内存
class RawBlock {
public:
RawBlock(std::size_t bytes,std::size_t alignmemt):size_(bytes),alignment_(alignmemt){
void* p = ::operator new(bytes, std::align_val_t{ alignmemt });//new一块内存
data_ = static_cast<std::byte*>(p);
}
//不允许拷贝
RawBlock(const RawBlock&) = delete;
RawBlock& operator=(const RawBlock&) = delete;
//允许移动
RawBlock(RawBlock&& other)noexcept :data_(other.data_),size_(other.size_),alignment_(other.alignment_){
other.data_ = nullptr;
other.size_ = 0;
}
RawBlock& operator=(RawBlock&& other)noexcept {
if (this != &other) {
release();
data_ = other.data_;
size_ = other.size_;
alignment_ = other.alignment_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
~RawBlock() {
release();
}
//返回视图,返回类型是span类型
std::span<std::byte> span()noexcept {
return { data_,size_ };
}
std::span<const std::byte> span()const noexcept {
return { data_,size_ };
}
std::size_t size()const noexcept { return size_; }
private:
//释放资源
void release()noexcept {
if (data_) {
::operator delete(data_, std::align_val_t{ alignment_ });
data_ = nullptr;
}
}
std::byte* data_{ nullptr };//原始字节块
std::size_t size_{ 0 };//字节数
std::size_t alignment_{ alignof(std::max_align_t) };//对其数
};
对齐的概念
对齐就是:
一个对象在内存中的起始地址,必须是某个值的整数倍
| 类型 | 常见对齐 |
|---|---|
char |
1 |
int |
4 |
double |
8 |
void* |
8(x64) |
__m256 |
32 |
如果一个 double 的地址不是 8 的倍数,在某些平台上:
-
要么 性能极差
-
要么 直接硬件异常
alignof 是什么?
alignof(T)含义是:
类型 T 的最小对齐要求(以字节为单位)
cpp
alignof(int) // 通常 4
alignof(double) // 通常 8
alignof(void*) // 通常 8 (x64)
std::max_align_t
这是一个 标准库定义的类型(比如MSVC定义如下):
cpp
using max_align_t = double; // most aligned type
标准规定:
std::max_align_t的对齐要求≥ 所有「基本类型」的对齐要求
也就是说:
cpp
alignof(std::max_align_t)
等价于:
当前平台上,malloc / operator new 能"自然"支持的最大对齐
普通 operator new(size) 只保证 alignof(std::max_align_t) 对齐
std::align_val_t(C++17)
这是一个 标签类型(tag type):
它的唯一作用是:
告诉
operator new:我这次要"过对齐"的内存
cpp
::operator new(size, std::align_val_t{ alignment });
语义是:
"请给我一块
size字节的内存,并且起始地址至少对齐到
alignment"
对应的 delete 必须匹配
cpp
::operator delete(ptr, std::align_val_t{ alignment });
普通 new vs 对齐 new
| 分配方式 | 对齐保证 | 代价 |
|---|---|---|
operator new(size) |
alignof(std::max_align_t) |
快 |
operator new(size, align_val_t) |
用户指定 | 慢一点 |
第二层:Block / Slab 管理层(核心)
这是内存池真正的"灵魂"。
基本思想:Slab Allocator
-
管理 N 个等大小 slot
-
O(1) allocate / deallocate
-
不产生碎片
-
不做类型构造(只管内存)
我们用 intrusive free list(最经典、最快)
cpp
slot:
+------------------+
| next_free (ptr) |
+------------------+
| user memory ... |
+------------------+
空闲 slot 的前几个字节存"下一个 free slot 的指针"
代码实现
cpp
#include <cassert>
#include <cstddef>
#include <new>
class SlabBlock {
public:
SlabBlock(std::size_t slot_size,
std::size_t slot_count,
std::size_t alignment) :
slot_size_(slot_size),
slot_count_(slot_count),
//构造原始内存
memory_(slot_size*slot_count,alignment){
//初始化free list
auto mem = memory_.span().data();
free_list_ = mem;
for (std::size_t i = 0; i < slot_count - 1; i++) {
auto current = mem + i * slot_size_;
auto next = mem + (i + 1) * slot_size_;
//一个内存块的前一部分,存放下一个位置的指针
*reinterpret_cast<void**>(current) = next;
}
//最后一个指向nullptr
auto last = mem + (slot_count - 1) * slot_size_;
*reinterpret_cast<void**>(last) = nullptr;
}
//分配内存
void* allocate() {
//没有内存了
if (!free_list_) {
return nullptr;
}
void* result = free_list_;
//拿出之前存的下一个位置的指针,更新当前free list
free_list_ = *reinterpret_cast<void**>(free_list_);
used_++;//使用数+1
return result;
}
//归还内存
void deallocate(void* p) {
assert(owns(p));
//p存放当前空位指针
*reinterpret_cast<void**>(p) = free_list_;
//free list更新
free_list_ = p;
--used_;
}
//判断归还的内存是否合法
bool owns(void* p)const noexcept {
auto begin = memory_.span().data();
auto end = begin + memory_.size();
return p >= begin && p < end;
}
bool full() const noexcept {
return used_ == slot_count_;
}
bool empty() const noexcept {
return used_ == 0;
}
private:
RawBlock memory_;//管理的整个内存
void* free_list_{ nullptr };
std::size_t used_{ 0 };
std::size_t slot_size_;
std::size_t slot_count_;
};
第三层:Pool Interface 接口层
这里我们选择多 size-class Pool Interface 实现,也就是有不同slot大小的Slab管理层的池化接口层,会选择合适大小的Slab里的slot进行分配
代码实现
cpp
//实际应用中可以看情况是否选择做成单例
class MemoryPool {
public:
//size classes
static constexpr std::array<std::size_t, 8> kSizeClasses = {
32,64,128,256,512,1024,2048,4096
};
//每个Slab槽的个数
static constexpr std::size_t kSlabSlotCount = 1024;
explicit MemoryPool(std::size_t alignment = alignof(std::max_align_t)) :alignment_(alignment) {
//每个SlabBlock的slot大小都不一样
for (std::size_t i = 0; i < kSizeClasses.size(); i++) {
slabs_[i] = std::make_unique<SlabBlock>(
kSizeClasses[i],
kSlabSlotCount,
alignment_
);
}
}
void* allocate(std::size_t size, std::size_t alignment = alignof(std::max_align_t)) {
//对齐过大Slab无法满足,直接fallback到系统分配
if (alignment > alignment_) {
return system_alloc(size, alignment);
}
//找符合大小的
if (auto idx = pick_slab(size)) {
if (void* p = slabs_[*idx]->allocate()) {
return p;
}
}
//slab没有合适的/或者满了
return system_alloc(size, alignment);
}
void deallocate(void* p, std::size_t size, std::size_t alignment = alignof(std::max_align_t)) {
if (!p) return;
//尝试归还到slab
if (auto idx = pick_slab(size)) {
auto& slab = slabs_[*idx];
if (slab->owns(p)) {
slab->deallocate(p);
return;
}
}
// fallback 内存
system_free(p, alignment);
}
// ===== 模板接口 =====
template<typename T>
T* allocate() {
void* mem = allocate(sizeof(T), alignof(T));
return static_cast<T*>(mem);
}
template<typename T>
void deallocate(T* p) {
deallocate(p, sizeof(T), alignof(T));
}
private:
//选择一个size-class
static std::optional<std::size_t> pick_slab(std::size_t size) {
for (std::size_t i = 0; i < kSizeClasses.size(); i++) {
//选择刚好大一点的
if (size <= kSizeClasses[i])
return i;
}
//太大了,没有符合的
return std::nullopt;
}
//系统的调用
static void* system_alloc(std::size_t size, std::size_t alignment) {
return ::operator new(size, std::align_val_t{ alignment });
}
static void system_free(void* p, std::size_t alignment) {
::operator delete(p, std::align_val_t{ alignment });
}
private:
std::array<std::unique_ptr<SlabBlock>, kSizeClasses.size()> slabs_;
std::size_t alignment_;
};
使用内存池
裸指针使用
cpp
void test_raw() {
MemoryPool pool;
struct Foo {
int a, b;
};
Foo* f = pool.allocate<Foo>();//用模板形式申请内存,当然现在的f仅仅是裸内存
new (f)Foo{ 1,2 };//使用placement new构造对象
f->~Foo();//析构对象
pool.deallocate(f);//内存回收
}
当然每次从内存池到释放都要四步还是有点多了,并且还得手动操作,这与现代C++思想也是背道而驰了。
使用智能指针
这里用unique_ptr作为演示
cpp
template<typename T>
struct PoolDeleter {
MemoryPool* pool;
void operator()(T* p)const noexcept {
if (!p) return;
p->~T();// 显式析构对象
pool->deallocate(p); // 归还内存到池
}
};
void test_unique_ptr() {
MemoryPool pool;
struct Foo {
int a, b;
~Foo() {
std::cout << "destroy Foo!\n";
}
};
Foo* raw = pool.allocate<Foo>();
new (raw) Foo{ 1,2 };
std::unique_ptr<Foo, PoolDeleter<Foo>> ptr{
raw,
PoolDeleter<Foo>{&pool}
};
}
这样释放就不用自己每次记住了。对于shared_ptr也是一个思路,只不过shared_ptr是传的是函数对象。
智能指针再次封装
当然申请部分也挺费劲,每次都要自己写placement new也挺烦的
cpp
template<typename T,typename...Args>
std::unique_ptr<T, PoolDeleter<T>> pool_make_unique(MemoryPool& pool, Args&&...args) {
T* mem = pool.allocate<T>();
if (!mem) throw std::bad_alloc{};
new (mem) T(std::forward<Args>(args)...);
return std::unique_ptr<T, PoolDeleter<T>>{mem, PoolDeleter<T>{&pool}};
}
void test_unique_ptr_template() {
MemoryPool pool;
struct Foo {
int a, b;
~Foo() {
std::cout << "destroy Foo!\n";
}
};
auto foo_ptr = pool_make_unique<Foo>(pool, 1, 2);
}
总体代码
方便直接copy。当然不保证完全正确,我也没大规模测试
cpp
#include<cstddef>
#include <cassert>
#include<memory>
#include<new>
#include<span>
#include <array>
#include<optional>
//提供原始内存
class RawBlock {
public:
RawBlock(std::size_t bytes,std::size_t alignmemt):size_(bytes),alignment_(alignmemt){
void* p = ::operator new(bytes, std::align_val_t{ alignmemt });//new一块内存
data_ = static_cast<std::byte*>(p);
}
//不允许拷贝
RawBlock(const RawBlock&) = delete;
RawBlock& operator=(const RawBlock&) = delete;
//允许移动
RawBlock(RawBlock&& other)noexcept :data_(other.data_),size_(other.size_),alignment_(other.alignment_){
other.data_ = nullptr;
other.size_ = 0;
}
RawBlock& operator=(RawBlock&& other)noexcept {
if (this != &other) {
release();
data_ = other.data_;
size_ = other.size_;
alignment_ = other.alignment_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
~RawBlock() {
release();
}
//返回视图,返回类型是span类型
std::span<std::byte> span()noexcept {
return { data_,size_ };
}
std::span<const std::byte> span()const noexcept {
return { data_,size_ };
}
std::size_t size()const noexcept { return size_; }
private:
//释放资源
void release()noexcept {
if (data_) {
::operator delete(data_, std::align_val_t{ alignment_ });
data_ = nullptr;
}
}
std::byte* data_{ nullptr };//原始字节块
std::size_t size_{ 0 };//字节数
std::size_t alignment_{ alignof(std::max_align_t) };//对其数
};
//
class SlabBlock {
public:
SlabBlock(std::size_t slot_size,
std::size_t slot_count,
std::size_t alignment)
: slot_count_(slot_count)
, slot_size_(compute_slot_size(slot_size, alignment))//内部保证slot_size的合理
, memory_(slot_size_* slot_count_,
std::max(alignment, alignof(void*)))//这里的alignment原理详见compute_slot_size
{
assert(slot_count_ > 0);
auto mem = memory_.span().data();
free_list_ = mem;//获取原始地址的指针
for (std::size_t i = 0; i + 1 < slot_count_; ++i) {
auto cur = mem + i * slot_size_;
auto next = mem + (i + 1) * slot_size_;
//把next的地址信息存入cur里内存里
std::memcpy(cur, &next, sizeof(next));
}
//最后一块地址,指针域存nullptr
auto last = mem + (slot_count_ - 1) * slot_size_;
std::byte* nullp = nullptr;
std::memcpy(last, &nullp, sizeof(nullp));
}
//分配内存
void* allocate() noexcept {
//没有空闲内存了
if (!free_list_) return nullptr;
//取出当前空闲内存
void* result = free_list_;
//更新freelist
free_list_ = load_next(free_list_);
++used_;
return result;
}
//归还内存
void deallocate(void* p) noexcept {
//要判断一下归还的对不对
assert(owns(p));
store_next(p, free_list_);
//更新freelist
free_list_ = p;
--used_;
}
bool owns(void* p) const noexcept {
auto begin = memory_.span().data();
auto end = begin + memory_.size();
auto bp = static_cast<std::byte*>(p);
//如果bp不在原始内存里,肯定不对
if (bp < begin || bp >= end) return false;
//而且bp指向的位置也必须是该slot开头,不然也是错误的
return (bp - begin) % slot_size_ == 0;
}
private:
//计算合法的slot_size
static std::size_t compute_slot_size(std::size_t slot,
std::size_t alignment) {
//因为每个slot的头部需要存一个void*,所以对齐也必须至少是sizeof指针
std::size_t min_align = std::max(alignment, alignof(void*));
//这也是同理,就算外面传进来slot小,也必须扩大到指针大小
slot = std::max(slot, sizeof(void*));
return align_up(slot, min_align);
}
static std::size_t align_up(std::size_t n, std::size_t align) {
//返回align的倍数,保证对齐
return ((n + align - 1) / align) * align;
}
//取当前slot里存的下一个slot的地址
static void* load_next(void* slot) noexcept {
void* next;
std::memcpy(&next, slot, sizeof(next));
return next;
}
//将下一个slot的地址指针next存入当前slot内存里
static void store_next(void* slot, void* next) noexcept {
std::memcpy(slot, &next, sizeof(next));
}
std::size_t slot_count_{ 0 };//slot个数
std::size_t slot_size_{ 0 };//每个slot的大小
RawBlock memory_;//原始内存
void* free_list_{ nullptr };//当前空闲slot
std::size_t used_{ 0 };
};
//实际应用中可以看情况是否选择做成单例
class MemoryPool {
private:
template<typename T>
struct PoolDeleter {
MemoryPool* pool;
void operator()(T* p)const noexcept {
if (!p) return;
p->~T();// 显式析构对象
pool->deallocate(p); // 归还内存到池
}
};
public:
//size classes
static constexpr std::array<std::size_t, 8> kSizeClasses = {
32,64,128,256,512,1024,2048,4096
};
//每个Slab槽的个数
static constexpr std::size_t kSlabSlotCount = 1024;
explicit MemoryPool(std::size_t alignment = alignof(std::max_align_t)) :alignment_(alignment) {
//每个SlabBlock的slot大小都不一样
for (std::size_t i = 0; i < kSizeClasses.size(); i++) {
slabs_[i] = std::make_unique<SlabBlock>(
kSizeClasses[i],
kSlabSlotCount,
alignment_
);
}
}
void* allocate(std::size_t size, std::size_t alignment = alignof(std::max_align_t)) {
//对齐过大Slab无法满足,直接fallback到系统分配
if (alignment > alignment_) {
return system_alloc(size, alignment);
}
//找符合大小的
if (auto idx = pick_slab(size)) {
if (void* p = slabs_[*idx]->allocate()) {
return p;
}
}
//slab没有合适的/或者满了
return system_alloc(size, alignment);
}
void deallocate(void* p, std::size_t size, std::size_t alignment = alignof(std::max_align_t)) {
if (!p) return;
//尝试归还到slab
if (auto idx = pick_slab(size)) {
auto& slab = slabs_[*idx];
if (slab->owns(p)) {
slab->deallocate(p);
return;
}
}
// fallback 内存
system_free(p, alignment);
}
// ===== 模板接口 =====
template<typename T>
T* allocate() {
void* mem = allocate(sizeof(T), alignof(T));
return static_cast<T*>(mem);
}
template<typename T>
void deallocate(T* p) {
deallocate(p, sizeof(T), alignof(T));
}
//===== 智能指针模板接口 =====
template<typename T, typename... Args>
std::unique_ptr<T, PoolDeleter<T>>
make_unique(Args&&... args) {
void* mem = allocate(sizeof(T), alignof(T));
if (!mem) {
throw std::bad_alloc();
}
T* obj = new (mem) T( std::forward<Args>(args)... );
return std::unique_ptr<T, PoolDeleter<T>>(
obj,
PoolDeleter<T>{ this }
);
}
private:
//选择一个size-class
static std::optional<std::size_t> pick_slab(std::size_t size) {
for (std::size_t i = 0; i < kSizeClasses.size(); i++) {
//选择刚好大一点的
if (size <= kSizeClasses[i])
return i;
}
//太大了,没有符合的
return std::nullopt;
}
//系统的调用
static void* system_alloc(std::size_t size, std::size_t alignment) {
return ::operator new(size, std::align_val_t{ alignment });
}
static void system_free(void* p, std::size_t alignment) {
::operator delete(p, std::align_val_t{ alignment });
}
private:
std::array<std::unique_ptr<SlabBlock>, kSizeClasses.size()> slabs_;
std::size_t alignment_;
};
注意事项!!!
这个内存池是非线程安全的,不能多线程并发申请内存。
真正多线程安全的内存池有点复杂,我还暂且不太会