【项目日记(四)】第一层: 线程缓存的具体实现

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:项目日记-高并发内存池⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你做项目
  🔝🔝
开发环境: Visual Studio 2022


在这里插入图片描述

项目日记

  • 1. 前言
  • 2. ThreadCache结构设计
  • 3. 哈希桶中的内存对齐规则
  • 4. 线程缓存申请内存的全过程
  • 5. 线程缓存释放内存的全过程
  • 6. 对项目边角代码的拓展

1. 前言

由于此项目需要创建多个文件
所以我直接在.h文件中既放声明
也存放实现,减少文件的数量

本章重点:

本篇文章着重讲解ThreadCache线程
缓存结构的具体实现,包含内存对齐的
方法,申请/释放内存的函数以及向中心
缓存中索要/还回内存的函数!本篇文章
大多数都是代码实现,请大家耐心学习


2. ThreadCache结构设计

在上一章谈到,线程缓存是一个哈希桶结构
每个桶中存放的是自由链表(小块儿内存)

由于自由链表不止在ThreadCache中使用,所以定义一个shared.h存放所有文件共享的内容!

shared.h文件中的自由链表结构:

//管理切分好的小对象的自由链表
class FreeList
{
public:
	void Push(void* obj)
	{
		assert(obj);
		//头插
		*(void**)obj = _freeList;
		_freeList = obj;
		_size++;
	}
	void PushRange(void* start, void* end, size_t n)//范围型的插入
	{
		*(void**)end = _freeList;
		_freeList = start;
		_size += n;
	}
	void* Pop()
	{
		assert(_freeList);
		//头删
		void* obj = _freeList;
		_freeList = *(void**)obj;
		--_size;
		return obj;
	}
	void PopRange(void*& start, void*& end, size_t n)//start和end是输出型参数
	{
		assert(n <= _size);
		start = _freeList;
		end = start;
		for (int i = 0; i < n - 1; i++)
			end = *(void**)end;
		_freeList = *(void**)end;
		*(void**)end = nullptr;
		_size -= n;
	}
	bool Empty()
	{
		return _freeList == nullptr;
	}
	size_t& MaxSize()
	{
		return _maxSize;
	}
	size_t Size()
	{
		return _size;
	}
private:
	void* _freeList = nullptr;
	size_t _maxSize = 1;//记录ThreadCache一次性最多向CentralCache申请多少个对象
	size_t _size = 0;//记录自由链表中挂的内存个数
};

关于什么是自由链表的问题我们在
定长池的实现中已经讲解过了,如果
你不懂,简易从头开始看博客,学项目!

前置文章: 定长池的实现

然后在ThreadCache.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[208];
};

3. 哈希桶中的内存对齐规则

你可能会有以下问题:

  1. 为什么上面写的哈希桶个数是208?
  2. 要是遇见不规则的字节数该怎么办?

这里用到一个非常巧妙的方法,也就是内存对齐!比如想要申请1~7字节的内存,先把它对齐为8字节再进行后续的操作,这么做的原因有两个: 一是因为如果申请多少内存就给多少内存,那么我们需要的哈希桶的数量就太多了,不合理. 二是因为释放内存时比较方便

内存对齐的具体规则:

在这里插入图片描述
这里有一个问题: 为什么不都使用8字节对齐?

因为若使用8字节对齐,共256KB内存
一共要使用三万多个桶,也是不合理的
这样的对齐方式只用使用208个桶!

对齐规则的具体实现:

static inline size_t AlignUp(size_t size)//将size向上对齐
{
	if (size <= 128)
		return _AlignUp(size, 8);
	else if (size <= 1024)
		return _AlignUp(size, 16);
	else if (size <= 8 * 1024)
		return _AlignUp(size, 128);
	else if (size <= 64 * 1024)
		return _AlignUp(size, 1024);
	else if (size <= 256 * 1024)
		return _AlignUp(size, 8*1024);
	else
		return _AlignUp(size, 1 << PAGE_SHIFT);
}
static inline size_t _AlignUp(size_t size, size_t align_number)
{
	return (((size)+align_number - 1) & ~(align_number - 1));
}

4. 线程缓存申请内存的全过程

首先,要先把申请的内存进行内存对齐
然后再用这个对齐后的内存去找对应
的哈希桶结构,若这个桶中有小块儿内
存,那么直接将小块儿内存返回,若桶中
没有内存了,就去中心缓存中拿内存!

void* ThreadCache::Allocate(size_t size)
{
	assert(size <= MAX_BYTES);//申请的字节数不能大于了256KB
	size_t align_size = AlignmentRule::AlignUp(size);//计算对齐数
	size_t index = AlignmentRule::Index(size);//计算在哪个桶
	if (!_freeList[index].Empty())//若当前自由链表有资源则优先拿释放掉的资源
		return _freeList[index].Pop();
	else//自由链表没有就从中心缓存获取空间
		return ThreadCache::FetchFromCentralCache(index, align_size);
}

注:计算在哪个桶的函数会放在文章最后

当走到else语句,也就是要去中心缓存获取内存时,会有个问题:一次性拿几个小块儿内存过来?拿多了怕浪费,拿少了又怕要重新再来拿,这里采用的是慢启动的算法,第一次先拿一个,第二次再拿两个,以此类推.除此之外,小对象应该多拿,大对象应该少拿.所以拿的个数应该是这两个条件中较小的内一个

void* ThreadCache::FetchFromCentralCache(size_t index, size_t size)
{
	//采用慢开始的反馈调节算法,小对象给多一点,大对象给少一点
	size_t massNum = min(_freeList[index].MaxSize(),AlignmentRule::NumMoveSize(size));//向中心缓存获取多少个对象
	if (_freeList[index].MaxSize() == massNum)//慢增长,最开始一定是给1,不会一次性给它很多内存,若不断有size大小的内存需求,再慢慢多给你
		_freeList[index].MaxSize() += 1;
	void* begin = nullptr;
	void* end = nullptr;
	//需要massnum个对象,但是实际上不一定有这么多个,返回值为实际上获取到的个数
	size_t fact = CentralCache::GetInstance()->CentralCache::FetchRangeObj(begin,end,massNum,size);//要massmun个对象,每个对象的大小是size
	assert(fact != 0);
	if (fact == 1)
	{
		assert(begin == end);
		return begin;
	}
	else
	{
		//如果从中心缓存获取了多个,则将第一个返回给threadcachee,然后再将剩余的内存挂在threadcache的自由链表中
		_freeList[index].PushRange((*(void**)begin), end, fact - 1);
		return begin;
	}
	return nullptr;
}

注: 根据对象大小获取个数的函数放在文章最后
FetchRangeObj函数是中心缓存要关心的东西


5. 线程缓存释放内存的全过程

由于申请内存时已经内存对齐了,所以释放的内存肯定是已经对齐过的了,找到对应的桶,直接将这个小块儿内存挂在桶后面,若当换回来的内存在自由链表中的长度大于一次性向中心缓存申请的长度,就将内存还给中心缓存

void ThreadCache::Deallocate(void* ptr, size_t size)
{
	assert(ptr);
	assert(size <= MAX_BYTES);
	//找到对应的自由链表桶[[0,208]
	size_t index = AlignmentRule::Index(size);
	_freeList[index].Push(ptr);
	//当换回来的内存在自由链表中的长度大于一次性向中心缓存申请的长度,就将内存还给中心缓存
	if (_freeList[index].Size() >= _freeList[index].MaxSize())
		ListTooLong(_freeList[index],size);
}
void ThreadCache::ListTooLong(FreeList& list, size_t size)//大于等于一个批量内存就还回去一个批量,不一定全部还回去
{
	void* start = nullptr;
	void* end = nullptr;
	list.PopRange(start, end, list.MaxSize());//start和end是输出型参数
	CentralCache::GetInstance()->ReleaseListToSpans(start, size);
}

注:ReleaseListToSpans函数是中心缓存需要关心的


6. 对项目边角代码的拓展

这里存放的是shared.h文件中,存放
如何通过字节数来找到对应的桶的函数
以及慢增长函数的具体实现:

慢增长函数:

static const size_t MAX_BYTES = 256 * 1024; //最大内存限制,256KB
static const size_t N_FREE_LIST = 208; //哈希桶的数量

static size_t NumMoveSize(size_t size)//此函数代表从中心缓存取多少个对象(和慢增长对比)
{
	assert(size > 0);
	// [2, 512],一次批量移动多少个对象的(慢启动)上限值
	// 小对象一次批量上限高
	// 大对象一次批量上限低
	int num = MAX_BYTES / size; //256KB除单个对象大小
	if (num < 2)//最少给2个
		num = 2;
	if (num > 512)//最大给512个
		num = 512;
	return num;
}

通过字节数计算在哪个桶:

// 计算映射的哪一个自由链表桶
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
		return -1;
}
static inline size_t _Index(size_t bytes, size_t align_shift)
{
	return ((bytes + (1 << align_shift) - 1) >> align_shift) - 1;
}

对于边角的函数,我们了解它的原理即可
不需要完全掌握它是如何实现的!


🔎 下期预告:中心缓存的具体实现🔍

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity中URP下获取每一个额外灯数据

文章目录 前言一、我们先来看一下 SimpleLit 中的调用二、获取额外灯索引1、非移动平台2、非GLES平台3、大多数平台 三、获取额外灯数据 前言 在上一篇文章中&#xff0c;我们知道了URP下是怎么获取额外灯数量的。 Unity中URP下获取额外灯数量 在这篇文章中&#xff0c;我们…

场内基金出货是什么意思?出货和洗盘有什么区别?

场内基金出货是股市中常见的一种操作策略&#xff0c;指股市中的投资大户或者机构大量或者批次买入某只股票&#xff0c;并散发利好该股票的消息&#xff0c;导致该股票在短时间内股价升高&#xff0c;从而吸引投资散户购买该股票。等到股价上升到一定的阶段时&#xff0c;庄家…

nextjs中beforePopState使用

在某些情况下&#xff0c;希望监听popstate并在路由器对其进行操作之前执行某些操作。可以使用beforePopState。 在Next.js中&#xff0c;beforePopState是一个可选的生命周期函数&#xff0c;用于在浏览器的历史记录发生更改之前执行一些操作。具体来说&#xff0c;beforePopS…

两千字讲明白java中instanceof关键字的使用!

写在开头 在过往的内容中&#xff0c;我们讲了不少的Java关键字&#xff0c;比如final、static、this、super等等&#xff0c;Java中的关键字非常之多&#xff0c;下图是整理的关键字集合 而我们今天要学习的就是其中的instanceof关键字&#xff01; instanceof的定义 instanc…

k8s安全机制

安全机制&#xff1a;k8s的安全机制&#xff0c;分布式集群管理工具&#xff0c;就是容器编排。 安全机制的核心&#xff1a;API SERVER作为整个集群内部通信的中介&#xff0c;也是外控控制的入口。所有的安全机制都是围绕api server来进行设计&#xff1a; 请求api资源&#…

数据结构·单链表

不可否认的是&#xff0c;前几节我们讲解的顺序表存在一下几点问题&#xff1a; 1. 中间、头部的插入和删除&#xff0c;需要移动一整串数据&#xff0c;时间复杂度O(N) 2. 增容需要申请新空间&#xff0c;拷贝数据&#xff0c;释放旧空间。会有不小的消耗 3. 增容一般是2倍的增…

MySQL安装及可视化工具SQLyog下载

编程如画&#xff0c;我是panda&#xff01; 最近学习Web开发的时候要用到数据库&#xff0c;一开始下载的ZIP版本的&#xff0c;还得修改配置文件&#xff0c;挺麻烦的&#xff0c;后来发现可以直接使用msi版的安装包疯狂next&#xff0c;所以就出一期教程。 前言 MySQL 是一…

链表--104. 二叉树的最大深度/medium 理解度A

104. 二叉树的最大深度 1、题目2、题目分析3、复杂度最优解代码示例4、适用场景 1、题目 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,n…

22款奔驰GLS450升级香氛负离子车载香薰功能

奔驰原厂香氛系统激活原车自带系统&#xff0c;将香气加藏储物盒中&#xff0c;通过系统调节与出风口相结合&#xff0c;再将香味传达至整个车厢&#xff0c;达到净化车厢空气的效果&#xff0c;让整个车厢更加绿色健康&#xff0c;清新淡雅。星骏汇小许Xjh15863 产品功能&…

Vue3+Vite使用Puppeteer进行SEO优化(SSR+Meta)

1. 背景 【笑小枫】https://www.xiaoxiaofeng.com上线啦 资源持续整合中&#xff0c;程序员必备网站&#xff0c;快点前往围观吧~ 我的个人博客【笑小枫】又一次版本大升级&#xff0c;虽然知道没有多少访问量&#xff0c;但我还是整天没事瞎折腾。因为一些功能在Halo上不太好实…

MES管理系统计划排产有哪些重要作用

在当今制造业中&#xff0c;MES管理系统解决方案已经成为提高生产效率、优化资源利用以及提升企业整体运营水平的关键工具。尤其是在生产计划排产这一核心环节&#xff0c;MES管理系统发挥着无可替代的作用。通过精准的计划和调度&#xff0c;MES管理系统帮助企业实现生产过程的…

SAP EXCEL上传如何实现指定读取某一个sheet页(ALSM_EXCEL_TO_INTERNAL_TABLE)

如何读取指定的EXCEL sheet 页签&#xff0c;比如要读取下图中第二个输出sheet页签 具体实现方法如下&#xff1a; 拷贝标准的函数ALSM_EXCEL_TO_INTERNAL_TABLE封装成一个自定义函数ZCALSM_EXCEL_TO_INTERNAL_TABLE 在自定义函数导入参数页签新增一个参数SHEET_NAME 在源代码…

【word密码】Word 文档设置了只读为什么还能编辑?

有些朋友可能会遇到这种疑问&#xff0c;为什么我的Word文档设置了只读模式&#xff0c;还是可以编辑的&#xff0c;这是什么原因呢&#xff1f; 其实是因为大部分的只读模式&#xff0c;设置完成之后都是可以编辑的&#xff0c;但是当我们进行保存的时候就会发现&#xff0c;…

【边缘计算】TA的基本概念,以及TA的挑战和机遇

大家好&#xff0c;我是全栈小5&#xff0c;欢迎阅读文章&#xff01; 此篇是【话题达人】序列文章&#xff0c;这一次的话题是《边缘计算的挑战和机遇》 文章将以博主的角度进行讲述&#xff0c;理解和水平有限&#xff0c;不足之处&#xff0c;望指正。 目录 背景基本概念挑战…

软件游戏提示msvcp140.dll丢失的解决方法,全面分析msvcp140.dll文件

msvcp140.dll是Microsoft Visual C 2015 Redistributable的一部分&#xff0c;它包含了许多用于运行程序的函数和类库。当这个文件丢失或损坏时&#xff0c;依赖于该组件的应用程序可能无法正常启动&#xff0c;系统会弹出错误提示&#xff0c;告知用户找不到msvcp140.dll文件。…

【Linux】糟糕,是心动的感觉——与Linux的初次相遇

初识Linux 导言一、计算机的发展1.1 历史背景1.2 计算机的发明 二、操作系统2.1 什么是操作系统&#xff1f;2.2 操作系统的诞生2.3 操作系统的发展2.3.1 批处理系统的发展2.3.2 分时系统2.3.3 实时系统2.3.4 通用操作系统 2.4 UNIX操作系统2.4.1 UNIX的诞生2.4.2 UNIX的发展 2…

ESXI 本地和虚拟机之间可以自由复制和粘贴

文章目录 ESXI 本地和虚拟机之间可以自由复制和粘贴 ESXI 本地和虚拟机之间可以自由复制和粘贴 web访问esxi&#xff0c;然后&#xff1a; 1、右击新建的虚拟机&#xff0c;确保是在关机状态下&#xff0c;点击编辑设置 2. 找到 虚拟机选项→高级→常规→配置参数 3、点击添加…

Scrapy爬虫在新闻数据提取中的应用

Scrapy是一个强大的爬虫框架&#xff0c;广泛用于从网站上提取结构化数据。下面这段代码是Scrapy爬虫的一个例子&#xff0c;用于从新闻网站上提取和分组新闻数据。 使用场景 在新闻分析和内容聚合的场景中&#xff0c;收集和组织新闻数据是常见需求。例如&#xff0c;如果我…

❤css实用

❤ css实用 渐变色边框&#xff08;Gradient borders方法的汇总 5种-代码可直接下载&#xff09; 资源链接 https://download.csdn.net/download/weixin_43615570/88779950?spm1001.2014.3001.5503 给 border 设置渐变色是很常见的效果&#xff0c;实现这个效果有很多思路 1…

Python requests网络库源码分析(第三篇:通过学习异常模块,了解http协议)

前言 作者在requests包下&#xff0c;定义了exceptions模块&#xff0c;该模块中定义执行http请求过程中常见的错误&#xff0c;熟悉这些错误有助于我们写出健壮的业务程序&#xff0c;同时还能温习http的知识点&#xff0c;本文基于的requests版本为2.27.1 exceptions模块&…