文章目录
- ThreadCache层的结构
- 申请内存逻辑
- 释放内存逻辑
- 自由链表的实现
- 自由链表的成员变量
- 自由链表的成员函数
- 自由链表的完整实现
- ThreadCache申请内存过程的实现
- ThreadCache需要的成员变量
- ThreadCache需要的成员函数
- ThreadCache.h文件代码
- Allocate的实现
- Deallocate的实现
- 封装ThreadCache层可以多线程访问
- ThreadCache层完整代码
- Common.h
- ThreadCache.h
- ThreadCache.cpp
- ConcurrentAlloc.h
ThreadCache层的结构
thread cache是哈希桶结构,每个桶是一个按桶位置映射大小的内存块对象的自由链表。每个线程都会有一个thread cache对象,这样每个线程在这里获取对象和释放对象时是无锁的。
申请内存逻辑
- 当内存申请size<=256KB,先获取到线程本地存储的thread cache对象,计算size映射的哈希桶自由链表下标i。
- 如果自由链表_freeLists[i]中有对象,则直接Pop一个内存对象返回。
- 如果_freeLists[i]中没有对象时,则批量从central cache中获取一定数量的对象,插入到自由链表并返回一个对象。
释放内存逻辑
释放逻辑之后再完整实现,现在先了解释放流程
- 当释放内存小于256k时将内存释放回thread cache,计算size映射自由链表桶位置i,将对象Push到_freeLists[i]。
- 当链表的长度过长,则回收一部分内存对象到central cache。
自由链表的实现
从上面我们可以知道,ThreadCache存放内存块需要自由链表,所以我们要先实现自由链表。
自由链表的成员变量
之前学过定长内存池就知道用一个_freeList指针就能管理好多个内存块。
自由链表的成员函数
Push:可以让内存块链入自由链表中
Pop:可以将内存块从自由链表中移除(也就是该内存块被申请出去了)
Empty:可以判断该自由链表是否为空。
Push的过程
Push的实现
void Push(void* obj)
{
assert(obj);//断言保证obj不为空
// 头插
*(void**)obj = _freeList;
_freeList = obj;
}
因为*(void**)obj在后面很多地方都会用到,所以我们对其封装成函数
static void*& NextObj(void* obj)
{
return *(void**)obj;
}
//这里用static的原因是因为,NextObj是
//被放进行Common.h文件的,而Common.h文件
//肯被对个.cpp文件包含,那么到时编译器就会
//报错
//而static修饰函数表示只在Common.h文件内有效
所以我们Push就可改成
void Push(void* obj)
{
assert(obj);//断言保证obj不为空
// 头插
//*(void**)obj = _freeList;
NextObj(obj) = _freeList;
_freeList = obj;
}
如果大家不理解*(void**)obj,可以去之前的博客观看,这里不再做讲解。
Pop的过程:
Pop的实现:
void* Pop()
{
assert(_freeList);//_freeList一定不能为空
// 头删
void* obj = _freeList;
_freeList = NextObj(obj);
return obj;
}
Empty的实现
bool Empty()
{
return _freeList == nullptr;
}
自由链表的完整实现
// 管理切分好的小对象的自由链表
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);
return obj;
}
bool Empty()
{
return _freeList == nullptr;
}
private:
void* _freeList = nullptr;
};
ThreadCache申请内存过程的实现
首先做项目,我们肯定要定义一个包含头文件的.h文件一般命名为Common.h文件
Common.h
//Common.h
#pragma once
#include <iostream>
#include <assert.h>
using std::cout;
using std::endl;
// thread cache 和 central cache自由链表哈希桶的表大小
static const size_t NFREELISTS = 208;
static 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);
return obj;
}
bool Empty()
{
return _freeList == nullptr;
}
private:
void* _freeList = nullptr;
};
这里我们先定义这几个,等其它有用到再慢慢添加。
然后实现ThreadCache也要定义两个文件。
一个是ThreadCache.h,ThreadCache.cpp
ThreadCache定义两个文件是为了声明和定义分离,这样更符合实际的项目场景。
ThreadCache.h
//ThreadCache.h
#include "Common.h"
ThreadCache.cpp
//ThreadCache.cpp
#include "ThreadCache.h"
ThreadCache需要的成员变量
从ThreadCache的结构中我们就可以知道,我们肯定要定义一个指针数组,而每个指针都用来管理内存块,所以这些指针都是自由链表指针
class ThreadCache
{
public:
private:
FreeList _freeLists[NFREELIST];
};
NFREELIST是thread cache自由链表哈希桶的表大小,为208个,所以我们还要Common.h中定义。
ThreadCache需要的成员函数
class ThreadCache
{
public:
// 申请和释放内存对象
void* Allocate(size_t size);
void Deallocate(void* ptr, size_t size);
// 从中心缓存获取对象
void* FetchFromCentralCache(size_t index, size_t size);
private:
FreeList _freeLists[NFREELIST];
};
Allocate:申请内存对象
Deallocate:释放内存对象
FetchFromCentralCache:哈希桶如果内存空间不够,向中心缓存(central cache)申请内存空间
ThreadCache.h文件代码
#pragma once
#include "Common.h"
class ThreadCache
{
public:
// 申请和释放内存对象
void* Allocate(size_t size);
void Deallocate(void* ptr, size_t size);
// 从中心缓存获取对象
void* FetchFromCentralCache(size_t index, size_t size);
private:
FreeList _freeLists[NFREELIST];
};
那么接下来就是实现这些成员函数了。
Allocate的实现
首先我们要知道申请的空间的字节数大小是多少。
其次我们要对该大小进行对齐,我们用自己设定的对齐规则对原字节数大小对齐
最后我们要根据字节数大小找到对应的哈希桶。
那么这是我们就要设计一个类帮助我们解决这些问题。
//放在common.h中
// 计算对象大小的对齐映射规则
class SizeClass
{
public:
//计算对象大小的对齐映射规则
// 整体控制在最多10%左右的内碎片浪费
// [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)
//方法一:
/*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;
}*/
// 1-8
//方法二:
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, 13) + group_array[3] + group_array[2] + group_array[1] + group_array[0];
}
else{
assert(false);
}
return -1;
}
};
Allocate的实现
void* ThreadCache::Allocate(size_t size)
{
//size是要申请空间的字节大小
//MAX_BYTES为最大申请的空间字节数 256*1024字节
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);
}
}
Deallocate的实现
void ThreadCache::Deallocate(void* ptr, size_t size)
{
assert(ptr);
assert(size <= MAX_BYTES);
// 找对映射的自由链表桶,对象插入进入
size_t index = SizeClass::Index(size);
_freeLists[index].Push(ptr);
封装ThreadCache层可以多线程访问
只要在ThreadCache.h文件添加下列代码,就可以支持多线程访问。
// TLS thread local storage
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;
然后用ConcurrentAlloc.h封装ThreadCache层,不让外面直接可以访问到ThreadCache的函数。保证安全性
pragma once
#include "Common.h"
#include "ThreadCache.h"
static void* ConcurrentAlloc(size_t size)
{
// 通过TLS 每个线程无锁的获取自己的专属的ThreadCache对象
if (pTLSThreadCache == nullptr)
{
pTLSThreadCache = new ThreadCache;
}
return pTLSThreadCache->Allocate(size);
}
static void ConcurrentFree(void* ptr, size_t size)
{
assert(pTLSThreadCache);
pTLSThreadCache->Deallocate(ptr, size);
}
ThreadCache层完整代码
Common.h
#pragma once
#include <iostream>
#include <vector>
#include <thread>
#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;
static 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);
return obj;
}
bool Empty()
{
return _freeList == nullptr;
}
private:
void* _freeList = nullptr;
};
// 计算对象大小的对齐映射规则
class SizeClass
{
public:
// 整体控制在最多10%左右的内碎片浪费
// [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)
/*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;
}*/
// 1-8
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;
}
}*/
// 1 + 7 8
// 2 9
// ...
// 8 15
// 9 + 7 16
// 10
// ...
// 16 23
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, 13) + 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);
// 从中心缓存获取对象
void* FetchFromCentralCache(size_t index, size_t size);
private:
FreeList _freeLists[NFREELIST];
};
// TLS thread local storage
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;
ThreadCache.cpp
#include "ThreadCache.h"
void* ThreadCache::FetchFromCentralCache(size_t index, size_t size)
{
// ...
return nullptr;
}
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)
{
assert(ptr);
assert(size <= MAX_BYTES);
// 找对映射的自由链表桶,对象插入进入
size_t index = SizeClass::Index(size);
_freeLists[index].Push(ptr);
}
ConcurrentAlloc.h
#pragma once
#include "Common.h"
#include "ThreadCache.h"
static void* ConcurrentAlloc(size_t size)
{
// 通过TLS 每个线程无锁的获取自己的专属的ThreadCache对象
if (pTLSThreadCache == nullptr)
{
pTLSThreadCache = new ThreadCache;
}
return pTLSThreadCache->Allocate(size);
}
static void ConcurrentFree(void* ptr, size_t size)
{
assert(pTLSThreadCache);
pTLSThreadCache->Deallocate(ptr, size);
}