6.高并发内存池的内存释放全流程

在高性能内存池的设计中,内存释放的逻辑直接决定了碎片率、并发效率和整体性能。本文基于自研的三级缓存内存池(ThreadCache → CentralCache → PageCache),拆解从用户调用ConcurrentFree开始的完整释放链路,聚焦 "正确释放 + 高效复用" 的核心逻辑

1.释放入口:用户层的 ConcurrentFree

内存释放的入口是ConcurrentFree函数,这是暴露给用户的唯一释放接口,核心作用是将释放请求转发到当前线程私有的ThreadCache

复制代码
// ConcurrentAlloc.h
static void ConcurrentFree(void* ptr, size_t size)  //用户层释放接口
{
    assert(pTLSThreadCache);
    assert(ptr);
    assert(size <= MAX_BYTES);
    pTLSThreadCache->Deallocate(ptr, size); //转发到ThreadCache层
}

这里的关键是pTLSThreadCache是线程局部存储(TLS)的指针,每个线程有独立的ThreadCache,释放操作先在 "线程私有空间" 完成,避免跨线程竞争

2.第一步:ThreadCache 层的释放(私有缓存,无锁)

ThreadCache::Deallocate是释放流程的第一层,核心逻辑是 "先缓存,超阈值再批量归还"

复制代码
// ThreadCache.cpp
void ThreadCache::Deallocate(void* ptr, size_t size) //释放到ThreadCache
{
    assert(ptr);
    assert(size <= MAX_BYTES);
    size_t index = SizeClass::Index(size);			 //计算内存块对应FreeList桶下标
    _freeList[index].push(ptr);						 //头插法将内存块归还给对应FreeList
    
    // 检查FreeList长度:超过慢启动阈值则批量释放到CentralCache
    if (_freeList[index].Size() >= _freeList[index].GetMaxSize())
    {
        ListTooLong(_freeList[index], size);		 //触发批量释放
    }
}
  • 核心规则:ThreadCache为每个规格的内存块维护独立的FreeList,释放的内存块先缓存到链表中
  • 慢启动机制:每个FreeList有动态阈值_maxSize,只有当缓存的内存块数量超过阈值时,才会调用ListTooLong批量释放到CentralCache,避免频繁的跨层交互

ListTooLong的逻辑是批量取出_maxSize个内存块,转发到CentralCache

复制代码
void ThreadCache::ListTooLong(FreeList& list, size_t size)
{
    void* start = nullptr;
    void* end = nullptr;
    list.rangePop(start, end, list.GetMaxSize()); //批量弹出内存块
    CentralCache::GetInstance()->ReleaseListToSpans(start, size); //转发到CentralCache
}

3.第二步:CentralCache 层的释放(全局缓存,桶锁保护)

CentralCache::ReleaseListToSpans是释放流程的核心中转层,负责将ThreadCache归还的内存块归还给对应的span,并在span完全空闲时归还到PageCache。核心逻辑拆解如下:

1. 加桶锁:保证并发安全
复制代码
// CentralCache.cpp
void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
    size_t index = SizeClass::Index(size);
    _spanList[index]._mutex.lock(); //加对应桶的细粒度锁,避免全局竞争
    while (start)
    {
        // 步骤1:取下一个待释放的内存块(避免遍历中链表断裂)
        void* next = NextObj(start);

        // 步骤2:通过内存块地址找到对应的span(核心:_idSpanMap映射)
        span* span = PageCache::GetInstance()->MapObjectToSpan(start);

        // 步骤3:将内存块头插回span的_freeList(归还小块内存)
        NextObj(start) = span->_freeList;
        span->_freeList = start;

        // 步骤4:span的使用计数-1(记录已归还的小块数量)
        span->_useCount--;

        // 步骤5:若span的所有小块都已归还,将span归还到PageCache
        if (span->_useCount == 0)
        {
            // 从CentralCache的桶中移除该span
            _spanList[index].Erase(span);
            span->_freeList = nullptr;
            span->_next = nullptr;
            span->_prev = nullptr;

            // 解锁桶锁→加PageCache全局锁→释放span→重新加桶锁(避免死锁)
            _spanList[index]._mutex.unlock();
            PageCache::GetInstance()->_pageMutex.lock();
            PageCache::GetInstance()->ReleaseSpanToPageCache(span);
            PageCache::GetInstance()->_pageMutex.unlock();
            _spanList[index]._mutex.lock();
        }

        start = next; //遍历下一个内存块
    }
    _spanList[index]._mutex.unlock(); //解锁桶锁
}
  • 桶锁的价值:CentralCache为每个规格维护独立的spanList桶,每个桶有专属_mutex,释放操作仅锁定当前桶,而非全局,最大化并发效率
  • 关键判断_useCount == 0:只有当span切分的所有小块内存都已归还时,才会将span归还给PageCache,保证span的 "完整复用"

4.第三步:PageCache 层的释放(页级缓存,合并碎片)

PageCache::ReleaseSpanToPageCache是释放流程的最后一层,核心目标是将空闲的span合并成更大的span,减少内存碎片,其核心逻辑是 "双向合并 + 首尾页映射更新"

复制代码
// PageCache.cpp
void PageCache::ReleaseSpanToPageCache(span* obj)
{
    // 1. 前向合并:查找当前span的前一页,判断是否可合并
    while (1)
    {
        PAGE_ID prevId = obj->_pageId - 1;
        auto ret = _idSpanMap.find(prevId);
        if (ret == _idSpanMap.end()) break; //前一页无映射,无法合并
        
        span* prevSpan = ret->second;
        if (prevSpan->_isUse == true) break; //前span正在使用,无法合并
        if (prevSpan->_n + obj->_n > MAX_PAGE_BUCKETS - 1) break; //合并后超最大页限制
        
        // 合并前span到当前span
        obj->_pageId = prevSpan->_pageId;
        obj->_n += prevSpan->_n;
        _spanlist[prevSpan->_n].Erase(prevSpan);
        delete prevSpan;
    }

    // 2. 后向合并:查找当前span的后一页,判断是否可合并(逻辑同前向)
    while (1)
    {
        PAGE_ID nextId = obj->_pageId + obj->_n;
        auto ret = _idSpanMap.find(nextId);
        if (ret == _idSpanMap.end()) break;
        
        span* nextSpan = ret->second;
        if (nextSpan->_isUse == true) break;
        if (nextSpan->_n + obj->_n > MAX_PAGE_BUCKETS - 1) break;
        
        // 合并后span到当前span
        obj->_n += nextSpan->_n;
        _spanlist[nextSpan->_n].Erase(nextSpan);
        delete nextSpan;
    }

    // 3. 将合并后的span加入PageCache的对应桶,并更新首尾页映射
    _spanlist[obj->_n].Push_front(obj);
    obj->_isUse = false; //标记为空闲
    _idSpanMap[obj->_pageId] = obj; //更新首页映射
    _idSpanMap[obj->_pageId + obj->_n - 1] = obj; //更新尾页映射
}
  • 双向合并:先合并前序空闲 span,再合并后序空闲 span,最大化减少碎片
  • 首尾页映射优化:仅更新合并后 span 的首尾页到_idSpanMap(而非全量页),因为空闲 span 不会被MapObjectToSpan访问(只有分配态 span 需要全量映射),大幅降低哈希表维护成本

5.释放流程总结

整个内存释放流程遵循 "分层缓存、按需合并、细粒度锁" 的核心设计:

  1. 优先释放到线程私有ThreadCache,无锁且高效
  2. 超过阈值后批量释放到CentralCache,桶锁保证并发安全
  3. span完全空闲时归还到PageCache,双向合并减少碎片
  4. 首尾页映射优化_idSpanMap,兼顾正确性与性能

如果代码进行到这里,说明我们写的高并发内存池已经完成了80%,接下来我们进行优化代码,就算完成这个项目啦~~

老样子,下面是总代码~~

6.全部代码

1.CentralCache.h
cpp 复制代码
#pragma once
#include "Common.h"

//单例模式(饿汉模式)
class CentralCache
{
public:
	//获取实例
	static CentralCache* GetInstance()
	{
		return &_sInst;
	}

	// 获取⼀个⾮空的span
	span* GetOneSpan(spanList& list, size_t size);

	// 从中⼼缓存获取⼀定数量的对象给thread cache
	size_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size);

	//将一点数量的对象释放到span跨度
	void ReleaseListToSpans(void* start, size_t byte_size);
private:
	spanList _spanList[MAX_BUCKETS]; //和ThreadCheche逻辑是一样的,对应字节找对应桶
private:
	CentralCache()
	{ }
	CentralCache(const CentralCache&) = delete;
	static CentralCache _sInst;
};
2.Common.h
cpp 复制代码
#pragma once
#include <iostream>
#include <vector>
#include <unordered_map>

#include <algorithm>
#include <time.h>
#include <assert.h>

#include <thread>
#include <mutex>

#ifdef _WIN32
	#include <windows.h>
#else
	//linux...
#endif

static const size_t MAX_BYTES = 262144;          //1024 * 256 -> 256kb //最大字节数
static const size_t MAX_BUCKETS = 208;           //最大_freeList桶数量
static const size_t MAX_PAGE_BUCKETS = 129;      //最大_pageList桶数量(128,但是是直接映射,没有0页这种说法,所以我们直接将128写为129,不要下标0)
static const size_t PAGE_SHIFT = 13;             //定义一页8k,也就是1024 * 8 = 2^13字节

//如果在32位下,假设每一个页大小是8k,也就是2^13,那么最多有2^32 / 2^13 = 2^19,只有524,288个
//size_t完全够用
//但是在64位下,每一个页大小是8k,那么就有2^64 / 2^13 = 5,070,602,400,912,917,605,986,812,821,504个
//size_t不够用,只能使用unsigned long long
#ifdef _WIN64
	using PAGE_ID = unsigned long long;
#elif _WIN32
	using PAGE_ID = size_t;
#endif

// 直接去堆上按⻚申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
		void* ptr = VirtualAlloc(0, kpage * (1 << PAGE_SHIFT), MEM_COMMIT | MEM_RESERVE,
			PAGE_READWRITE);
#else
		// linux下brk mmap等
#endif
		if (ptr == nullptr)
			throw std::bad_alloc();
		return ptr;
}

static void*& NextObj(void* obj)
{
	return *(void**)obj;
}

class FreeList //定义一个FreeList类,因为不管是哪个自由链表桶都是一样的结构
{
public:
	FreeList()
		: _freeList(nullptr)
		, _size(0)
		, _maxSize(1)
	{}

	void push(void* obj) //头插
	{
		assert(obj != nullptr);
		NextObj(obj) = _freeList;
		_freeList = obj;
		_size++;
	}

	void rangePush(void* start, void* end, size_t range) //范围头插
	{
		NextObj(end) = _freeList;
		_freeList = start;
		_size += range;
	}

	void* pop() //头删
	{
		assert(_freeList != nullptr);
		void* obj = _freeList;
		_freeList = NextObj(_freeList);
		_size--;
		return obj;
	}

	void rangePop(void*& start, void*& end, size_t range) //范围头删
	{
		assert(range <= _size);
		start = _freeList;
		end = start;
		for (size_t i = 0; i < range - 1; i++)
		{
			end = NextObj(end);
		}
		_freeList = NextObj(end);
		NextObj(end) = nullptr;
		_size -= range;
	}

	bool empty()
	{
		return _freeList == nullptr;
	}

	size_t& GetMaxSize()
	{
		return _maxSize;
	}

	size_t Size()
	{
		return _size;
	}
private:
	void* _freeList;
	size_t _size;
	size_t _maxSize;
};

class SizeClass //确定哪个桶
{
public:
	// 整体控制在最多10%左右的内碎⽚浪费
	// 字节数                     对齐数           桶的数量
	// [1,128]                  8byte对⻬        freelist[0,16) -> 16个桶
	// [128+1,1024]             16byte对⻬       freelist[16,72) -> 56个桶
	// [1024+1,8*1024]          128byte对⻬      freelist[72,128) -> 56个桶
	// [8*1024+1,64*1024]       1024byte对⻬     freelist[128,184) -> 56个桶
	// [64*1024+1,256*1024]     8*1024byte对⻬   freelist[184,208) -> 24个桶
	// 这样可以让桶的数量只有208个,比起每8个字节为一个桶节省了很多

	//普通写法
	/*
	static inline size_t _RoundUp(size_t bytes, size_t align) //对齐,比如7字节对齐8字节,9字节对齐16字节,129字节对齐144字节
	{
		if (bytes % align == 0)
		{
			return bytes; //无需对齐,因为已经对齐了
		}
		//2 -> 8     (2 / 8 + 1) * 8 -> 8
		//129 -> 144     (129 / 16 + 1) * 16 -> 144
		return (bytes / align + 1) * align;
	}
	*/
	//位运算写法
	static inline size_t _RoundUp(size_t bytes, size_t alignNum)
	{
		return (((bytes) + alignNum - 1) & ~(alignNum - 1));
	}

	static inline size_t RoundUp(size_t bytes) //对齐
	{
		assert(bytes <= MAX_BYTES);
		if (bytes <= 128)
		{
			return _RoundUp(bytes, 8);
		}
		else if (bytes <= 1024)
		{
			return _RoundUp(bytes, 16);
		}
		else if (bytes <= 8192)
		{
			return _RoundUp(bytes, 128);
		}
		else if (bytes <= 65536)
		{
			return _RoundUp(bytes, 1024);
		}
		else if (bytes <= 262144)
		{
			return _RoundUp(bytes, 8192);
		}
		else
		{
			assert(false);
			return -1;
		}
	}

	//普通写法
	/*
	static inline size_t _Index(size_t bytes, size_t alignNum) //计算映射的哪⼀个⾃由链表桶
	{
		if (bytes % align == 0)
			return bytes / align - 1;
		else
			return bytes / align;
	}
	*/
	//位运算写法
	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);
		if (bytes <= 128)
		{
			//return _Index(bytes, 8);
			return _Index(bytes, 3);
		}
		else if (bytes <= 1024)
		{
			//return _Index(bytes - 128, 16) + 16;
			return _Index(bytes - 128, 4) + 16;
		}
		else if (bytes <= 8192)
		{
			//return _Index(bytes - 1024, 128) + 72; //16 + 56
			return _Index(bytes - 1024, 7) + 72; //16 + 56
		}
		else if (bytes <= 65536)
		{
			//return _Index(bytes - 8192, 1024) + 128; //16 + 56 + 56
			return _Index(bytes - 8192, 10) + 128; //16 + 56 + 56
		}
		else if (bytes <= 262144)
		{
			//return _Index(bytes - 65536, 8192) + 184; //16 + 56 + 56 + 56
			return _Index(bytes - 65536, 13) + 184; //16 + 56 + 56 + 56
		}
		else
		{
			assert(false);
			return -1;
		}
	}

	// ⼀次从中⼼缓存获取多少个对象
	static size_t NumMoveSize(size_t size)
	{
		if (size == 0)
			return 0;
		// [2, 512],⼀次批量移动多少个对象的(慢启动)上限值
		// ⼩对象⼀次批量上限⾼
		// ⼩对象⼀次批量上限低
		int batchNum = MAX_BYTES / size;
		if (batchNum < 2)
			batchNum = 2;
		if (batchNum > 512)
			batchNum = 512;
		return batchNum;
	}

	//计算一次向PageCache获取几个页
	//单个对象 8byte
	//...
	//单个对象 256k
	static size_t NumMovePage(size_t size)
	{
		size_t num = NumMoveSize(size);
		size_t npage = num * size;
		npage >>= PAGE_SHIFT; //除以8k,也就是8 * 1024字节
		if (npage == 0)
			npage = 1;
		return npage;
	}
};

//管理多个连续页大块内存跨度结构
struct span
{
	PAGE_ID _pageId = 0;                //大块内存起始页的页号(比如一共有100页,这里的66就代表这是第66页的起始位置)
	size_t _n = 0;			            //页的数量(比如3代表这个span有三个页)

	span* _next = nullptr;              //双链表结构,便于插入删除
	span* _prev = nullptr;              

	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 Push_front(span* newspan)
	{
		Insert(Begin(), newspan);
	}

	span* Pop_front()
	{
		span* span = _head->_next;
		Erase(span);
		return span;
	}

	void Insert(span* pos, span* newspan) //插入
	{
		//span pos span -> span newspan pos span
		assert(pos);
		assert(newspan);
		newspan->_next = pos;
		newspan->_prev = pos->_prev;
		pos->_prev->_next = newspan;
		pos->_prev = newspan;
	}

	void Erase(span* pos) //删除
	{
		//span pos span -> span span
		assert(pos);
		assert(pos != _head);
		pos->_next->_prev = pos->_prev;
		pos->_prev->_next = pos->_next;
	}

	span* Begin()
	{
		return _head->_next;
	}

	span* End()
	{
		return _head;
	}

	bool Empty()
	{
		return _head->_next == _head;
	}
private:
	span* _head;
public:
	std::mutex _mutex; //桶锁
};
3.ConcurrentAlloc.h
cpp 复制代码
#pragma once
#include "Common.h"
#include "ThreadCache.h"

//用户层

static void* ConcurrentAlloc(size_t size)			//用户接口--申请内存
{
	if (pTLSThreadCache == nullptr)
	{
		pTLSThreadCache = new ThreadCache;
	}
	//std::cout << std::this_thread::get_id() << ":" << pTLSThreadCache << std::endl;
	return pTLSThreadCache->Allocate(size);
}

static void ConcurrentFree(void* ptr, size_t size)  //用户接口--释放内存
{
	assert(pTLSThreadCache);
	assert(ptr);
	assert(size <= MAX_BYTES);
	pTLSThreadCache->Deallocate(ptr, size);
}
4.PageCache.h
cpp 复制代码
#pragma once
#include "Common.h"

class PageCache
{
public:
	static PageCache* GetInstance()
	{
		return &_sInst;
	}

	span* NewSpan(size_t size);				//在PageList里面找有没有符合要求的_freeList的节点,没有就向系统申请一个大块内存

	span* MapObjectToSpan(void* obj);		//获取从对象到span的映射

	void ReleaseSpanToPageCache(span* obj); //将CentralCache里面的span还回PageCache

private:
	spanList _spanlist[MAX_PAGE_BUCKETS];   //定义128个桶
	std::unordered_map<PAGE_ID, span*> _idSpanMap;
public:
	std::mutex _pageMutex;					//定义一个大锁而不是桶锁
private:
	PageCache()
	{ }
	PageCache(const PageCache&) = delete;
	static PageCache _sInst;
};
5.ThreadCache.h
cpp 复制代码
#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);   //向中心缓存获取对象

	void ListTooLong(FreeList& list, size_t size);			  //释放对象的时候发现链表过长,就回收内存到中心缓存
private:
	FreeList _freeList[MAX_BUCKETS];						  //使用我们的分组方案,只需要分208组,就可以表示256kb以下情况
};

//_declspec(thread) 是MSVC(Windows)专属语法,标记变量为线程私有
//线程局部存储(TLS):_declspec(thread) 是为了让 pTLSThreadCache 成为线程私有变量,每个线程有独立副本,实现 ThreadCache 无锁访问
//个人解释:
//没有 _declspec(thread) 的问题:
//如果直接写 static ThreadCache* pTLSThreadCache = nullptr; 
//这个指针是全局共用的------所有线程都用这一个指针指向同一个 ThreadCache(内存池)
//多线程同时分配 / 释放内存时,就得加锁保护这个内存池,一锁就慢,高并发下性能直接垮掉(这也是系统 malloc 慢的原因之一)。
//加了_declspec(thread) 的好处:
//这个关键字就是告诉编译器:"给每个线程都单独复制一份 pTLSThreadCache 指针,每个线程的指针只归自己用"
//比如线程 A 有自己的 pTLSThreadCache,指向线程 A 专属的内存池;线程 B 有自己的 pTLSThreadCache,指向线程 B 专属的内存池
//线程 A 和 B 操作自己的内存池时,完全不用管对方,不用加锁,速度直接拉满(这就是 TCMalloc "高并发" 的核心)
//*************************
//一句话,无锁操作就是让每个线程先用地盘专属的小内存池(ThreadCache),不用抢全局大池,所以不用加锁
//只有小池空了,才去全局大池批量拿内存(仅锁对应桶),整体几乎无锁
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;
6.CentralCache.cpp
cpp 复制代码
#include "CentralCache.h"
#include "PageCache.h"

CentralCache CentralCache::_sInst;

// 获取⼀个⾮空的span
span* CentralCache::GetOneSpan(spanList& list, size_t size)
{
	span* it = list.Begin();
	while (it != list.End())
	{
		if (it->_freeList != nullptr)
		{
			return it;
		}
		it = it->_next;
	}
	//这里有两种解锁方法
	//1.把锁带入NewSpan()中
	//2.现在就解锁
	//我们选择2,如果只考虑申请,两种锁都可以,但是如果释放呢,那么第一种锁会让释放的线程拿不到锁而阻塞
	//而第二种因为已经把锁释放了,所以可以完成线程释放
	list._mutex.unlock();

	//在外部加入page全局锁
	PageCache::GetInstance()->_pageMutex.lock();
	//走到这里说明没有非空span,需要向pageCache申请了
	span* newspan = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
	newspan->_isUse = true;
	//完成函数解锁
	//为什么切分的时候不需要这个锁呢,因为此时线程切分的内容只有自己访问得到,不涉及共享资源,所以不需要加锁
	PageCache::GetInstance()->_pageMutex.unlock();

	//计算span的大块内存起始地址以及内存大小(字节数) -> 加起来就是结束地址
	//这里用char* 而不是void* 是因为char* 更适合字节加减(+1就是一字节)
	char* start = (char*)(newspan->_pageId << PAGE_SHIFT); //相当于 * 1024 * 8 (8k)
	size_t bytes = newspan->_n << PAGE_SHIFT;
	char* end = start + bytes;

	//把大块内存切成小块内存自由链表挂载到span->_freeList中
	//1.先切一个小块作为头节点
	newspan->_freeList = start;
	start += size;
	void* tail = newspan->_freeList;
	//2.切剩下的,尾插(也可以使用头插,不过尾插可以保证物理内存线性连续)
	while (start < end)
	{
		//头插
		NextObj(tail) = start;
		tail = NextObj(tail); //tail = start;
		//后移
		start += size;
	}

	//会访问共享资源,eg:
	//线程一找list里面有没有span,而线程儿二正在插入span
	//所以加上桶锁
	list._mutex.lock();
	list.Push_front(newspan); //头插到list中

	return newspan;
}

// 从中⼼缓存获取⼀定数量的对象给thread cache
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
	size_t index = SizeClass::Index(size);
	//加上桶锁避免线程竞争(互斥)
	_spanList[index]._mutex.lock();
	span* span = GetOneSpan(_spanList[index], size);
	assert(span);
	assert(span->_freeList);
	start = span->_freeList;
	end = start;
	size_t i = 0;
	size_t actualNum = 1;
	while (i < batchNum - 1 && NextObj(end) != nullptr)
	{
		end = NextObj(end);
		i++;
		actualNum++;
	}
	span->_freeList = NextObj(end);
	NextObj(end) = nullptr;
	span->_useCount += actualNum;

	_spanList[index]._mutex.unlock();
	return actualNum;
}

//将一定数量的对象释放到span跨度
//为什么要给字节数呢,当然是要算在哪一个桶
//但是我们的start里面可能存在多个span的节点,所以要用到哈希表让每一个节点精准释放到size映射的桶里面
void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
	size_t index = SizeClass::Index(size);
	_spanList[index]._mutex.lock();
	while (start)
	{
		void* next = NextObj(start);

		span* span = PageCache::GetInstance()->MapObjectToSpan(start); //获取从对象到span的映射

		//将对应映射的start内存头插还给span
		NextObj(start) = span->_freeList;
		span->_freeList = start;

		span->_useCount--;

		//说明span的切分出去的所有块都已经还回来了,可以返还给PageCache了
		if (span->_useCount == 0)
		{
			_spanList[index].Erase(span);
			span->_freeList = nullptr;
			span->_next = nullptr;
			span->_prev = nullptr;
			_spanList[index]._mutex.unlock();
			PageCache::GetInstance()->_pageMutex.lock();
			PageCache::GetInstance()->ReleaseSpanToPageCache(span);
			PageCache::GetInstance()->_pageMutex.unlock();
			_spanList[index]._mutex.lock();
		}

		start = next;
	}
	_spanList[index]._mutex.unlock();
}
7.PageCache.cpp
cpp 复制代码
#include "PageCache.h"

PageCache PageCache::_sInst;

//在PageList里面找有没有符合要求的_freeList的节点,没有就向系统申请一个大块内存
span* PageCache::NewSpan(size_t size) //这里的size是页数
{
	assert(size > 0 && size < MAX_PAGE_BUCKETS);
	//先直接看size桶有没有
	if (!_spanlist[size].Empty())
	{
		return _spanlist[size].Pop_front();
	}
	//size桶没有就往后找有的
	//如果找到直接切下来size大小返回,i - size插入i - size桶
	for (int i = size + 1; i < MAX_PAGE_BUCKETS; i++)
	{
		if (!_spanlist[i].Empty())
		{
			span* nspan = _spanlist[i].Pop_front();
			span* kspan = new span;
			//找页数大小为三的,发现100号桶有内存
			//那么切除为3块 与 100 - 3块
			//此时id 变为100与100 + 3,因为少了三块,往后移3块
			kspan->_n = size;
			kspan->_pageId = nspan->_pageId;

			nspan->_n -= size;
			nspan->_pageId += size;

			_spanlist[nspan->_n].Push_front(nspan);

			//存储nspan的首尾地址就可以(相当于挂接第103起始位置和对应的nspan)
			_idSpanMap[nspan->_pageId] = nspan;
			_idSpanMap[nspan->_pageId + nspan->_n - 1] = nspan;
			//建立id和span的映射,方便CentralCache回收小块内存时,查找对应的span
			for (size_t i = 0; i < kspan->_n; i++)
			{
				_idSpanMap[kspan->_pageId + i] = kspan;
			}

			return kspan;
		}
	}
	//如果没有符合的,那么就向堆(系统)申请一个大块内存(128 * 8k)
	span* bigspan = new span; //new只是创建一个span结构体对象(用来管理页的元数据,存_pageId、_n、_freeList等)
	void* ptr = SystemAlloc(MAX_PAGE_BUCKETS - 1); //申请内存
	bigspan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
	bigspan->_n = MAX_PAGE_BUCKETS - 1;
	_spanlist[bigspan->_n].Push_front(bigspan);
	//为什么选择递归掉自己而不是直接切分返回呢
	//1.现代计算机太快了,再遍历128次的时间损耗可以忽略不计
	//2.在这个微乎其微的时间损耗下,我们更偏向于进行代码复用
	return NewSpan(size);
}

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* obj)
{
	//向前合并
	while (1)
	{
		PAGE_ID prevId = obj->_pageId - 1;
		auto ret = _idSpanMap.find(prevId);
		if (ret == _idSpanMap.end())
		{
			break;
		}
		span* prevSpan = ret->second;
		if (prevSpan->_isUse == true)
		{
			break;
		}
		if (prevSpan->_n + obj->_n > MAX_PAGE_BUCKETS - 1)
		{
			break;
		}
		obj->_pageId = prevSpan->_pageId;
		obj->_n += prevSpan->_n;
		_spanlist[prevSpan->_n].Erase(prevSpan);
		delete prevSpan;
	}
	//向后合并
	while (1)
	{
		PAGE_ID nextId = obj->_pageId + obj->_n;
		auto ret = _idSpanMap.find(nextId);
		if (ret == _idSpanMap.end())
		{
			break;
		}
		span* nextSpan = ret->second;
		if (nextSpan->_isUse == true)
		{
			break;
		}
		if (nextSpan->_n + obj->_n > MAX_PAGE_BUCKETS - 1)
		{
			break;
		}
		obj->_n += nextSpan->_n;
		_spanlist[nextSpan->_n].Erase(nextSpan);
		delete nextSpan;
	}
	_spanlist[obj->_n].Push_front(obj);
	obj->_isUse = false;
	_idSpanMap[obj->_pageId] = obj;
	_idSpanMap[obj->_pageId + obj->_n - 1] = obj;
}
8.ThreadCache.cpp
cpp 复制代码
#include "ThreadCache.h"
#include "CentralCache.h"

//向中心缓存(central cache)申请内存对象
//申请到多个就先挂载(start->next到end, 因为start是要申请的内存结点)
//最终都返回start这个节点(一个节点,即申请的内存)
void* ThreadCache::FetchFromCentralCache(size_t index, size_t size)
{
	assert(index < MAX_BUCKETS);
	assert(size <= MAX_BYTES);
	//满开始反馈调节算法
	//最开始申请就给一,之后_freeList[index].GetMaxSize()++
	//最次也会保证即增长的同时也不会一次给太多
	size_t batchNum = min(_freeList[index].GetMaxSize(), SizeClass::NumMoveSize(size)); //这里使用的是_WIN32下的宏min,而不是algorithm
	if (batchNum == _freeList[index].GetMaxSize())
	{
		_freeList[index].GetMaxSize()++;
	}

	void* start = nullptr;
	void* end = nullptr;
	// 从中心缓存获取一定数量的对象给thread cache
	size_t actualNum = CentralCache::GetInstance()->FetchRangeObj(start, end, batchNum, size);
	assert(actualNum > 0);

	if (actualNum == 1) //只申请了一个,直接返回就可以
	{
		assert(start == end);
		return start;
	}
	else //申请了多个,就需要先将[NextObj(start), end]挂到_freeList[index]中,再返回start
	{
		_freeList[index].rangePush(NextObj(start), end, actualNum - 1);
		return start;
	}
}

void* ThreadCache::Allocate(size_t size)             //申请内存
{
	size_t align = SizeClass::RoundUp(size);         //对齐
	size_t index = SizeClass::Index(size);		     //映射
	if (_freeList[index].empty())				     //为空,向中心缓存(central cache)申请内存对象
	{
		return FetchFromCentralCache(index, align);
	}
	else										     //有的话直接返回节点就行
	{
		return _freeList[index].pop();
	}
}

void ThreadCache::Deallocate(void* ptr, size_t size) //释放内存
{
	assert(ptr);
	assert(size <= MAX_BYTES);
	size_t index = SizeClass::Index(size);			 //映射
	_freeList[index].push(ptr);						 //向指定映射的_freeList里面push
	if (_freeList[index].Size() >= _freeList[index].GetMaxSize())
	{
		ListTooLong(_freeList[index], size);		 //太长了就还给CentralCache
	}
}

void ThreadCache::ListTooLong(FreeList& list, size_t size)
{
	void* start = nullptr;
	void* end = nullptr;
	list.rangePop(start, end, list.GetMaxSize()); //删掉list.GetMaxSize()个
	CentralCache::GetInstance()->ReleaseListToSpans(start, size); //将一点数量的对象释放到span跨度
}
相关推荐
OxyTheCrack2 小时前
【C++】简述Observer观察者设计模式附样例(C++实现)
开发语言·c++·笔记·设计模式
小小unicorn2 小时前
[微服务即时通讯系统]3.服务端-环境搭建
数据库·c++·redis·微服务·云原生·架构
耶叶2 小时前
kotlin的修饰符
android·开发语言·kotlin
Vic101012 小时前
java的分布式协议
java·开发语言·分布式
格林威2 小时前
工业相机图像高速存储(C#版):先存内存,后批量转存方法,附堡盟 (Baumer) 相机实战代码!
开发语言·人工智能·数码相机·opencv·计算机视觉·c#·halcon
格林威2 小时前
工业相机图像高速存储(C++版):先存内存,后批量转存方法,附堡盟相机实战代码!
开发语言·c++·人工智能·数码相机·计算机视觉·视觉检测·堡盟相机
所谓伊人,在水一方3332 小时前
【Python数据科学实战之路】第6章 | 高级数据可视化:从统计洞察到交互叙事
开发语言·python·信息可视化
郝学胜-神的一滴2 小时前
力扣86题分隔链表:双链表拆解合并法详解
开发语言·数据结构·算法·leetcode·链表·职场和发展
愿天堂没有C++2 小时前
Pimpl 设计模式(指针指向实现)
开发语言·c++·设计模式