当C++项目里做了大量的动态内存分配与释放,可能会导致内存碎片,使系统性能降低。当动态内存分配的开销变得不容忽视时,一种解决办法是一次从操作系统分配一块大的静态内存作为内存池进行手动管理,堆对象内存分配时从内存池中分配一块类对象大小的内存,释放时并不实际将内存归还给操作系统,而是交给自定义的内存管理模块处理。本文介绍基于std::shared_ptr自定义allocator引入内存池的方法。
公众号:七昂的技术之旅
尝试重写new和delete运算符
项目中大量使用std::shared_ptr且与多个模块耦合, 如果直接将 std::shared_ptr
重构为手动管理裸指针的实现,改动量太大,而且可能会带来不可预料的问题。于是尝试了重写new和delete运算符并添加了打印,发现 std::shared_ptr
的创建并不会直接调用 new
和 delete
, 原因在于std::shared_ptr
有自己的内存分配机制。
std::allocate_shared
于是,想到了STL的一大组件 Allocator
。C++提供了 std::alloc_shared
函数,可以自定义std::shared_ptr
的内存分配方式,其定义如下:
std::allocate_shared<T>(custom_alloc, std::forward<Args>(args)...);
仅需传入自定义分配器allocator和T的构造参数列表。
实际上, std::make_shared
就是对以上函数进行了封装,使用了默认的分配器。
MemoryPool的使用
内存池直接采用了相关开源项目的定义:
可以选用
https://github.com/DevShiftTeam/AppShift-MemoryPool
或
Fast Efficient Fixed-Sized Memory Pool
MemoryPoolManager 管理内存池的类
- 分配内存池
内存池需要拥有静态生命周期,因此将内存池管理类 MemoryPoolManager
设计为全局单例模式实现,定义Alloc()
和 Free()
方法,实现了内存池与自定义分配器解耦。
- 引入自旋锁实现线程安全
由于使用的相关开源内存池不是线程安全的,因此引入了自旋锁在内存池做内存分配和释放时加锁。自旋锁采用了以下文章中的实现:
Correctly implementing a spinlock in C++
MemoryPoolManager
的完整实现如下:
class MemoryPoolManager {
public:
static MemoryPoolManager& GetInstance();
void* Alloc(size_t sz);
void Free(void* p);
~MemoryPoolManager();
private:
MemoryPoolManager();
MemoryPoolManager(const MemoryPoolManager