设计高并发内存池申请流程
Posted 楠c
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计高并发内存池申请流程相关的知识,希望对你有一定的参考价值。
实现线程缓存
分析完整体架构我们来从最底层实现。
取下一个对象
之前提到这里使用头四个字节来存下一个对象的。适用于32位以及64位
头插与头删
将对象头插与头删
这样就可以实现从自由链表中放对象,拿对象。
和STL空间配置器结构类似,多个不同大小的对象,所以用一个对象数组。
假如我们最大也是128个字节,以8为间隔,那么数组长度就是16
对象映射到桶
现在来一个对象,怎么算他在那个桶呢?
TLS技术
测试
公共头文件
#pragma once
#include <iostream>
#include <exception>
#include <vector>
#include <time.h>
#include <assert.h>
#include <thread>
using std::cout;
using std::endl;
inline void*& NextObj(void* obj)
return *((void**)obj);
//size_t Index(size_t size)
//
// if (size % 8 == 0)
//
// return size / 8 - 1;
//
// else
//
// return size / 8;
//
//
// 8 + 7 = 15
// 7 + 7
// ...
// 1 + 7 = 8
static size_t Index(size_t size)
return ((size + (2^3 - 1)) >> 3) - 1;
class FreeList
public:
// Í·²å
void Push(void* obj)
NextObj(obj) = _head;
_head = obj;
// ͷɾ
void* Pop()
void* obj = _head;
_head = NextObj(_head);
return obj;
bool Empty()
return _head == nullptr;
private:
void* _head = nullptr;
;
#pragma once
#include "Common.h"
class ThreadCache
public:
void* Allocate(size_t size);
void Deallocate(void* ptr, size_t size);
private:
FreeList _freeLists[16];
;
static __declspec(thread) ThreadCache* tls_threadcache = nullptr;
#include "ThreadCache.h"
void* ThreadCache::Allocate(size_t size)
size_t i = Index(size);
if (!_freeLists[i].Empty())
//return _freeLists[i].Pop();
else
// ...
return nullptr;
void ThreadCache::Deallocate(void* ptr, size_t size)
size_t i = Index(size);
_freeLists[i].Push(ptr);
遇到个错误
调用顺序
#pragma once
#include "Common.h"
#include "ThreadCache.h"
//void* tcmalloc(size_t size)
void* ConcurrentAlloc(size_t size)
if (tls_threadcache == nullptr)
tls_threadcache = new ThreadCache;
cout << tls_threadcache << endl;
return tls_threadcache->Allocate(size);
void ConcurrentFree(void* ptr, size_t size)
assert(tls_threadcache);
tls_threadcache->Deallocate(ptr, size);
更改代码
定义线程缓存结构
使内存碎片始终控制在1%-12%
//辅助计算thread_Cache中各个freeList的映射,central_cache中各个Spanlist的映射
static size_t _Index(size_t bytes,size_t align_shift)
//[1,128]区间,8字节对齐,例如分配1字节,最小8字节
//((1 + (2^3)-1)/8 ) - 1
//算出本区间桶的相对位置
//[129,1024]区间,16字节对齐,例如分配129字节(129先减去前面的128),最小144字节,但此时应该减去前128个字节按照16字节的对齐方式计算
//(1 + (2^4)-1)/16) -1
//算出本区间桶的相对位置
return ((bytes + (1<<align_shift)-1)>>align_shift) - 1;
// 控制在平均1%-12%左右的内碎片浪费
// [1,128] 8byte对齐 freelist[0,15]
// [129,1024] 16byte对齐 freelist[16,71]
// [1025,8*1024] 128byte对齐 freelist[72,127]
// [8*1024+1,64*1024] 1024byte对齐 freelist[128,183]
//算出他们在那个桶
static size_t Index(size_t bytes)
assert(bytes <= MAXBYTES);
//每个区间桶的数量
static int group[4] = 16, 56, 56, 56 ;
//8字节对齐
if (bytes <= 128)
return _Index(bytes, 3);
//16字节对齐
else if (bytes <= 1024)
//由于返回的是本区间桶的相对位置,所以加上上个区间的所有桶,就是整体的绝对位置
return _Index(bytes - 128, 4)+group[0];
//128字节对齐
else if (bytes <= (8 * 1024))
return _Index(bytes - 1024, 7) + group[0] + group[1];
//1024字节对齐
else if (bytes <= (64 * 1024))
return _Index(bytes - (8 * 1024), 10) + group[0] + group[1] + group[2];
assert(false);
return -1;
线程缓存申请
//申请内存
void* ThreadCache::Allocate(size_t size)
size_t i = SizeClass::Index(size);
//对应的桶非空,取一个对象弹出去
if (!_freelist[i].Empty())
return _freelist[i].Pop();
else//没空间,去向central cache要
//两种情况1.中心缓存也没有2.中心缓存有(其他线程空闲的较多还给中心缓存,从pagecache切分而来)
return FetchFromCentralCache(i, size);
定义中心缓存结构
定义Spnalist将Span连接起来
中心缓存申请内存
这个是假如线程缓存没有对象的话,向中心缓存拿一批对象的接口
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t n, size_t size)
size_t i = SizeClass::Index(size);
Span* span = GetOneSpan(_spanLists[i], size);
// 找到一个有对象的span,有多少给多少
size_t j = 1;
start = span->_list;
void* cur = start;
void* prev = start;
while (j <= n && cur != nullptr)
prev = cur;
cur = NextObj(cur);
++j;
span->_usecount++;
span->_list = cur;
end = prev;
NextObj(prev) = nullptr;
return j-1;
可以看到里面的GetSpan是获取一个Span对象
页缓存结构
看一下之前的两种映射
第一次过来会向系统申请128页的大Span。根据中心缓存所需进行切分
#include "PageCache.h"
// 向系统申请k页内存
void* PageCache::SystemAllocPage(size_t k)
return ::SystemAlloc(k);
Span* PageCache::NewSpan(size_t k)
if (!_spanList[k].Empty())
Span* it = _spanList[k].Begin();
_spanList[k].Erase(it);
return it;
for (size_t i = k+1; i < NPAGES; ++i)
// 大页给切小,切成k页的span返回
// 切出i-k页挂回自由链表
if (!_spanList[i].Empty())
Span* span = _spanList[i].Begin();
_spanList->Erase(span);
Span* splitSpan = new Span;
splitSpan->_pageId = span->_pageId + k;
splitSpan->_n = span->_n - k;
span->_n = k;
_spanList[splitSpan->_n].Insert(_spanList[splitSpan->_n].Begin(), splitSpan);
return span;
Span* bigSpan = new Span;
void* memory = SystemAllocPage(NPAGES - 1);
bigSpan->_pageId = (size_t)memory >> 12;
bigSpan->_n = NPAGES - 1;
_spanList[NPAGES - 1].Insert(_spanList[NPAGES - 1].Begin(), bigSpan);
return NewSpan(k);
总结
以上是关于设计高并发内存池申请流程的主要内容,如果未能解决你的问题,请参考以下文章
高并发内存池——基于Google开源项目tcmalloc的简洁实现
高并发内存池——基于Google开源项目tcmalloc的简洁实现