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

news2024/9/23 13:31:04

💓博主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;
}

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


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

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

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

相关文章

语义分割 | 基于 VGG16 预训练网络和 Segnet 架构实现迁移学习

Hi&#xff0c;大家好&#xff0c;我是源于花海。本文主要使用数据标注工具 Labelme 对猫&#xff08;cat&#xff09;和狗&#xff08;dog&#xff09;这两种训练样本进行标注&#xff0c;使用预训练模型 VGG16 作为卷积基&#xff0c;并在其之上添加了全连接层。基于标注样本…

客户大批量保密文件销毁,数据销毁新方案及实践 文件销毁 硬盘销毁 数据销毁 物料销毁

2023年春节前夕&#xff0c;青岛客户经理接到一个电话&#xff0c;韩国驻华机构想请我们做文件销毁&#xff0c;要求在2天内销毁800多箱纸文件。800多箱需要在短短两天内完成销毁&#xff0c;这一数字创下了淼一文件数据销毁自2009年以来的历史记录。单从业绩和营销角度看&…

提取视频中的某一帧画面,留住视频中的美好瞬间

你是否曾经被视频中的某一帧画面深深吸引&#xff0c;却又惋惜于无法将其永久保存&#xff1f;现在&#xff0c;有了我们【媒体梦工厂】&#xff0c;这一遗憾将成为过去&#xff0c;这个软件可以提取视频中的某一帧保存为图片&#xff0c;为你留住那些稍纵即逝的美好。 所需工…

使用 docker 搭建搭建私有仓库 ~ Registry

博客原文 文章目录 前言安装 docker让apt可以支持HTTPS将官方Docker库的GPG公钥添加到系统中将Docker库添加到APT里更新包列表为了确保修改生效&#xff0c;让新的安装从Docker库里获取&#xff0c;而不是从Ubuntu自己的库里获取&#xff0c;执行&#xff1a;安装 docker-ce配置…

《WebKit 技术内幕》学习之八(2):硬件加速机制

2 Chromium的硬件加速机制 2.1 GraphicsLayer的支持 GraphicsLayer对象是对一个渲染后端存储中某一层的抽象&#xff0c;同众多其他WebKit所定义的抽象类一样&#xff0c;在WebKit移植中&#xff0c;它还需要具体的实现类来支持该类所要提供的功能。为了完成这一功能&#x…

nodejs学习计划--(六)包管理工具

包管理工具 1. 介绍 包是什么 『包』英文单词是 package &#xff0c;代表了一组特定功能的源码集合包管理工具 管理『包』的应用软件&#xff0c;可以对「包」进行 下载安装 &#xff0c; 更新 &#xff0c; 删除 &#xff0c; 上传 等操作 借助包管理工具&#xff0c;可以快…

用graalvm将maven项目打包成可执行文件

概述&#xff1a;配置graalvm或者用graalvm打包springboot项目请看下面文章&#xff1a; Springboot3新特性&#xff1a;开发第一个 GraalVM 本机应用程序(完整教程)-CSDN博客 废话不多说&#xff0c;咱们开始用GraalVM打包maven项目。 第一步&#xff1a;引入依赖和插件 p…

C++ 设计模式之责任链模式

【声明】本题目来源于卡码网&#xff08;卡码网KamaCoder&#xff09; 【提示&#xff1a;如果不想看文字介绍&#xff0c;可以直接跳转到C编码部分】 【设计模式大纲】 【简介】 --什么是责任链模式&#xff08;第21种设计模式&#xff09; 责任链模式是⼀种行为型设计模式&am…

bean的一生

你曾读spring源码 “不知所云”、“绞尽脑汁”、“不知所措”嘛&#x1f923;&#x1f923;&#x1f923; 那这篇文章可能会对你有所帮助&#xff0c;小编尝试用简单、易懂的例子来模拟spring经典代码&#x1f449;Spring Bean生命周期及扩展点&#xff0c; 让你能够****轻松…

Word中插入公式并引用

1、如何插入公式 在word中,键入快捷键 “alt” + “=”,即可快速插入一个公式,并立即编辑。 2、利用表格框住公式 新建一个 1 行 3 列的表格,总宽度为页面宽度,第一个单元格和最后一个单元格都保持在 2.25cm,中间尽可能长。我设置的这个数值比较合理。 记住,要把表格…

mask transformer相关论文阅读

前面讲了mask-transformer对医学图像分割任务是非常适用的。本文就是总结一些近期看过的mask-transformer方面的论文。 因为不知道mask transformer是什么就看了一些论文。后来得出结论&#xff0c;应该就是生成mask的transformer就是mask transformer。 相关论文&#xff1a; …

数据结构OJ题——二叉树前序、中序遍历非递归实现(Java版)

二叉树前序、中序遍历非递归实现 前序非递归遍历实现中序非递归遍历实现 前序非递归遍历实现 题目&#xff1a; 二叉树前序遍历非递归实现 总体思路&#xff1a;用非递归的方式模拟递归遍历。 以下图为例&#xff1a; 图示详解&#xff1a; 代码实现&#xff1a; /*** Defi…

【4.LCD显示】蓝桥杯嵌入式一周拿奖速成系列

系列文章目录 蓝桥杯嵌入式系列文章目录(更多此系列文章可见) 文章目录 系列文章目录LCD显示一、官方例程讲解二、main.c --> LcdProcess总结 LCD显示 因为官方给了我们LCD的例程,所以很easy,我们照着套就行 LcdProcess() 一、官方例程讲解 二、main.c --> LcdProcess…

143基于matlab的2D平面桁架有限元分析

基于matlab的2D平面桁架有限元分析&#xff0c;可以改变材料参数&#xff0c;输出平面结构外形&#xff0c;各桁架应力&#xff0c;位移及作用力。可查看节点力&#xff0c;程序已调通&#xff0c;可直接运行。 143 matlab 平面桁架 有限元分析 桁架应力 (xiaohongshu.com)

ubuntu下docker卸载和重新安装

卸载&#xff1a;步骤一&#xff1a;停止Docker服务 首先&#xff0c;我们需要停止正在运行的Docker服务。打开终端&#xff0c;执行以下命令&#xff1a; sudo systemctl stop docker 步骤二&#xff1a;删除Docker安装包 接下来&#xff0c;我们需要删除已经安装的Docker软件…

《WebKit 技术内幕》学习之六(3): CSS解释器和样式布局

3 WebKit布局 3.1 基础 当WebKit创建RenderObject对象之后&#xff0c;每个对象是不知道自己的位置、大小等信息的&#xff0c;WebKit根据框模型来计算它们的位置、大小等信息的过程称为布局计算&#xff08;或者称为排版&#xff09;。 图描述了这一过程中涉及的主要WebKit…

全球机器人产业:技术创新驱动下的市场与竞争新态势

原创 | 文 BFT机器人 近年来&#xff0c;随着颠覆性技术创新的不断涌现、市场新需求的迅速崛起以及外部冲击的深远影响&#xff0c;机器人产业正经历着前所未有的变革。在技术领域&#xff0c;机器人技术不断突破&#xff0c;智能化、自主化、协同化水平日益提升&#xff1b;在…

防火墙综合拓扑接口配置

目录 1、先给Server1、Server2&#xff0c;PC1、Client1、Client2、PC2配置IP、掩码、 网关。 2、LSW1 3、Cloud1 4、FW2 5、 Web界面配置防火墙 6、测试 1、先给Server1、Server2&#xff0c;PC1、Client1、Client2、PC2配置IP、掩码、 网关。 2、LSW1 [Huawei]int g …

明天见!跨越“白酒+文旅+文创”赛道,密鉴品牌将大幅焕新

执笔 | 洪大大 编辑 | 扬 灵 过去的2023年&#xff0c;外部环境的变化叠加产业周期的调整&#xff0c;使得行业呈现出更强的挤压态势&#xff0c;在此背景下&#xff0c;白酒品牌期望对各方资源进行高效整合与充分联动&#xff0c;以此来应对行业周期调整并适应产业升级步伐…

松散子序列(第十四届蓝桥杯省赛PythonB组)

给定一个仅含小写字母的字符串 s&#xff0c;假设 s 的一个子序列 t 的第 i 个字符对应了原字符串中的第 pi 个字符。 我们定义 s 的一个松散子序列为&#xff1a;对于 i>1 总是有 pi−pi−1≥2。 设一个子序列的价值为其包含的每个字符的价值之和&#xff08;a∼z 分别为…