高并发内存池 - 简化版 tcmalloc

目录

一、为什么我们需要内存池?

[1. 系统 malloc 到底慢在哪?](#1. 系统 malloc 到底慢在哪?)

[2. 内存池:池化技术的终极答案](#2. 内存池:池化技术的终极答案)

二、定长内存池

[1. 核心思想](#1. 核心思想)

[2. 关键 trick:用内存自身存指针](#2. 关键 trick:用内存自身存指针)

[3. 极简代码](#3. 极简代码)

[4. 性能碾压 new/delete](#4. 性能碾压 new/delete)

[三、高并发内存池三层架构(TCMalloc 核心)](#三、高并发内存池三层架构(TCMalloc 核心))

[四、第一层:Thread Cache ------ 无锁极速分配](#四、第一层:Thread Cache —— 无锁极速分配)

源码

[1. 设计精髓](#1. 设计精髓)

[2. 分配流程](#2. 分配流程)

[3. 释放流程](#3. 释放流程)

[4. SizeClass 对齐规则](#4. SizeClass 对齐规则)

[五、第二层:Central Cache ------ 全局调度器](#五、第二层:Central Cache —— 全局调度器)

源码

[1. 定位](#1. 定位)

[2. 为什么用桶锁?](#2. 为什么用桶锁?)

[3. 分配逻辑](#3. 分配逻辑)

[4. 回收逻辑](#4. 回收逻辑)

[六、第三层:Page Cache ------ 内存页总管](#六、第三层:Page Cache —— 内存页总管)

源码

[1. 核心能力](#1. 核心能力)

[2. 分配逻辑](#2. 分配逻辑)

[3. 合并逻辑](#3. 合并逻辑)

[七、为什么 TCMalloc 能吊打系统 malloc?](#七、为什么 TCMalloc 能吊打系统 malloc?)

[1. 无锁分配](#1. 无锁分配)

[2. 批量获取 + 慢启动](#2. 批量获取 + 慢启动)

[3. 精细对齐 + 低碎片](#3. 精细对齐 + 低碎片)

[4. 基数树优化](#4. 基数树优化)

[八、性能实测:我们的内存池 vs malloc](#八、性能实测:我们的内存池 vs malloc)

九、补充类


一、为什么我们需要内存池?

1. 系统 malloc 到底慢在哪?

平时写 C/C++,malloc/free

  • 每次申请都要走系统调用(Linux:brk/mmap;Windows:VirtualAlloc),开销巨大
  • 多线程下全局锁竞争,线程越多越卡
  • 频繁小块分配,产生大量内存碎片,最后 "内存够但申请不出"

一句话:malloc 是通用货,不是为高并发而生

2. 内存池:池化技术的终极答案

池化 = 提前批发、自己零售、用完回收。内存池就是:

  • 先向系统一次性申请一大块内存
  • 程序要内存,直接从池里拿
  • 释放内存,还给池子,不还给系统
  • 程序退出,池子统一归还系统

优势:

  • 少系统调用 → 速度起飞
  • 无锁 / 细粒度锁 → 高并发不卡
  • 规整切割 → 几乎无碎片

二、定长内存池

只分配固定大小内存 的内存池。它是高并发内存池的基础积木

1. 核心思想

  • 预先申请一大块连续内存
  • 切成固定大小的小格子
  • 自由链表管理空闲格子
  • 分配:摘链头;释放:头插回链
  • 全程 O (1),极快

2. 关键 trick:用内存自身存指针

小内存块前 4/8 字节(32/64 位)存下一个块地址,不额外占空间。

3. 极简代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <time.h>
using std::cout;
using std::endl;

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

// 定长内存池
//template<size_t N>
//class ObjectPool
//{};

// 直接去堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
	void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
	// linux下brk mmap等
#endif

	if (ptr == nullptr)
		throw std::bad_alloc();

	return ptr;
}

template<class T>
class ObjectPool
{
public:
	T* New()
	{
		T* obj = nullptr;

		// 优先把还回来内存块对象,再次重复利用
		if (_freeList)
		{
			void* next = *((void**)_freeList);
			obj = (T*)_freeList;
			_freeList = next;
		}
		else
		{
			// 剩余内存不够一个对象大小时,则重新开大块空间
			if (_remainBytes < sizeof(T))
			{
				_remainBytes = 128 * 1024;
				//_memory = (char*)malloc(_remainBytes);
				_memory = (char*)SystemAlloc(_remainBytes >> 13);
				if (_memory == nullptr)
				{
					throw std::bad_alloc();
				}
			}

			obj = (T*)_memory;
			size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
			_memory += objSize;
			_remainBytes -= objSize;
		}

		// 定位new,显示调用T的构造函数初始化
		new(obj)T;

		return obj;
	}

	void Delete(T* obj)
	{
		// 显示调用析构函数清理对象
		obj->~T();

		// 头插
		*(void**)obj = _freeList;
		_freeList = obj;
	}

private:
	char* _memory = nullptr; // 指向大块内存的指针
	size_t _remainBytes = 0; // 大块内存在切分过程中剩余字节数

	void* _freeList = nullptr; // 还回来过程中链接的自由链表的头指针
};

struct TreeNode
{
	int _val;
	TreeNode* _left;
	TreeNode* _right;

	TreeNode()
		:_val(0)
		, _left(nullptr)
		, _right(nullptr)
	{
	}
};

4. 性能碾压 new/delete

  • 无锁、无系统调用、无碎片
  • 高频小对象场景,速度快几倍到几十倍

三、高并发内存池三层架构(TCMalloc 核心)

真正的高并发内存池,靠三级缓存解决所有问题:

Thread Cache → Central Cache → Page Cache

这是 Google 工程师的神作,Go 语言内存分配直接抄它。

整体架构

bash 复制代码
线程1 → ThreadCache ──┐
线程2 → ThreadCache ──┼→ CentralCache → PageCache → 系统
线程3 → ThreadCache ──┘
层级 作用 管理粒度
Thread Cache 线程独享小内存 无锁 <256KB 小块
Central Cache 全局调度中心 桶锁(细粒度) 切好的小块
Page Cache 与系统交互 全局锁(极少触发) 以页为单位(8KB / 页)

四、第一层:Thread Cache ------ 无锁极速分配

源码

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 ListToLong(FreeList& list, size_t size);
private:
	FreeList _freeLists[NFREELIST];
};
// TLS thread local storage
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;

#include "ThreadCache.h"
#include "CentralCache.h"

void* ThreadCache::FetchFromCentralCache(size_t index, size_t size)
{
	size_t batchNum = min(_freeLists[index].MaxSize(), SizeClass::NumMoveSize(size));
	if (_freeLists[index].MaxSize() == 1)
	{
		_freeLists[index].MaxSize() += 1;
	}
	void* start = nullptr;
	void* end = nullptr;
	size_t actualNum = CentralCache::GetInstrance()->FetchRangeObj(start, end, batchNum, size);

	if (actualNum == 1)
	{
		return start;
	}
	else
	{
		_freeLists[index].PushRange(NextObj(start), end, actualNum - 1);
		return start;
	}
}

void* ThreadCache::Allocate(size_t size)
{
	size_t alignSize = SizeClass::RoundUp(size);
	size_t index = SizeClass::Index(size);
	if (!_freeLists[index].Empty())
	{
		return _freeLists[index].Pop();
	}
	else
	{
		return FetchFromCentralCache(index, alignSize);
	}
}
void ThreadCache::Deallocate(void* ptr, size_t size)
{
	size_t index = SizeClass::Index(size);
	_freeLists[index].Push(ptr);

	if (_freeLists[index].Size() >= _freeLists[index].MaxSize())
	{
		ListToLong(_freeLists[index], size);
	}
}

void ThreadCache::ListToLong(FreeList& list, size_t size)
{
	void* start = nullptr;
	void* end = nullptr;
	list.PopRange(start, end, list.MaxSize());

	CentralCache::GetInstrance()->ReleaseListToSpans(start, size);
}

1. 设计精髓

  • 每个线程一个独立 Thread Cache
  • 哈希桶管理不同大小的自由链表
  • 8B、16B、24B ...... 256KB,一共 208 个桶
  • 申请 / 释放完全不加锁 → 并发性能爆炸

2. 分配流程

  • 申请 size ≤256KB
  • 计算对齐大小 → 找到对应桶
  • 桶里有块 → 直接拿走,O (1)
  • 桶空了 → 找 Central Cache 批量拿一批

3. 释放流程

  • 计算桶 → 头插回链表
  • 链表太长 → 回收一部分给 Central Cache
  • 全程无锁,快到离谱

4. SizeClass 对齐规则

控制内碎片在 10% 以内,行业标准:

  • 1,128\] → 8B 对齐

  • 1025,8K\] → 128B 对齐

  • 64K+1,256K\] → 8KB 对齐

源码

cpp 复制代码
#pragma once 

#include "Common.h"

class CentralCache
{
public:
	static CentralCache* GetInstrance()
	{
		return &_sInt;
	}
	Span* GetOneSpan(SpanList& list, size_t byte_size);
	
	size_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size);

	void ReleaseListToSpans(void* start, size_t byte_size);
private:
	SpanList _spanLists[NFREELIST];
private:
	CentralCache()
	{ }
	CentralCache(const CentralCache&) = delete;
	static CentralCache _sInt;
};

#include "CentralCache.h"
#include "PageCache.h"

CentralCache CentralCache::_sInt;

Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
	Span* it = list.Begin();
	while (it != list.End())
	{
		if (it->_freeList != nullptr)
		{
			return it;
		}
		else
		{
			it = it->_next;
		}
	}
	list._mtx.unlock();

	PageCache::GetInstance()->_pageMtx.lock();
	Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
	span->_isUse = true;
	PageCache::GetInstance()->_pageMtx.unlock();

	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;
	int i = 1;
	while (start < end)
	{
		i++;
		NextObj(tail) = start;
		tail = NextObj(tail);
		start += size;
	}

	list._mtx.lock();
	list.PushFront(span);

	return span;


}
size_t CentralCache::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);

	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;
	_spanLists[index]._mtx.unlock();

	return actualNum;
}

void CentralCache::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--;
		if (span->_useCount == 0)
		{
			_spanLists[index].Erase(span);
			span->_freeList = nullptr;
			span->_next = nullptr;
			span->_prev = nullptr;

			_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();
}

1. 定位

  • 所有线程共享
  • 每个桶对应一个 Span 链表
  • Span = 一段连续页,切成固定大小小块

2. 为什么用桶锁?

  • 不是全局一把大锁
  • 每个桶一把锁
  • 只有 Thread Cache 空了才来抢
  • 竞争极低,几乎无阻塞

3. 分配逻辑

  1. Thread Cache 来批量要内存
  2. 找对应桶的 Span,切出一批给它
  3. 所有 Span 都空 → 找 Page Cache 要新 Span
  4. 给出去一个对象,use_count++

4. 回收逻辑

  1. 回收对象 → use_count--
  2. use_count == 0 → 所有对象都回来了
  3. 把 Span 还给 Page Cache,等待合并

六、第三层:Page Cache ------ 内存页总管

源码

cpp 复制代码
#pragma once

#include "Common.h"
#include "ObjectPool.h"

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

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

	// 释放空闲span回到Pagecache,并合并相邻的span
	void ReleaseSpanToPageCache(Span* span);

	// 获取一个K页的span
	Span* NewSpan(size_t k);

	std::mutex _pageMtx;
private:
	SpanList _spanLists[NPAGES];
	ObjectPool<Span> _spanPool;

	//std::unordered_map<PAGE_ID, Span*> _idSpanMap;
	std::map<PAGE_ID, Span*> _idSpanMap;

	PageCache()
	{
	}
	PageCache(const PageCache&) = delete;


	static PageCache _sInst;
};

#include "PageCache.h"

PageCache PageCache::_sInst;

// 获取一个K页的span
Span* PageCache::NewSpan(size_t k)
{
	assert(k > 0);

	// 大于128 page的直接向堆申请
	if (k > NPAGES - 1)
	{
		void* ptr = SystemAlloc(k);
		//Span* span = new Span;
		Span* span = _spanPool.New();

		span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
		span->_n = k;

		_idSpanMap[span->_pageId] = span;
		return span;
	}

	// 先检查第k个桶里面有没有span
	if (!_spanLists[k].Empty())
	{
		Span* kSpan = _spanLists[k].PopFront();

		// 建立id和span的映射,方便central cache回收小块内存时,查找对应的span
		for (PAGE_ID i = 0; i < kSpan->_n; ++i)
		{
			_idSpanMap[kSpan->_pageId + i] = kSpan;
		}

		return kSpan;
	}

	// 检查一下后面的桶里面有没有span,如果有可以把他它进行切分
	for (size_t i = k + 1; i < NPAGES; ++i)
	{
		if (!_spanLists[i].Empty())
		{
			Span* nSpan = _spanLists[i].PopFront();
			//Span* kSpan = new Span;
			Span* kSpan = _spanPool.New();

			// 在nSpan的头部切一个k页下来
			// k页span返回
			// nSpan再挂到对应映射的位置
			kSpan->_pageId = nSpan->_pageId;
			kSpan->_n = k;

			nSpan->_pageId += k;
			nSpan->_n -= k;

			_spanLists[nSpan->_n].PushFront(nSpan);
			// 存储nSpan的首位页号跟nSpan映射,方便page cache回收内存时
			// 进行的合并查找
			_idSpanMap[nSpan->_pageId] = nSpan;
			_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;


			// 建立id和span的映射,方便central cache回收小块内存时,查找对应的span
			for (PAGE_ID i = 0; i < kSpan->_n; ++i)
			{
				_idSpanMap[kSpan->_pageId + i] = kSpan;
			}

			return kSpan;
		}
	}

	// 走到这个位置就说明后面没有大页的span了
	// 这时就去找堆要一个128页的span
	//Span* bigSpan = new Span;
	Span* bigSpan = _spanPool.New();
	void* ptr = SystemAlloc(NPAGES - 1);
	bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
	bigSpan->_n = NPAGES - 1;

	_spanLists[bigSpan->_n].PushFront(bigSpan);

	return NewSpan(k);
}

Span* PageCache::MapObjectToSpan(void* obj)
{
	PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);

	std::unique_lock<std::mutex> lock(_pageMtx);

	auto ret = _idSpanMap.find(id);
	if (ret != _idSpanMap.end())
	{
		return ret->second;
	}
	else
	{
		assert(false);
		return nullptr;
	}
}

void PageCache::ReleaseSpanToPageCache(Span* span)
{
	// 大于128 page的直接还给堆
	if (span->_n > NPAGES - 1)
	{
		void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
		SystemFree(ptr);
		//delete span;
		_spanPool.Delete(span);

		return;
	}

	// 对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;
		_spanPool.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;
		_spanPool.Delete(nextSpan);
	}

	_spanLists[span->_n].PushFront(span);
	span->_isUse = false;
	_idSpanMap[span->_pageId] = span;
	_idSpanMap[span->_pageId + span->_n - 1] = span;
}

1. 核心能力

  • 直接和操作系统打交道
  • 管理以页为单位的内存(1 页 = 8KB)
  • 合并相邻空闲页,根治外碎片

2. 分配逻辑

  1. Central Cache 要 k 页
  2. 找 k 页的 Span,有就给
  3. 没有 → 找更大的页,分裂
  4. 都没有 → 向系统申请 128 页大 Span

3. 合并逻辑

  • 回收 Span 时,向前 / 向后找相邻空闲 Span
  • 能合并就合并,小页→大页
  • 彻底解决内存碎片

七、为什么 TCMalloc 能吊打系统 malloc?

1. 无锁分配

90% 小对象在 Thread Cache 搞定,全程无锁。多线程场景下,性能差距指数级拉开。

2. 批量获取 + 慢启动

像 TCP 拥塞控制:

  • 小对象一次多拿点
  • 大对象一次少拿点
  • 平衡速度与内存占用

3. 精细对齐 + 低碎片

内碎片控制 <10%,外碎片靠 Page 合并几乎消灭。

4. 基数树优化

基数树代替哈希表管理页号→Span 映射:

  • 更快
  • 更省内存
  • 64 位系统无压力

八、性能实测:我们的内存池 vs malloc

4 线程,每轮 10000 次 malloc/free,跑 10 轮:

  • malloc 总耗时:164 ms
  • 我们的并发内存池:40 ms
  • 提速 3~10 倍,线程越多差距越大

九、补充类

cpp 复制代码
#pragma once

#include <iostream>
#include <vector>
#include <unordered_map>
#include <map>
#include <algorithm>

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

#include <thread>
#include <mutex>
#include <atomic>

using std::cout;
using std::endl;

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

static const size_t MAX_BYTES = 256 * 1024;
static const size_t NFREELIST = 208;
static const size_t NPAGES = 129;
static const size_t PAGE_SHIFT = 13;

#ifdef _WIN64
typedef unsigned long long PAGE_ID;
#elif _WIN32
typedef size_t PAGE_ID;
#else
// linux
#endif

// 直接去堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
	void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
	// linux下brk mmap等
#endif

	if (ptr == nullptr)
		throw std::bad_alloc();

	return ptr;
}


inline static void SystemFree(void* ptr)
{
#ifdef _WIN32
	VirtualFree(ptr, 0, MEM_RELEASE);
#else
	// sbrk unmmap等
#endif
}

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


// 管理切分好的小对象的自由链表
class FreeList
{
public:
	void Push(void* obj)
	{
		assert(obj);

		// 头插
		//*(void**)obj = _freeList;
		NextObj(obj) = _freeList;
		_freeList = obj;

		++_size;
	}

	void PushRange(void* start, void* end, size_t n)
	{
		NextObj(end) = _freeList;
		_freeList = start;

		// 测试验证+条件断点
		/*int i = 0;
		void* cur = start;
		while (cur)
		{
			cur = NextObj(cur);
			++i;
		}

		if (n != i)
		{
			int x = 0;
		}*/

		_size += n;
	}

	void PopRange(void*& start, void*& end, size_t n)
	{
		assert(n <= _size);
		start = _freeList;
		end = start;

		for (size_t i = 0; i < n - 1; ++i)
		{
			end = NextObj(end);
		}

		_freeList = NextObj(end);
		NextObj(end) = nullptr;
		_size -= n;
	}

	void* Pop()
	{
		assert(_freeList);

		// 头删
		void* obj = _freeList;
		_freeList = NextObj(obj);
		--_size;

		return obj;
	}

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

	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)

	/*size_t _RoundUp(size_t size, size_t alignNum)
	{
		size_t alignSize;
		if (size % alignNum != 0)
		{
			alignSize = (size / alignNum + 1)*alignNum;
		}
		else
		{
			alignSize = size;
		}

		return alignSize;
	}*/
	// 1-8 
	static inline size_t _RoundUp(size_t bytes, size_t alignNum)
	{
		return ((bytes + alignNum - 1) & ~(alignNum - 1));
	}

	static inline size_t RoundUp(size_t size)
	{
		if (size <= 128)
		{
			return _RoundUp(size, 8);
		}
		else if (size <= 1024)
		{
			return _RoundUp(size, 16);
		}
		else if (size <= 8 * 1024)
		{
			return _RoundUp(size, 128);
		}
		else if (size <= 64 * 1024)
		{
			return _RoundUp(size, 1024);
		}
		else if (size <= 256 * 1024)
		{
			return _RoundUp(size, 8 * 1024);
		}
		else
		{
			return _RoundUp(size, 1 << PAGE_SHIFT);
		}
	}

	/*size_t _Index(size_t bytes, size_t alignNum)
	{
	if (bytes % alignNum == 0)
	{
	return bytes / alignNum - 1;
	}
	else
	{
	return bytes / alignNum;
	}
	}*/

	// 1 + 7  8
	// 2      9
	// ...
	// 8      15

	// 9 + 7 16
	// 10
	// ...
	// 16    23
	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);

		// 每个区间有多少个链
		static int group_array[4] = { 16, 56, 56, 56 };
		if (bytes <= 128) {
			return _Index(bytes, 3);
		}
		else if (bytes <= 1024) {
			return _Index(bytes - 128, 4) + group_array[0];
		}
		else if (bytes <= 8 * 1024) {
			return _Index(bytes - 1024, 7) + group_array[1] + group_array[0];
		}
		else if (bytes <= 64 * 1024) {
			return _Index(bytes - 8 * 1024, 10) + group_array[2] + group_array[1] + group_array[0];
		}
		else if (bytes <= 256 * 1024) {
			return _Index(bytes - 64 * 1024, 13) + group_array[3] + group_array[2] + group_array[1] + group_array[0];
		}
		else {
			assert(false);
		}

		return -1;
	}

	// 一次thread cache从中心缓存获取多少个
	static size_t NumMoveSize(size_t size)
	{
		assert(size > 0);

		// [2, 512],一次批量移动多少个对象的(慢启动)上限值
		// 小对象一次批量上限高
		// 小对象一次批量上限低
		int num = MAX_BYTES / size;
		if (num < 2)
			num = 2;

		if (num > 512)
			num = 512;

		return num;
	}

	// 计算一次向系统获取几个页
	// 单个对象 8byte
	// ...
	// 单个对象 256KB
	static size_t NumMovePage(size_t size)
	{
		size_t num = NumMoveSize(size);
		size_t npage = num * size;

		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;
	}

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

	Span* End()
	{
		return _head;
	}

	bool Empty()
	{
		return _head->_next == _head;
	}

	void PushFront(Span* span)
	{
		Insert(Begin(), span);
	}

	Span* PopFront()
	{
		Span* front = _head->_next;
		Erase(front);
		return front;
	}

	void Insert(Span* pos, Span* newSpan)
	{
		assert(pos);
		assert(newSpan);

		Span* prev = pos->_prev;
		// prev newspan pos
		prev->_next = newSpan;
		newSpan->_prev = prev;
		newSpan->_next = pos;
		pos->_prev = newSpan;
	}

	void Erase(Span* pos)
	{
		assert(pos);
		assert(pos != _head);

		// 1、条件断点
		// 2、查看栈帧
		/*if (pos == _head)
		{
		int x = 0;
		}*/

		Span* prev = pos->_prev;
		Span* next = pos->_next;

		prev->_next = next;
		next->_prev = prev;
	}

private:
	Span* _head;
public:
	std::mutex _mtx; // 桶锁
};

#pragma once

#include "Common.h"
#include "ThreadCache.h"
#include "PageCache.h"
#include "ObjectPool.h"

static void* ConcurrentAlloc(size_t size)
{
	if (size > MAX_BYTES)
	{
		size_t alignSize = SizeClass::RoundUp(size);
		size_t kpage = alignSize >> PAGE_SHIFT;

		PageCache::GetInstance()->_pageMtx.lock();
		Span* span = PageCache::GetInstance()->NewSpan(kpage);
		span->_objSize = size;
		PageCache::GetInstance()->_pageMtx.unlock();

		void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
		return ptr;
	}
	else
	{
		// 通过TLS 每个线程无锁的获取自己的专属的ThreadCache对象
		if (pTLSThreadCache == nullptr)
		{
			static ObjectPool<ThreadCache> tcPool;
			//pTLSThreadCache = new ThreadCache;
			pTLSThreadCache = tcPool.New();
		}

		//cout << std::this_thread::get_id() << ":" << pTLSThreadCache << endl;

		return pTLSThreadCache->Allocate(size);
	}
}

static void ConcurrentFree(void* ptr)
{
	Span* span = PageCache::GetInstance()->MapObjectToSpan(ptr);
	size_t size = span->_objSize;

	if (size > MAX_BYTES)
	{
		PageCache::GetInstance()->_pageMtx.lock();
		PageCache::GetInstance()->ReleaseSpanToPageCache(span);
		PageCache::GetInstance()->_pageMtx.unlock();
	}
	else
	{
		assert(pTLSThreadCache);
		pTLSThreadCache->Deallocate(ptr, size);
	}
}
相关推荐
少司府2 小时前
C++基础入门:内存管理
c语言·开发语言·c++·内存管理·delete·new·malloc
郝学胜-神的一滴2 小时前
从零起步:CMake基础入门与实战跨平台编译
c++·软件工程·软件构建·cmake
charlie1145141913 小时前
嵌入式现代C++工程实践——第14篇:第二次重构 —— 模板登场,编译时绑定端口和引脚
开发语言·c++·stm32·安全·重构
同勉共进3 小时前
并发编程核心概念辨析
c++·cpu·内存屏障·缓存一致性·memory order
良木生香3 小时前
【C++初阶】C++编程基石:编码表&&STL的入门指南
c语言·开发语言·数据结构·c++·算法
并不喜欢吃鱼3 小时前
从零开始C++----四.vector的使用与底层实现
开发语言·c++
沐雪轻挽萤3 小时前
17. C++17新特性-并行算法 (Parallel Algorithms)
java·开发语言·c++
A7bert7774 小时前
【YOLOv8部署至RDK X5】模型训练→转换bin→Sunrise 5部署
c++·人工智能·python·深度学习·yolo·机器学习
EllinY4 小时前
扩展欧几里得算法 exgcd 详解
c++·笔记·数学·算法·exgcd