内存池是什么原理?|内存池简易模拟实现|为学习高并发内存池tcmalloc做准备
前言
那么这里博主先安利一些干货满满的专栏了!
这两个都是博主在学习Linux操作系统过程中的记录,希望对大家的学习有帮助!
操作系统Operating Syshttps://blog.csdn.net/yu_cblog/category_12165502.html?spm=1001.2014.3001.5482
Linux Syshttps://blog.csdn.net/yu_cblog/category_11786077.html?spm=1001.2014.3001.5482这两个是博主学习数据结构的同时,手撕模拟STL标准模版库各种容器的专栏。
STL源码剖析https://blog.csdn.net/yu_cblog/category_11983210.html?spm=1001.2014.3001.5482手撕数据结构https://blog.csdn.net/yu_cblog/category_11490888.html
什么是内存池
因此最近博主准备开始学习Google的tcmalloc技术了,其实它就是一个高并发的内存池,效率做到极致,因此在此之前,博主先稍微学习了一下内存池的基本技术和基本概念,为后面的高并发内存池项目做准备,今天先给大家带来这个简单的内存池实现技术的简单demo代码。
所以本博客的代码,这是内存池的demo代码,实际上的内存池,比这个要复杂的多!这里的代码只是供学习使用。tcmalloc技术才是学习内存池的目标。
什么是内存池?
内存池是一种用来管理计算机程序中内存分配和释放的技术。它类似于一个预先准备好的内存存储区域,程序可以从中获取内存块而不是频繁地向操作系统请求。这种方式更高效,因为内存块的分配和释放开销较小,并且可以避免碎片化。内存池提高了程序的性能和响应速度,特别适用于频繁创建和销毁对象的场景,如网络服务器和并发编程。C语言的malloc,本质就是一个内存池。
内存池的基本原理
为了更高效的处理向内存获取资源,向内存获取资源,向内存释放资源,内存池一般这么做:
博主为了大家好理解,说的比较通俗:
- 内存池初始化的时候,直接向内存先要一大块资源。
- 向内存池获取资源的时候,从刚才初始化获取的一大块内存中切出一部分交给上层使用。
- 当进程向内存池释放资源的时候,不直接释放到系统中,而是首先把不用的空间,交给内存池,内存池内部维护一个单链表,把还回来的资源串起来。
- 当大块内存用完的时候,再向系统索要新内存。
- 当进程索取资源的时候,优先从链表中获取还回来的资源,如果链表中没有资源了,再向系统索要资源。
ObjectPool.hpp
#ifndef __OBJECT_POOL__
#define __OBJECT_POOL__
#include <iostream>
#include <vector>
// #include "../utils/Log.hpp" // 大家下载代码的时候可以暂时不要日志
#define __DEFAULT_KB__ 128
/*
其实这里直接使用malloc的,malloc其实自己就是C语言的一个内存池,
其实我们可以直接用系统调用,跳过malloc,这样测试现象更明显
调用SystemAlloc替换malloc即可
*/
inline static void *SystemAlloc(size_t kpage)
{
void *ptr = nullptr;
#ifdef _WIN32
*ptr = VirtualAlloc(0, kpage * (1 << 12), MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
#else
// linux下brk mmap等 #endif
if (ptr == nullptr)
throw std::bad_alloc();
return ptr;
#endif
}
template <class T>
class ObjectPool
{
private:
char *__memory = nullptr; // char 方便切
size_t __remain_bytes = 0; // 大块内存在切的过程中剩余的字节数
void *__free_list = nullptr; // 还回来的时候形成的自由链表
public:
T *New()
{
T *obj = nullptr;
// 不够空间 首选是把还回来的内存块对象进行再次利用
if (__free_list)
{
// 头删
void *next = *((void **)__free_list);
obj = (T *)__free_list;
__free_list = next;
return obj;
}
if (__remain_bytes < sizeof(T))
{
// 空间不够了,要重新开一个空间
__remain_bytes = __DEFAULT_KB__ * 1024;
__memory = (char *)malloc(__remain_bytes);
if (__memory == nullptr)
{
throw std::bad_alloc();
// logMessage(ERROR, "ObjectPool::New() malloc error");
}
}
obj = (T *)__memory;
size_t obj_size = sizeof(T) < sizeof(void *) ? sizeof(void *) : sizeof(T); // 小于一个指针的大小就给一个指针的大小就行
__memory += obj_size;
__remain_bytes -= obj_size;
// 定位new,显示调用T的构造函数,让T初始化一下
new (obj) T;
return obj;
}
void Delete(T *obj)
{
// 这样写无论是第一次插入还是后面的插入,都可以
// 这样写无论是32位还是64位,都可以
obj->~T(); // 显示调用析构函数
*(void **)obj = __free_list;
__free_list = obj;
}
};
#endif
testPerf.hpp
用于测试这个简易内存池的性能
#include "ObjectPool.hpp"
struct TreeNode
{
int _val;
TreeNode *_left;
TreeNode *_right;
TreeNode()
: _val(0), _left(nullptr), _right(nullptr)
{
}
};
void TestObjectPool()
{
// 申请释放的轮次
const size_t Rounds = 5;
// 每轮申请释放多少次
const size_t N = 10000000;
size_t begin1 = clock();
std::vector<TreeNode *> v1;
v1.reserve(N);
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();
}
size_t end1 = clock();
ObjectPool<TreeNode> TNPool;
size_t begin2 = clock();
std::vector<TreeNode *> v2;
v2.reserve(N);
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();
}
size_t end2 = clock();
std::cout << "new cost time:" << end1 - begin1 << std::endl;
std::cout << "object pool cost time:" << end2 - begin2 << std::endl;
}
test.cc
#include "testPerf.hpp"
int main()
{
TestObjectPool();
return 0;
}