一、将一定数量的对象释放到span跨度
// 将一定数量的对象释放到span跨度
void ReleaseListToSpans(void *start, size_t size)
{
size_t index = Sizeclass::Index(size);
_spanLists[index]._mtx.lock();
while (start)
{
void *next = NextObj(start);
Span *span = PageCache::GetInstance()->MapObjectToSpan(start);
NextObj(start) = span->_freeList;
span->_freeList = start;
span->_useCount--;
// 说明span切分出去的小块内存都回来了
// 这个span就可以再回收给pagecache,pagecache可以再尝试进行前后页的合并、
if (span->_useCount == 0)
{
_spanLists[index].Erase(span);
span->_freeList = nullptr;
span->_next = nullptr;
span->_prev = nullptr;
// 释放span给pagecache时,使用pagecache的锁就好了
// 解掉桶锁
_spanLists[index]._mtx.unlock();
PageCache::GetInstance()->_pageMtx.lock();
PageCache::GetInstance()->ReleaseSpanToPageCache(span);
PageCache::GetInstance()->_pageMtx.unlock();
_spanLists[index]._mtx.lock();
}
start = next;
}
_spanLists[index]._mtx.unlock();
}
从代码来看,ReleaseListToSpans
函数的功能是将一定数量的对象释放到 Span
中,并且在 Span
的使用计数为 0 时,将 Span
释放回 PageCache
。这个函数涉及多线程同步(通过锁)和资源管理。以下是对代码的分析和一些需要注意的地方:
1. 代码分析
1.1 锁的使用
_spanLists[index]._mtx.lock()
和_spanLists[index]._mtx.unlock()
用于保护_spanLists[index]
的操作,确保线程安全。PageCache::GetInstance()->_pageMtx.lock()
和PageCache::GetInstance()->_pageMtx.unlock()
用于保护PageCache
的操作,确保线程安全。
1.2 对象释放逻辑
遍历对象链表,将每个对象加入到对应的
Span
的_freeList
中。每次释放一个对象时,
span->_useCount
减 1。如果
span->_useCount
为 0,说明该Span
的所有对象都已释放,可以将Span
释放回PageCache
。
1.3 锁的嵌套
在释放
Span
时,先解锁_spanLists[index]._mtx
,然后加锁PageCache::GetInstance()->_pageMtx
,最后再次加锁_spanLists[index]._mtx
。这种锁的嵌套需要特别小心,否则可能导致死锁或竞态条件。
2. 代码优化和改进建议
2.1 锁的优化
减少锁的持有时间:尽量减少锁的持有时间,以提高性能。
避免锁的嵌套:如果可能,尽量避免锁的嵌套。如果必须嵌套,确保嵌套顺序一致,避免死锁。
2.2 错误处理
空指针检查:在访问指针(如
span
)之前,确保指针不为空。异常安全:确保在发生异常时,锁能够正确释放。
2.3 代码可读性
注释:增加注释,解释每个步骤的目的。
函数拆分:将复杂的逻辑拆分成多个函数,提高代码可读性。
二、 释放对象时,链表过长时,回收内存回到中心缓存
// 释放对象时,链表过长时,回收内存回到中心缓存
void ListTooLong(FreeList& list, size_t size)
{
void* start = nullptr;
void* end = nullptr;
list.PopRange(start, end, list.MaxSize());
CentralCache::GetInstance()->ReleaseListToSpans(start, size);
}
三、释放空闲span到pagecache,并合并相邻的span
//释放空闲span到pagecache,并合并相邻的span
void ReleaseSpanToPageCache(Span* span)
{
//对span前后的页尝试进行合并,缓解内存碎片的问题
while (1)
{
PAGE_ID prevId = span->_pageId - 1;
auto ret = _idSpanMap.find(prevId);
//前面的页号没有,不合并
if(ret == _idSpanMap.end())
{
break;
}
//前面相邻页的span在使用,不合并了
Span* prevSpan = ret->second;
if(prevSpan->_isUse == true)
{
break;
}
//合并后超过128页的span没办法管理,不合并
if(prevSpan->_n + span->_n > NPAGES - 1)
{
break;
}
span->_pageId = prevSpan ->_pageId;
span->_n += prevSpan->_n;
_spanLists[prevSpan->_n].Erase(prevSpan);
delete prevSpan;
}
//向后合并
while(1)
{
PAGE_ID nextId = span->_pageId + span->_n;
auto ret = _idSpanMap.find(nextId);
if(ret == _idSpanMap.end())
{
break;
}
Span* nextSpan = ret->second;
if(nextSpan->_isUse == true)
{
break;
}
if(nextSpan->_n + span->_n > NPAGES - 1)
{
break;
}
span->_n += nextSpan->_n;
_spanLists[nextSpan->_n].Erase(nextSpan);
delete nextSpan;
}
_spanLists[span->_n].PushFront(span);
span->_isUse = false;
_idSpanMap[span->_pageId] = span;
_idSpanMap[span->_pageId + span->_n - 1] = span;
}
这段代码的功能是将一个空闲的 Span
释放到 PageCache
中,并尝试合并相邻的 Span
,以减少内存碎片。以下是对代码的详细分析和改进建议。
1. 代码分析
1.1 向前合并逻辑
prevId
:计算当前Span
前一个页面的 ID。_idSpanMap.find(prevId)
:查找前一个页面的Span
。合并条件:
如果前一个
Span
未使用(prevSpan->_isUse == false
)。合并后的
Span
大小不超过NPAGES - 1
。
合并操作:
更新当前
Span
的_pageId
和_n
。从
_spanLists
中移除前一个Span
。删除前一个
Span
。
1.2 向后合并逻辑
nextId
:计算当前Span
后一个页面的 ID。_idSpanMap.find(nextId)
:查找后一个页面的Span
。合并条件:
如果后一个
Span
未使用(nextSpan->_isUse == false
)。合并后的
Span
大小不超过NPAGES - 1
。
合并操作:
更新当前
Span
的_n
。从
_spanLists
中移除后一个Span
。删除后一个
Span
。
1.3 最终操作
将当前
Span
插入到_spanLists
中。更新
_idSpanMap
,确保当前Span
的起始和结束页面 ID 都指向该Span
。