C++项目 -- 高并发内存池(五)释放内存过程

C++项目 – 高并发内存池(五)释放内存过程

文章目录

  • C++项目 -- 高并发内存池(五)释放内存过程
  • 一、Thread Cache释放内存
    • 1.完善FreeList功能
    • 2.Thread Cache释放内存
  • 二、Central Cache释放内存
  • 三、Page Cache释放内存
  • 四、释放内存过程联调
  • 五、代码实现


一、Thread Cache释放内存

1.完善FreeList功能

  • 当一块内存块释放时,我们将其归还给对应的Thread Cache的freeList,当前freeList的长度如果达到了一定值,我们就可以将一段list归还给central cache管理,以减少内存碎片,因此FreeList类需要增加统计链表长度的成员及接口;
  • PopRange用于批量从FreeList中取走size个对象;
//自由链表类,用于管理切分好的小内存块
class FreeList {
public:void Push(void* obj) {assert(obj);//头插NextObj(obj) = _freeList;_freeList = obj;_size++;}//范围插入void PushRange(void* start, void* end, size_t size) {assert(start);assert(end);NextObj(end) = _freeList;_freeList = start;_size += size;}void* Pop() {assert(_freeList);//头删void* obj = _freeList;_freeList = NextObj(obj);_size--;return obj;}//批量取走对象void PopRange(void* start, void* end, size_t size) {assert(size >= _size);start = _freeList;end = start;for (size_t i = 0; i < size - 1; i++) {end = NextObj(end);}_freeList = NextObj(end);NextObj(end) = nullptr;_size -= size;}bool Empty() {return _freeList == nullptr;}//用于实现thread cache从central cache获取内存的慢开始算法size_t& MaxSize() {return _maxSize;}size_t Size() {return _size;}
private:void* _freeList = nullptr;size_t _maxSize = 1;size_t _size = 0;
};

2.Thread Cache释放内存

  • Deallocate函数用于将释放的内存块插入到对应的Thread Cache的自由链表中,如果自由链表的长度超过了一次批量申请内存块的数量,就调用ListTooLong函数归还一段链表给Central Cache;
void ThreadCache::Deallocate(void* obj, size_t size) {assert(obj);assert(size <= MAX_BYTES);//找该对象对应的freeList的桶,直接插入size_t index = SizeClass::Index(size);_freeLists[index].Push(obj);//当链表的长度大于一次批量申请的内存块的数量时,就归还一段list给central cacheif (_freeLists[index].Size() > _freeLists[index].MaxSize()) {ListTooLong(_freeLists[index], size);}
}
  • ListTooLong函数用于从当前自由链表中取出MaxSize长度的链表,归还到CentralCache的对应Span中:
void ThreadCache::ListTooLong(FreeList& list, size_t size) {void* start = nullptr;void* end = nullptr;//从list中取出MaxSize长度的链表list.PopRange(start, end, list.MaxSize());//归还给CentralCache的对应spanCentralCache::GetInstance()->ReleaseListToSpan(start, size);
}

二、Central Cache释放内存

  • 从central cache获取内存块到thread cache的时候,要更新span的_useCount参数;
//从CentralCache获取一定数量的内存对象给ThreadCache
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size) {//先根据对象size获取对应的spanList下标size_t index = SizeClass::Index(size);//每个线程访问spanList时需要加锁_spanLists[index]._mtx.lock();//获取非空的spanSpan* span = GetOneSpan(_spanLists[index], size);assert(span);assert(span->_freeList);//从span中获取batchNum个对象,若不够,就有多少拿多少start = span->_freeList;end = start;size_t i = 0;size_t actualNum = 1; // 实际拿到的对象数量while (i < batchNum - 1 && NextObj(end) != nullptr) {end = NextObj(end);actualNum++;i++;}//在span中去掉这一段对象span->_freeList = NextObj(end);NextObj(end) = nullptr;//更新span->_useCount参数span->_useCount += actualNum;_spanLists[index]._mtx.unlock();return actualNum;
}
  • 在Page Cache中加入页号与Span的映射关系;
  • 在central cache向page cache申请span的时候,申请下了span就立即将该span的_isUse属性设为true,避免后面page cache合并span的时候出现线程安全问题;
Span* CentralCache::GetOneSpan(SpanList& spanList, size_t size) {//先检查该SpanList有没有未分配的SpanSpan* it = spanList.Begin();while (it != spanList.End()) {if (it->_freeList != nullptr) {return it;}else {it = it->_next;}}//先把central cache 的桶锁解掉,这样如果其他线程释放对象回来,就不会被阻塞spanList._mtx.unlock();//SpanList中没有空闲的Span,需要向page cache申请//在此处加上page cache的全局锁,NewSpan的所有操作都是加锁进行的PageCache::GetInstance()->_pageMtx.lock();Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));//更新_isUse属性span->_isUse = true;PageCache::GetInstance()->_pageMtx.unlock();//从page cache获取到了新的span,需要进行切分//无需在此加上桶锁,因为该span还没有放到spanList中,其他线程访问不到//计算span大块内存的起始地址和大块内存的大小(字节数)char* start = (char*)(span->_pageID << PAGE_SHIFT);size_t bytes = span->_n << PAGE_SHIFT;char* end = start + bytes;//把大块内存切成自由链表链接起来//先切一块下来做头,方便尾插span->_freeList = start;start += size;void* tail = span->_freeList;while (start < end) {NextObj(tail) = start;tail = start;start += size;}//在span挂载到spanList之前加上桶锁spanList._mtx.lock();spanList.PushFront(span);return span;
}
  • ReleaseListToSpan函数用于将归还回来的内存块链表挂载回span;
    • 判断内存块属于哪一页,页的起始地址除以8k为页号,两页之间的任意地址除以8k也是该地址所属的页号
      在这里插入图片描述
    • 查找内存块对象对应的span,头插到对应的span中;
    • 更新span的useCount,如果useCount变为0,就说明所有内存块都已经归还,就可以将该span归还给page cache;
    • 归还page cache的时候也涉及解除central cache的桶锁,因为这之后的操作与该桶无关了,其他的线程也有可能在这个桶申请和释放内存,因此需要解除桶锁;
    • page cache的锁直接加在ReleaseSpanToPageCache之外
void CentralCache::ReleaseListToSpan(void* start, size_t byte_size) {size_t index = SizeClass::Index(byte_size);_spanLists[index]._mtx.lock();//该段list的尾部指针已经置空,遍历到空指针就停止while (start) {//将内存块对象挂载到对应的span上void* next = NextObj(start);//获取该对象对应的spanSpan* span = PageCache::GetInstance()->MapObjectToSpan(start);NextObj(start) = span->_freeList;span->_freeList = start;//更新_useCountspan->_useCount--;//说明该span的小块内存都回收了//这个span就可以回收给page cache,由page cache去做前后页的合并if (span->_useCount == 0) {_spanLists[index].Erase(span);span->_prev = nullptr;span->_next = nullptr;span->_freeList = nullptr;//释放span给page cache的时候,使用page cache的锁就可以了//将桶锁先解除,方便其他线程在该桶上申请和释放内存_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();
}

三、Page Cache释放内存

  • PageCache类中增加一个哈希表_idSpanMap成员,用于存储页号到Span的映射关系;
    NewSpan函数切分Span的时候,将切分后的两个Span的页号与Span的映射关系都加入到_idSpanMap中;
    MapObjectToSpan函数用于获取对象到span的映射关系;
    在这里插入图片描述
Span* PageCache::NewSpan(size_t k)
{assert(k > 0 && k < NPAGES);//先检查第k个桶里面有没有spanif (!_spanLists[k].Empty()) {//有就返回return _spanLists[k].PopFront();}//没有就需要检查后面的桶有没有更大的span,如果有可以拆分for (size_t i = k + 1; i < NPAGES; i++) {if (!_spanLists[i].Empty()) {Span* nspan = _spanLists[i].PopFront();Span* kspan = new Span;//在nspan头部且下一个k页的span//kspan返回//nspan剩下的部分挂载到相应的桶上kspan->_pageID = nspan->_pageID;kspan->_n = k;nspan->_pageID += k;nspan->_n -= k;_spanLists[nspan->_n].PushFront(nspan);//存储nspan的首尾页号与Span的关系,方便page cache回收内存时进行合并查找_idSpanMap[nspan->_pageID] = nspan;_idSpanMap[nspan->_pageID + nspan->_n - 1] = nspan;//存储kspan每一页的页号与span的映射,方便central cache回收小块内存时,查找对应的spanfor (PAGE_ID i = 0; i < kspan->_n; i++) {_idSpanMap[kspan->_pageID + i] = kspan;}return kspan;}}//走到这里说明没有更大的span了,需要向堆申请一个128页的大块内存Span* bigSpan = new Span;void* ptr = SystemAlloc(NPAGES - 1);bigSpan->_pageID = (PAGE_ID)ptr >> PAGE_SHIFT;bigSpan->_n = NPAGES - 1;_spanLists[NPAGES - 1].PushFront(bigSpan);//此时需要将_spanLists中的128页的内存切分,递归调用一下return NewSpan(k);
}
Span* PageCache::MapObjectToSpan(void* obj) {PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);auto ret = _idSpanMap.find(id);if (ret != _idSpanMap.end()) {return ret->second;}else {//应该是一定能够获取到的//如果获取不到就是出现了问题assert(false);return nullptr;}
}
  • ReleaseSpanToPageCache函数用于释放空闲span回到page cache,并合并相邻的span;
    • 通过_idSpanMap获取页号与span的映射关系,将处于空闲状态,并且页号相邻的span合并成更大的span,这样可以减少内存碎片;
    • 使用span的_useCount属性来判断span是否被使用会造成线程安全问题,因为一个span被分到central cache但是好没有被切分时,他的_useCount依然是0;
    • 因此需要使用_isUse属性来判断,在span被分给central cache后,就将_isUse置为true;
    • 不断地向前向后合并;
void PageCache::ReleaseSpanToPageCache(Span* span) {//对span前后的相邻页进行合并,缓解内存碎片的问题//向前合并while (1) {//前一页的idPAGE_ID prevId = span->_pageID - 1;//从map中寻找页号与span的映射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;}//合并前面的spanspan->_pageID = prevSpan->_pageID;span->_n += prevSpan->_n;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;}//将合并好的span挂载到对应的哈希桶,更新isUse_spanLists[span->_n].PushFront(span);span->_isUse = true;_idSpanMap[span->_pageID] = span;_idSpanMap[span->_pageID + span->_n - 1] = span;
}

四、释放内存过程联调

void TestConcurrentAlloc1() {void* p1 = ConcurrentAlloc(6);void* p2 = ConcurrentAlloc(8);void* p3 = ConcurrentAlloc(1);void* p4 = ConcurrentAlloc(7);void* p5 = ConcurrentAlloc(8);void* p6 = ConcurrentAlloc(7);void* p7 = ConcurrentAlloc(8);cout << p1 << endl;cout << p2 << endl;cout << p3 << endl;cout << p4 << endl;cout << p5 << endl;cout << p6 << endl;cout << p7 << endl;ConcurrentFree(p1, 6);ConcurrentFree(p2, 8);ConcurrentFree(p3, 1);ConcurrentFree(p4, 7);ConcurrentFree(p5, 8);ConcurrentFree(p6, 7);ConcurrentFree(p7, 8);
}
  • 能最终释放内存,并合并成128页的大块span

多线程测试

void MultiThreadAlloc1() {std::vector<void*> v;for (int i = 0; i < 7; i++) {void* ptr = ConcurrentAlloc(5);v.push_back(ptr);}for (auto e : v) {ConcurrentFree(e, 5);}
}void MultiThreadAlloc2() {std::vector<void*> v;for (int i = 0; i < 7; i++) {void* ptr = ConcurrentAlloc(16);v.push_back(ptr);}for (auto e : v) {ConcurrentFree(e, 16);}
}void TestMultiThread() {std::thread t1(Alloc1);std::thread t2(Alloc2);t1.join();t2.join();
}
  • 多线程联调,并行监视

五、代码实现

Common.h

#pragma once
//公共头文件#include <iostream>
#include <vector>
#include <assert.h>
#include <thread>
#include <mutex>
#include <algorithm>
#include <unordered_map>
using std::cout;
using std::endl;
using std::vector;static const size_t MAX_BYTES = 256 * 1024; //ThreadCache能分配对象的最大字节数
static const size_t NFREELIST = 208; //central cache 最大的哈希桶数量
static const size_t NPAGES = 129; //page cache 哈希桶的数量
static const size_t PAGE_SHIFT = 13; //页与字节的转换#ifdef _WIN32#include<windows.h>
#else
//linux
#endif#ifdef _WIN64typedef unsigned long long PAGE_ID;
#elif _WIN32typedef size_t PAGE_ID;
#elif//linux#endif//直接去堆上申请空间
inline static void* SystemAlloc(size_t kpage) {
#ifdef _WIN32void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else#endif // _WIN32if (ptr == nullptr) {throw std::bad_alloc();}return ptr;
}// 访问obj的前4 / 8字节地址空间
static void*& NextObj(void* obj) {return *(void**)obj;
}//自由链表类,用于管理切分好的小内存块
class FreeList {
public:void Push(void* obj) {assert(obj);//头插NextObj(obj) = _freeList;_freeList = obj;_size++;}//范围插入void PushRange(void* start, void* end, size_t size) {assert(start);assert(end);NextObj(end) = _freeList;_freeList = start;_size += size;}void* Pop() {assert(_freeList);//头删void* obj = _freeList;_freeList = NextObj(obj);_size--;return obj;}//批量取走对象void PopRange(void*& start, void* end, size_t size) {assert(_size >= size); /// ????????  _size >= sizestart = _freeList;end = start;for (size_t i = 0; i < size - 1; i++) {end = NextObj(end);}_freeList = NextObj(end);NextObj(end) = nullptr;_size -= size;}bool Empty() {return _freeList == nullptr;}//用于实现thread cache从central cache获取内存的慢开始算法size_t& MaxSize() {return _maxSize;}size_t Size() {return _size;}
private:void* _freeList = nullptr;size_t _maxSize = 1;size_t _size = 0;
};// 管理对齐和哈希映射规则的类
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)//RoundUp的子函数,根据对象大小和对齐数,返回对象对齐后的大小static inline size_t _RoundUp(size_t size, size_t align) {//if (size % align == 0) {//	return size;//}//else {//	return (size / align + 1) * align;//}//使用位运算能够得到一样的结果,但是位运算的效率很高return ((size + align - 1) & ~(align - 1));}//计算当前对象size字节对齐之后对应的sizestatic inline size_t RoundUp(size_t size) {assert(size <= MAX_BYTES);if (size <= 128) {//8字节对齐return _RoundUp(size, 8);}else if (size <= 1024) {//16字节对齐return _RoundUp(size, 16);}else if (size <= 8 * 1024) {//128字节对齐return _RoundUp(size, 128);}else if (size <= 64 * 1024) {//1024字节对齐return _RoundUp(size, 1024);}else if (size <= 256 * 1024) {//8KB字节对齐return _RoundUp(size, 8 * 1024);}else {assert(false);}return -1;}//Index的子函数,用于计算映射的哈希桶下标static inline size_t _Index(size_t size, size_t alignShift) {//if (size % align == 0) {//	return size / align - 1;//}//else {//	return size / align;//}//使用位运算能够得到一样的结果,但是位运算的效率很高//使用位运算需要将输入参数由对齐数改为对齐数是2的几次幂、return ((size + (1 << alignShift) - 1) >> alignShift) - 1;}//计算对象size映射到哪一个哈希桶(freelist)static inline size_t Index(size_t size) {assert(size <= MAX_BYTES);//每个区间有多少个哈希桶static int groupArray[4] = { 16, 56, 56, 56 };if (size <= 128) {return _Index(size, 3);}else if (size <= 1024) {//由于前128字节不是16字节对齐,因此需要减去该部分,单独计算16字节对齐的下标//再在最终结果加上全部的8字节对齐哈希桶个数return _Index(size - 128, 4) + groupArray[0];}else if (size <= 8 * 1024) {return _Index(size - 1024, 7) + groupArray[0] + groupArray[1];}else if (size <= 64 * 1024) {return _Index(size - 8 * 1024, 10) + groupArray[0] + groupArray[1] + groupArray[2];}else if (size <= 256 * 1024) {return _Index(size - 64 * 1024, 13) + groupArray[0] + groupArray[1] + groupArray[2] + groupArray[3];}else {assert(false);}return -1;}//thread cache一次从central cache中获取多少内存块static size_t NumMoveSize(size_t size) {//一次获取的内存块由对象的大小来决定assert(size > 0);//将获取的数量控制在[2, 512]size_t num = MAX_BYTES / size;if (num < 2) {num = 2;}if (num > 512) {num = 512;}return num;}//计算central cache一次向page cache获取多少页的spanstatic size_t NumMovePage(size_t size) {assert(size > 0);//先计算该对象一次申请内存块的上限值size_t num = NumMoveSize(size);//计算上限的空间大小size_t npage = num * size;//转换成page单位npage >>= PAGE_SHIFT;if (npage == 0) {npage = 1;}return npage;}
};struct Span
{PAGE_ID _pageID = 0; // 大块内存起始页的页号size_t _n = 0; // 页的数量Span* _next = nullptr; // 双向链表的结构Span* _prev = nullptr;size_t _objSize = 0; // 切好的小对象的大小size_t _useCount = 0; // 切好小块内存,被分配给thread cache的计数void* _freeList = nullptr; // 切好的小块内存的自由链表bool _isUse = false; // 是否在被使用
};class SpanList {
public:SpanList() {_head = new Span;_head->_next = _head;_head->_prev = _head;}void Insert(Span* pos, Span* newSapn) {assert(pos);assert(newSapn);Span* prev = pos->_prev;prev->_next = newSapn;newSapn->_prev = prev;newSapn->_next = pos;pos->_prev = newSapn;}void Erase(Span* pos) {assert(pos);assert(pos != _head);//不用释放空间Span* prev = pos->_prev;Span* next = pos->_next;prev->_next = next;next->_prev = prev;}Span* Begin() {return _head->_next;}Span* End() {return _head;}bool Empty() {return _head->_next == _head;}void PushFront(Span* newSapn) {Insert(Begin(), newSapn);}Span* PopFront() {Span* front = _head->_next;Erase(front);return front;}private:Span* _head;		//头节点
public:std::mutex _mtx;	//桶锁
};

ThreadCache.h

#pragma once
#include "Common.h"class ThreadCache {
public://申请和释放对象内存void* Allocate(size_t size);void Deallocate(void* obj, size_t size);//从中心缓存获取对象void* FetchFromCentralCache(size_t index, size_t alignSize);//自由链表过长时,回收一段链表到中心缓存void ListTooLong(FreeList& list, size_t size);private:FreeList _freeLists[NFREELIST];
};//声明_declspec(thread)后,会为每一个线程创建一个单独的拷贝
//使用_declspec(thread)声明了ThreadCache*指针变量,则该指针在该线程中会创建一份单独的拷贝
//pTLSThreadCache指向的对象在本线程内是能够全局访问的,但是无法被其他线程访问到,这就做到了多线程情景下的无锁访问
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;

ThreadCache.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "ThreadCache.h"
#include "CentralCache.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()) {//若对应的freeList桶不为空,直接pop一个内存块给该对象return _freeLists[index].Pop();}else {//否则需要从CentralCache获取内存空间return ThreadCache::FetchFromCentralCache(index, alignSize);}
}void ThreadCache::Deallocate(void* obj, size_t size) {assert(obj);assert(size <= MAX_BYTES);//找该对象对应的freeList的桶,直接插入size_t index = SizeClass::Index(size);_freeLists[index].Push(obj);//当链表的长度大于一次批量申请的内存块的数量时,就归还一段list给central cacheif (_freeLists[index].Size() >= _freeLists[index].MaxSize()) {ListTooLong(_freeLists[index], size);}
}void ThreadCache::ListTooLong(FreeList& list, size_t size) {void* start = nullptr;void* end = nullptr;//从list中取出MaxSize长度的链表list.PopRange(start, end, list.MaxSize());//归还给CentralCache的对应spanCentralCache::GetInstance()->ReleaseListToSpan(start, size);
}void* ThreadCache::FetchFromCentralCache(size_t index, size_t alignSize) {//慢开始算法//计算当前从Central Cache中获取内存块的最大数量size_t batchNum = min(_freeLists[index].MaxSize(), SizeClass::NumMoveSize(alignSize));//如果MaxSize未达上限,就将MaxSize + 1if (batchNum == _freeLists[index].MaxSize()) {_freeLists[index].MaxSize() += 1;}void* start = nullptr;void* end = nullptr;size_t actualNum = CentralCache::GetInstance()->FetchRangeObj(start, end, batchNum, alignSize);assert(actualNum >= 1);if (actualNum == 1) {//如果最终获取的数量为1,直接返回给对象assert(start == end);return start;}else {//如果最终获取的数量多于一个,则返回第一个给对象,剩下的插入freeList里_freeLists[index].PushRange(NextObj(start), end, actualNum - 1); // 批量插入//NextObj(start) = nullptr;return start;}
}

CentralCache.h

#pragma once
#include "Common.h"
#include "PageCache.h"//饿汉单例模式
class CentralCache {
public:static CentralCache* GetInstance() {return &_sInstance;}//从CentralCache获取一定数量的内存对象给ThreadCachesize_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size);//获取一个非空的SapnSpan* GetOneSpan(SpanList& spanList, size_t size);//归还一段list到对应的spanvoid ReleaseListToSpan(void* start, size_t byte_size);private:SpanList _spanLists[NFREELIST];//构造函数私有化CentralCache() {}//不生成默认拷贝构造CentralCache(const CentralCache&) = delete;static CentralCache _sInstance;
};

CentralCache.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "CentralCache.h"//单例模式静态成员的定义
CentralCache CentralCache::_sInstance;//从CentralCache获取一定数量的内存对象给ThreadCache
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size) {//先根据对象size获取对应的spanList下标size_t index = SizeClass::Index(size);//每个线程访问spanList时需要加锁_spanLists[index]._mtx.lock();//获取非空的spanSpan* span = GetOneSpan(_spanLists[index], size);assert(span);assert(span->_freeList);//从span中获取batchNum个对象,若不够,就有多少拿多少start = span->_freeList;end = start;size_t i = 0;size_t actualNum = 1; // 实际拿到的对象数量while (i < batchNum - 1 && NextObj(end) != nullptr) {end = NextObj(end);actualNum++;i++;}//在span中去掉这一段对象span->_freeList = NextObj(end);NextObj(end) = nullptr;//更新span->_useCount参数span->_useCount += actualNum;_spanLists[index]._mtx.unlock();return actualNum;
}Span* CentralCache::GetOneSpan(SpanList& spanList, size_t size) {//先检查该SpanList有没有未分配的SpanSpan* it = spanList.Begin();while (it != spanList.End()) {if (it->_freeList != nullptr) {return it;}else {it = it->_next;}}//先把central cache 的桶锁解掉,这样如果其他线程释放对象回来,就不会被阻塞spanList._mtx.unlock();//SpanList中没有空闲的Span,需要向page cache申请//在此处加上page cache的全局锁,NewSpan的所有操作都是加锁进行的PageCache::GetInstance()->_pageMtx.lock();Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));//更新_isUse属性span->_isUse = true;PageCache::GetInstance()->_pageMtx.unlock();//从page cache获取到了新的span,需要进行切分//无需在此加上桶锁,因为该span还没有放到spanList中,其他线程访问不到//计算span大块内存的起始地址和大块内存的大小(字节数)char* start = (char*)(span->_pageID << PAGE_SHIFT);size_t bytes = span->_n << PAGE_SHIFT;char* end = start + bytes;//把大块内存切成自由链表链接起来//先切一块下来做头,方便尾插span->_freeList = start;start += size;void* tail = span->_freeList;while (start < end) {NextObj(tail) = start;tail = start;start += size;}//在span挂载到spanList之前加上桶锁spanList._mtx.lock();spanList.PushFront(span);return span;
}void CentralCache::ReleaseListToSpan(void* start, size_t byte_size) {size_t index = SizeClass::Index(byte_size);_spanLists[index]._mtx.lock();//该段list的尾部指针已经置空,遍历到空指针就停止while (start) {//将内存块对象挂载到对应的span上void* next = NextObj(start);//获取该对象对应的spanSpan* span = PageCache::GetInstance()->MapObjectToSpan(start);NextObj(start) = span->_freeList;span->_freeList = start;//更新_useCountspan->_useCount--;//说明该span的小块内存都回收了//这个span就可以回收给page cache,由page cache去做前后页的合并if (span->_useCount == 0) {_spanLists[index].Erase(span);span->_prev = nullptr;span->_next = nullptr;span->_freeList = nullptr;//释放span给page cache的时候,使用page cache的锁就可以了//将桶锁先解除,方便其他线程在该桶上申请和释放内存_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();
}

PageCache.h

#pragma once
#include "Common.h"//单例模式
class PageCache {
public:static PageCache* GetInstance() {return &_sInstance;}std::mutex _pageMtx; //全局锁//获取一个k页的SpanSpan* NewSpan(size_t k);//获取对象到span的映射Span* MapObjectToSpan(void* obj);//释放空闲span回到page cache,并合并相邻的spanvoid ReleaseSpanToPageCache(Span* span);private:SpanList _spanLists[NPAGES];// 用于存储页号到Span的映射关系std::unordered_map<PAGE_ID, Span*> _idSpanMap;PageCache() {}PageCache(const PageCache&) = delete;static PageCache _sInstance;
};

PageCache.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "PageCache.h"PageCache PageCache::_sInstance;Span* PageCache::NewSpan(size_t k)
{assert(k > 0 && k < NPAGES);//先检查第k个桶里面有没有spanif (!_spanLists[k].Empty()) {//有就返回return _spanLists[k].PopFront();}//没有就需要检查后面的桶有没有更大的span,如果有可以拆分for (size_t i = k + 1; i < NPAGES; i++) {if (!_spanLists[i].Empty()) {Span* nspan = _spanLists[i].PopFront();Span* kspan = new Span;//在nspan头部且下一个k页的span//kspan返回//nspan剩下的部分挂载到相应的桶上kspan->_pageID = nspan->_pageID;kspan->_n = k;nspan->_pageID += k;nspan->_n -= k;_spanLists[nspan->_n].PushFront(nspan);//存储nspan的首尾页号与Span的关系,方便page cache回收内存时进行合并查找_idSpanMap[nspan->_pageID] = nspan;_idSpanMap[nspan->_pageID + nspan->_n - 1] = nspan;//存储kspan每一页的页号与span的映射,方便central cache回收小块内存时,查找对应的spanfor (PAGE_ID i = 0; i < kspan->_n; i++) {_idSpanMap[kspan->_pageID + i] = kspan;}return kspan;}}//走到这里说明没有更大的span了,需要向堆申请一个128页的大块内存Span* bigSpan = new Span;void* ptr = SystemAlloc(NPAGES - 1);bigSpan->_pageID = (PAGE_ID)ptr >> PAGE_SHIFT;bigSpan->_n = NPAGES - 1;_spanLists[NPAGES - 1].PushFront(bigSpan);//此时需要将_spanLists中的128页的内存切分,递归调用一下return NewSpan(k);
}Span* PageCache::MapObjectToSpan(void* obj) {PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);auto ret = _idSpanMap.find(id);if (ret != _idSpanMap.end()) {return ret->second;}else {//应该是一定能够获取到的//如果获取不到就是出现了问题assert(false);return nullptr;}
}void PageCache::ReleaseSpanToPageCache(Span* span) {//对span前后的相邻页进行合并,缓解内存碎片的问题//向前合并while (1) {//前一页的idPAGE_ID prevId = span->_pageID - 1;//从map中寻找页号与span的映射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;}//合并前面的spanspan->_pageID = prevSpan->_pageID;span->_n += prevSpan->_n;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;}//将合并好的span挂载到对应的哈希桶,更新isUse_spanLists[span->_n].PushFront(span);span->_isUse = true;_idSpanMap[span->_pageID] = span;_idSpanMap[span->_pageID + span->_n - 1] = span;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/690172.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pytorch关于CIFAR-10测试

下载 CIFAR-10数据集&#xff1a; 官网&#xff1a;https://www.cs.toronto.edu/~kriz/cifar.html CIFAR-10的网络结构&#xff1a; import torch from torch import nn from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential#定义网络结构 class Model(nn.Mo…

【社区投稿】Rust登陆华为鸿蒙操作系统之Native模块开发

Rust登陆【华为鸿蒙】操作系统之Native模块开发 名词解释 【鸿蒙操作系统】的英文全名是Open Harmony Operation System。正文将以其首字母缩写词ohos引用该词条。【鸿蒙软件开发工具包】的英文全名是Open Harmony Software Development Kit。正文也将以它的首字母缩写词ohsdk引…

C++-带你初步走进继承(1)

1.继承的概念及定义 1.1继承的概念 继承 (inheritance) 机制是面向对象程序设计 使代码可以复用 的最重要的手段&#xff0c;它允许程序员在 保 持原有类特性的基础上进行扩展 &#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承 呈现了面向对象 …

如何在IDEA中使用固定公网地址SSH远程连接服务器开发环境

文章目录 1. 检查Linux SSH服务2. 本地连接测试3. Linux 安装Cpolar4. 创建远程连接公网地址5. 公网远程连接测试6. 固定连接公网地址7. 固定地址连接测试 本文主要介绍如何在IDEA中设置远程连接服务器开发环境&#xff0c;并结合Cpolar内网穿透工具实现无公网远程连接&#xf…

Compose 1.6 发布:性能大升级、拖放新功能、文本新变化...

翻译自&#xff1a; https://android-developers.googleblog.com/2024/01/whats-new-in-jetpack-compose-january-24-release.html 基于 1 月 24 号的 Compose 发行计划&#xff0c;我们正式推出了 Jetpack Compose 1.6 版本。 作为 Android 平台备受推崇的原生 UI 工具包&…

P4447 [AHOI2018初中组] 分组题解

题目 小可可的学校信息组总共有n个队员&#xff0c;每个人都有一个实力值。现在&#xff0c;一年一度的编程大赛就要到了&#xff0c;小可可的学校获得了若干个参赛名额&#xff0c;教练决定把学校信息组的n个队员分成若干个小组去参加这场比赛。 但是每个队员都不会愿意与实…

安全测试工具安装指南:在统信UOS上部署Burp Suite

原文链接&#xff1a;安全测试工具安装指南&#xff1a;在统信UOS上部署Burp Suite 大家好&#xff01;在网络安全领域&#xff0c;Burp Suite是一款不可或缺的工具&#xff0c;它提供了从初级映射和分析应用程序攻击面到查找和利用安全漏洞的一系列功能。今天&#xff0c;我将…

Shiro-05-shiro 基础知识补充密码学+哈希散列

密码学 密码术是隐藏或混淆数据的过程&#xff0c;因此窥探眼睛无法理解它。 Shiro的加密目标是简化JDK的加密支持并使之可用。 需要特别注意的是&#xff0c;密码通常不是特定于主题的&#xff0c;因此Shiro API的其中一个领域不是特定于主题的。 即使未使用“主题”&…

【elk查日志 elastic(kibana)】

文章目录 概要具体的使用方式一&#xff1a;查找接口调用历史二&#xff1a;查找自己的打印日志三&#xff1a;查找错误日志 概要 每次查日志&#xff0c;我都需要别人帮我&#xff0c;时间长了总觉得不好意思&#xff0c;所以这次下定决心好好的梳理一下&#xff0c;怎么查日…

spellman电源维修X3635系列CCM5P4X3635

Spellman高压发生器维修Perkin Elmer分析仪电源维修CCM5P4X3635 Perkin Elmer W102266 X射线高压发生器spellman电源维修X4297系列CT机高压电源维修CT42&#xff1b;CT70系列。SPELLMAN高压发生器应用于东芝CT机XVISION/EX、AUKLET系列、ASTEION系列、以及多排系列&#xff0c…

一文概括|CSC访问学者/博士后/联培申请及派出流程详解

为帮助申请者了解国家留学基金委&#xff08;CSC&#xff09;的政策&#xff0c;以及申报及派出的全过程&#xff0c;知识人网小编利用本文简略介绍并提出规划建议。 公派留学包括国家、地方&#xff08;含省市、行业、学校医院等单位&#xff09;资助派出。而国家公派则由留学…

HarmonyOS开发篇—数据管理(分布式数据服务)

分布式数据服务概述 分布式数据服务&#xff08;Distributed Data Service&#xff0c;DDS&#xff09; 为应用程序提供不同设备间数据库数据分布式的能力。通过调用分布式数据接口&#xff0c;应用程序将数据保存到分布式数据库中。通过结合帐号、应用和数据库三元组&#xf…

软件测试实训系统建设方案2024

软件测试实训室解决方案 一 、方案概述 软件测试实训解决方案是一个复杂且至关重要的过程&#xff0c;它确保了软件在开发过程中的各个模块能够正确地集成和交互。通过这一系列的测试步骤&#xff0c;开发团队能够及时发现并修复潜在的问题&#xff0c;从而提高软件的整体质量…

【漏洞复现-通达OA】通达OA share存在前台SQL注入漏洞

一、漏洞简介 通达OA&#xff08;Office Anywhere网络智能办公系统&#xff09;是由北京通达信科科技有限公司自主研发的协同办公自动化软件&#xff0c;是与中国企业管理实践相结合形成的综合管理办公平台。通达OA为各行业不同规模的众多用户提供信息化管理能力&#xff0c;包…

【Java EE初阶十五】网络编程TCP/IP协议(二)

1. 关于TCP 1.1 TCP 的socket api tcp的socket api和U大片的socket api差异很大&#xff0c;但是和前面所讲的文件操作很密切的联系 下面主要讲解两个关键的类&#xff1a; 1、ServerSocket&#xff1a;给服务器使用的类&#xff0c;使用这个类来绑定端口号 2、Socket&#xf…

全网最容易理解的KMP算法讲解

引言 其实网上有很多讲解KMP算法的文章&#xff0c;详略不一&#xff0c;我认为有两点没有解释清楚&#xff1a; 第一点&#xff1a;匹配失败以后&#xff0c;模式串的位移 第二点&#xff1a;next数组的生成算法 希望本篇文章能将KMP算法清晰易懂的拆解开来。 暴力匹配 …

网络同步—帧同步和状态同步解析

概述 同步就是要多个客户端表现效果是一致的&#xff0c;而且对于大多数的游戏&#xff0c;不仅仅要表现一致&#xff0c;还要客户端和服务器的数据也是一致的。所以同步是个网络游戏概念&#xff0c;只有网络游戏才需要同步&#xff0c;而单机游戏是不需要同步的。 帧同步和…

算法-3-基本的数据结构

单双链表 1.单链表双链表如何反转 import java.util.ArrayList; import java.util.List;public class Code01_ReverseList {public static class Node {public int value;public Node next;public Node(int data) {value data;}}public static class DoubleNode {public int…

掘根宝典之C++深复制与浅复制(复制构造函数,默认复制构造函数)

到目前为止我们已经学了构造函数&#xff0c;默认构造函数&#xff0c;析构函数&#xff1a;http://t.csdnimg.cn/EOQxx 转换函数&#xff0c;转换构造函数&#xff1a;http://t.csdnimg.cn/kiHo6 友元函数&#xff1a;http://t.csdnimg.cn/To8Tj 接下来我们来学习一个新函数…

python毕设选题 - 大数据全国疫情数据分析与3D可视化 - python 大数据

文章目录 0 前言1 课题背景2 实现效果3 设计原理4 部分代码5 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&#xff0c;这两年不断有学弟学妹告诉学长自己做的…