此项目原型是Google的一个开源项目tcmalloc(Thread-Caching Malloc)即线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关的函数;
一、内存池
1.池化技术
程序现象系统申请过量的资源,然后自己管理。之所以要申请过量的资源,是因为每次申请该资源都会有较大的开销,提前申请好后,会大大提高程序运行效率。
除了有内存池,还有连接池、线程池、对象池等。想城池主要思想是:县启动若干数量的线程,让他们处于睡眠状态,当接收到客户端的请求时,唤醒池中某个睡眠的线程,让他来处理客户端的请求,档处理完成后,线程再次进入睡眠状态
2.内存池
内存池是指程序预先从操作系统中申请一块足够大的内存,当程序需要申请内存时,不直接向操作系统申请,而是直接从内存池中获取;当程序是方式,并不将内存返回给操作系统,而是返回内存池。当程序退出(或特定时间)时,内存池才将之前申请的内存真正释放。
3.内存池主要解决的问题
内存池主要解决的是现率问题,还需要解决内存碎片问题;
内存碎片分为内碎片和外碎片,外碎片是一些空闲的连续内存区域太小,这些内存空间不连续,以至于合集的内存足够,但是不能满足一些的内存分配申请需求。内部碎片式由于一些对齐的需求,导致分配出去的空间中一些内存无法被利用。
4.malloc
C/C++中动态申请内存都是使用malloc,而malloc就是一个内存池,malloc() 相当于向操作系统“批发”了一块较大的内存空间,然后“零售”给应用程序。当全部用完或程序有大量的内存需求时,再根据实际情况向操作系统申请内存,linux中的gcc用的是glibc中的ptmalloc。
二、设计一个定长内存池
#include <iostream>
using std::cout;
using std::endl;
//定长内存池控制大小
//template<size_t N>
//class ObjectPool
//{
//
//};
template<class T>
class ObjectPool
{
public:
T* New()
{
T* obj = nullptr;
if (_freeList)
{
void* next = *((void**)_freeList);
obj = (T*)_freeList;
_freeLost = next;
}
else
{
if (_remainBytes < sizeof(T))
{
_remainBytes= 128 * 1024
_memory = (char*)malloc(_remainBytes);
if (_memory == nullptr)
{
throw std::bad_alloc();
}
}
obj = (T*)_memory;
size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
_memory += objSize;
_remainBytes -= sizeof(T);
}
//定位new,显示调用T的构造函数初始化;
new(obj)T;
return obj;
}
void Delete(T* obj)
{
//显示调用析构函数,清理对象
obj->~T();
if (_freeList == nullptr)
{
_freeList = obj;
*(void**)obj = nullptr;
}
}
private:
char* _memory = nullptr;//使用char类型比较方便
size_T _remainBytes = 0;//剩余字节数;
void* _freeList = nullptr;//需要构建自由链表,帮助存储使用后返回的内存块,一般在内存块的头几个字节中存储返回字节的地址。
};
三、高并发内存池整体框架设计
目前许多开发场景都是多核多线程,在申请内存的场景下,存在激烈的所竞争问题,tcmalloc在多线程高并发场景下更好用,所以实现的内存池需要考虑以下方面:
1.性能问题
2.多线程环境下,锁的竞争问题
3.内存碎片问题;
高并发内存池主要由3各部分组成:
1.thread cache:线程缓存是每个线程独有的,用于小于256kb的内存分配,线程从这里申请内存不需要加锁,每个线程独享一个cache,这就是并发线程池高效的地方;
2.central cache:中心缓存是所有线程锁共享,Thread cache是按需从central cache中获取的对象。central合适的试剂回收thread cache中的对象,避免一个线程占用了太多的内存,而其他线程的内存不够用,达到内存分配在多线程中更均衡的按需调度的目的。central cache 是存在竞争的所以从这里取内存对象是需要加锁的。首先者利用的是桶锁,其次只有thread cache的没有内存对象时才会找central cache,所以这里竞争不会很激烈。
3.page cache:页缓存是在central cache缓存上面的一层缓存,存储的内存是以页单位存储及分配的,central cache没有内存对象时,从page cache分配处一定数量的page,并切割成定长大小的小块内存,分配给central cache。当一个span的几个跨度页的对象都回收以后,page cache会回收central cache满足条件的span对象,并且合并相邻的页,组成更大的页,缓解内存碎片的问题。
四、thread cache 设计
Common.h
#pragma once
#include <iostream>
#include <vector>
#include <time.h>
#include <assert.h>
using std::cout;
using std::endl;
static const size_t MAX_BYTES = 256 * 1024;
static const size_t NFREELIST = 208;
void*& NextObj(void* obj)
{
return *(void**)obj;
}
//管理切分好的小对象的自由链表
class FreeList
{
public:
void Push(void* obj)
{
assert(obj);
//头插
//*(void**)obj = _freeList;
NextObj(obj) = _freeList;
_freeList = obj;
}
void* Pop()
{
assert(_freeList);
//头删
void* obj = _freeList;
_freeList = NextObj(obj);
}
bool Empty()
{
return _freeList == nullptr;
}
private:
void* _freeList;
};
//计算对象大小的对齐映射规则
//[1,128] 8byte对齐 freelist[0,16)
//[128+1,1024] 16byte对齐 freelist[16,72)
//[1024+1,8*1024] 128byte对齐 freelist[72,128)
//[8*1024+1,64*1024] 1024byte对齐 freelist[128,184)
//[64*1024+1,256*1024] 8*1024byte对齐 freelist[184,208)
//
class SizeClass
{
public:
/*size_t _RoundUp(size_t size; size_t alignNum)
{
size_t alignSize;
if (size % alignNum != 0)
{
alignSize = size / (alignNum + 1)*alignNum;
}
else
{
alignSize = size;
}
return alignSize;
}*/
static inline size_t _RoundUp(size_t bytes, size_t alignNum)
{
return ((bytes + alignNum - 1) & ~(alignNum - 1));
}
static inline size_t RoundUp(size_t size)
{
if (size <= 128)
{
return _RoundUp(size, 8);
}
else if (size <= 1024)
{
return _RoundUp(size, 16);
}
else if (size <= 8 * 1024)
{
return _RoundUp(size, 128);
}
else if (size <= 64 * 1024)
{
return _RoundUp(size, 1024);
}
else if (size <= 256 * 1024)
{
return _RoundUp(size, 8*1024);
}
else
{
assert(false);
return -1;
}
}
/*size_t _Index(size_t bytes, size_t alignNum)
{
if (bytes%alignNum==0)
{
return bytes / alignNum - 1;
}
else
{
return bytes / alignNum;
}
}*/
static inline size_t _Index(size_t bytes, size_t align_shift)
{
return ((bytes + (1 << align_shift) - 1) >> align_shift) - 1;
}
static inline size_t Index(size_t bytes)
{
assert(bytes <= MAX_BYTES);
static int group_array[4] = { 16,56,56,56 };
if (bytes <= 128)
{
return _Index(bytes, 3);
}
else if (bytes <= 1024)
{
return _Index(bytes - 128, 4) + group_array[0];
}
else if (bytes <= 8 * 1024)
{
return _Index(bytes - 1024, 7) + group_array[1] + group_array[0];
}
else if (bytes <= 64 * 1024)
{
return _Index(bytes - 8 * 1024, 10) + group_array[2] + group_array[1] + group_array[0];
}
else if (bytes <= 256 * 1024)
{
return _Index(bytes - 64 * 1024, 10) + group_array[3] + group_array[2] + group_array[1] + group_array[0];
}
else
{
assert(false);
}
return -1;
}
};
ThreadCache.h
#pragma once
#include "Common.h"
class ThreadCache
{
public:
//申请和释放空间
void* Allocate(size_t size);
void Deallocate(void* ptr, size_t size);
private:
FreeList _freeLists[NFREELIST];
};
ThreadCache.cpp
#include "ThreadCache.h"
void* ThreadCache::Allocate(size_t size)
{
assert(size <= MAX_BYTES);
size_t alignSize = SizeClass::RoundUp(size);
size_t index = SizeClass::Index(size);
if (!_freeLists[index].Empty())
{
return _freeLists[index].Pop();
}
else
{
return FetchFromCentralCache(index,alignSize);
}
}
void ThreadCache::Deallocate(void* ptr, size_t size)
{
}