central cache也是⼀个哈希桶结构,他的哈希桶的映射关系跟threadcache是⼀样的。不同的是他的每个哈希桶位置,挂的是SpanList链表结构,不过每个映射桶下⾯的span中的⼤内存块被按映射关系切成 了⼀个个⼩内存块对象挂在span的⾃由链表中。
1.申请内存
1. 当threadcache中没有内存时,就会批量向centralcache申请⼀些内存对象,这⾥的批量获取对象的数量使⽤了类似⽹络tcp协议拥塞控制的慢开始算法;centralcache也有⼀个哈希映射的 spanlist,spanlist中挂着span,从span中取出对象给threadcache,这个过程是需要加锁的,不 过这里的使用是⼀个桶锁,尽可能提高效率。
2. central cache映射的spanlist中所有span的都没有内存以后,则需要向pagecache申请⼀个新的 span对象,拿到span以后将span管理的内存按⼤⼩切好作为⾃由链表链接到⼀起。然后从span中 取对象给threadcache。
3. central cache中挂的span中use_count记录分配了多少个对象出去,分配⼀个对象给thread cache,就++use_coun
2.释放内存:
当thread_cache过⻓或者线程销毁,则会将内存释放回centralcache中,释放回来时- use_count。当use_count减到0时则表⽰所有对象都回到了span,则将span释放回pagecache, pagecache中会对前后相邻的空闲⻚进⾏合并 。
3. 从中心缓存获取对象
// 从中心缓存获取对象
void *FetchFromCentralCache(size_t index, size_t size)
{
// 慢开始反馈调节算法
// 1、最开始不会一次向central cache一次批量要太多,因为要太多了可能用不完
// 2、如果你不要这个size大小内存需求,那么batchNum就会不断增长,直到上限
// 3、size越大,一次向central cache要的batchNum就越小
// 4、size越小,一次向central cache要的batchNum就越大
size_t batchNum = std::min(_freeLists[index].MaxSize(), Sizeclass::NumMoveSize(size));
if(_freeLists[index].MaxSize() = batchNum)
{
_freeLists[index].MaxSize() += 1;
}
void* start = nullptr;
void* end = nullptr;
size_t actualNum = CentralCache::GetInstance()->FetchRangeObj(start, end, batchNum, size);
assert(actualNum > 1);
if(actualNum == 1)
{
assert(start == end);
return start;
}
else
{
_freeLists[index].PushRange(NextObj(start), end);
return start;
}
return nullptr;
}
-
慢开始反馈调节算法: 代码注释说明了该函数使用的是一种慢开始反馈调节算法,用于优化从中心缓存获取对象的方式。
-
获取批量数量 (
batchNum
)size_t batchNum = std::min(_freeLists[index].MaxSize(), Sizeclass::NumMoveSize(size));
计算应该从中心缓存获取的对象批量数量。这是通过取
_freeLists[index]
的最大大小和Sizeclass::NumMoveSize(size)
的最小值得到的。 -
调整最大大小
if(_freeLists[index].MaxSize() == batchNum) { _freeLists[index].MaxSize() += 1; }
如果当前最大大小等于计算出的批量数量,则增加
_freeLists[index]
的最大大小。 -
初始化指针
void* start = nullptr; void* end = nullptr;
定义了两个指针
start
和end
,初始值都为nullptr
。 -
从中心缓存获取对象
size_t actualNum = CentralCache::GetInstance()->FetchRangeObj(start, end, batchNum, size);
调用
CentralCache
的FetchRangeObj
方法,尝试获取指定数量的对象。actualNum
存储实际获取到的对象数量。 -
检查获取结果
assert(actualNum > 1);
断言确保至少获取到两个对象。
-
处理单个对象:
if(actualNum == 1) { assert(start == end); return start; }
如果只获取到一个对象,检查
start
和end
是否相同,并返回start
指向的对象。 -
处理多个对象
else { _freeLists[index].PushRange(NextObj(start), end); return start; }
如果有多个对象,将它们添加到
_freeLists[index]
列表中,并返回start
指向的对象。
4.从中心获取一定数量的对象给ThreadCache
//从中心获取一定数量的对象给ThreadCache
size_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
size_t index = Sizeclass::Index(size);
_spanLists[index]._mtx.lock();
Span* span = GetOneSpan(_spanLists[index], size);
assert(span);
assert(span->_freeList);
//从Span中获取batchnum个对象
//如果不够那么batchnum个,有多少拿多少
start = span->_freeList;
end = start;
size_t i = 0;
size_t actualnum = 1;
while(i < batchNum - 1 && NextObj(end) != NULL)
{
end = NextObj(end);
++i;
++actualnum;
}
span->_freeList = NextObj(end);
NextObj(end) = nullptr;
_spanLists[index]._mtx.unlock();
return actualnum;
}
这段代码定义了一个名为 FetchRangeObj
的函数,该函数的目的是从中心缓存(CentralCache
)获取一定数量的对象,并将其放入 ThreadCache
。函数接收四个参数:两个指针引用 start
和 end
用于指向对象范围的起始和结束位置,batchNum
表示要获取的对象数量,size
表示对象的大小。
-
获取索引
size_t index = Sizeclass::Index(size);
使用
Sizeclass::Index
静态方法计算给定大小的对象应存放在哪个索引的_freeLists
中。 -
锁定互斥锁
_spanLists[index]._mtx.lock();
锁定对应索引的互斥锁,以确保在操作
_freeLists[index]
时的线程安全。 -
获取
Span
对象Span* span = GetOneSpan(_spanLists[index], size);
调用
GetOneSpan
函数从_spanLists[index]
中获取一个Span
对象,该Span
包含了一定数量的连续空闲对象。 -
断言
assert(span); assert(span->_freeList);
确保获取到的
Span
对象不为空,并且其_freeList
不为空。 -
从
Span
中获取对象start = span->_freeList; end = start; size_t i = 0; size_t actualnum = 1; while(i < batchNum - 1 && NextObj(end) != NULL) { end = NextObj(end); ++i; ++actualnum; }
遍历
Span
中的_freeList
,尝试获取batchNum
个对象。如果_freeList
中的对象数量不足batchNum
,则获取所有可用的对象。 -
更新
Span
状态span->_freeList = NextObj(end); NextObj(end) = nullptr;
更新
Span
的_freeList
指针到end
之后的位置,并将end
之后的对象标记为已使用(nullptr
)。 -
解锁互斥锁
_spanLists[index]._mtx.unlock();
操作完成后解锁互斥锁。
-
返回获取到的对象数量
return actualnum;
返回实际获取到的对象数量。
注意事项
-
确保
Sizeclass::Index
和GetOneSpan
函数正确实现,并且Span
类型正确管理内存跨度。 -
在操作
_freeLists[index]
时,需要确保互斥锁的正确使用,以避免数据竞争条件。 -
函数中的断言用于调试目的,确保代码逻辑的正确性。在生产环境中,可能需要更复杂的错误处理机制。