本项目为C++仿TCmalloc高并发内存池的实现,目前完成了定长内存池的按页资源申请,在单线程进程下实现了高频率申请资源的高效性与稳定性,并制定了资源回收复用机制
目录
ObjectPool.hpp
cpp
#pragma once
#include <iostream>
#include <vector>
#include <ctime>
#include <new>
// --------------------------------------------------------------------
#ifdef _WIN32
#include<windows.h>
#else
//
#endif
// 直接去堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
// linux下brk mmap等
#endif
if (ptr == nullptr)
throw std::bad_alloc();
return ptr;
}
// --------------------------------------------------------------------
template<typename T>
class ObjectPool
{
public:
T* New()
{
T* obj = nullptr;
size_t objsize = 0;
//优先把换回来的内存块对象,重复利用
if (_freelist_head)
{
void* next = *(void**)_freelist_head;
obj = (T*)_freelist_head;
_freelist_head = next;
}
else {
// 保证每一个内存块都足够存放一个void*指针,用于回收连接逻辑
objsize = sizeof(T) > sizeof(void*) ? sizeof(T) : sizeof(void*);
// 每次new一块内存,当剩余内存供应不足申请一个对象 T 时则重新申请
if (_ByteRemaining < objsize)
{
_ByteRemaining = 128 * 1024;
// ( 128 * 1024 ) / ( 8 * 1024 ) = 8, 按8kb/页开辟8页
_memory = (char*)SystemAlloc(_ByteRemaining >> 13);
if (_memory == nullptr)
{
throw std::bad_alloc();
}
}
obj = (T*)_memory;
_memory += objsize;
_ByteRemaining -= objsize;
}
// 定位new,显式调用T的构造函数初始化
new (obj) T;
return obj;
}
//不用STL中list的原因是下面方法不管什么类型大小的内存块都可以连接起来,list做不到
void Delete(T* obj)
{
// 显式调用析构函数,清除对象数据
// 但是这一块空间没有释放!而是清干净了回收到链表等待复用
obj->~T();
/*
不确定是在32位还是64位下执行
32位下存空指针4位字节,64位下8字节,而int始终占4字节,下面方法失效
*(int*)obj = nullptr;
*/
//头插操作无需关心头指针是否为空
*(void**)obj = _freelist_head;
_freelist_head = obj;
}
private:
char* _memory = nullptr; // 指向大块内存池的指针
void* _freelist_head = nullptr; // 连接回收的内存的链表的头指针
size_t _ByteRemaining = 0; // 内存池剩余的字节数
};
TestPool.hpp
共10轮测试,每轮申请释放1000000次,release下执行
cpp
#pragma once
#include "ObjectPool.hpp"
struct TreeNode
{
int _val;
TreeNode* _left;
TreeNode* _right;
TreeNode()
:_val(0)
, _left(nullptr)
, _right(nullptr)
{}
};
void TestObjectPool()
{
// 申请释放的轮次
const size_t Rounds = 10;
// 每轮申请释放多少次
const size_t N = 1000000;
std::vector<TreeNode*> v1;
//提前申请空间,防止扩容开销影响测试
v1.reserve(N);
clock_t begin1 = clock();
for (size_t j = 0; j < Rounds; ++j)
{
for (int i = 0; i < N; ++i)
{
v1.push_back(new TreeNode);
}
for (int i = 0; i < N; ++i)
{
delete v1[i];
}
v1.clear();
}
clock_t end1 = clock();
std::vector<TreeNode*> v2;
v2.reserve(N);
ObjectPool<TreeNode> TNPool;
clock_t begin2 = clock();
for (size_t j = 0; j < Rounds; ++j)
{
for (int i = 0; i < N; ++i)
{
v2.push_back(TNPool.New());
}
for (int i = 0; i < N; ++i)
{
TNPool.Delete(v2[i]);
}
v2.clear();
}
clock_t end2 = clock();
std::cout << "new cost time:" << end1 - begin1 << std::endl;
std::cout << "object pool cost time:" << end2 - begin2 << std::endl;
}
test.cpp
cpp
#include "ObjectPool.hpp"
#include "TestPool.hpp"
int main()
{
TestObjectPool();
return 0;
}
如下为时间开销,在百万级别高频申请内存下,内存池的效率远高于传统内存申请

已存在优点
1.分配内存速度快,避免频繁系统调用
2.固定大小分配,降低内存碎片
3.内存连续。CPU命中率高
4.非常适合大量同类型对象频繁创建销毁的场景
待解决缺陷
1.没有主动释放内存池
2.一个池只能分配一种类型
3.高对齐类型存在UB
4.多线程并发访问会崩溃