高并发内存池(六):补充内容

news2024/9/29 20:40:50

目录

有关大于256KB内存的申请和释放处理方法 

处理大于256KB的内存申请

补充内容1

补充内容2

补充内容3 

处理大于256KB的内存释放

新增内容1

新增内容2

测试函数

使用定长内存池替代new

释放对象时不传对象大小

补充内容1

补充内容2

 补充内容3

补充内容4

测试函数

为哈希桶加锁

多线程环境下对比malloc测试

复杂问题的调试技巧

性能瓶颈分析

针对性能瓶颈使用基数树进行优化

基数树代码


有关大于256KB内存的申请和释放处理方法 

内存申请和释放大于256KB也分为两种情况:

  1. 大于256KB但是小于128 * 8 * 1024KB
  2. 大于128 * 8 * 1024KB

内存申请大于256KB但小于128 * 8 * 1024KB:因为PageCache中存放的span所管理的最大页数为128,即span可分配的最大内存为128 * 8 * 1024 KB,所以当可以直接向PageCache申请一个合适的span(该span管理的页数为此次内存申请对齐后大小 / 页大小)

内存申请大于128 * 8 *1024 KB:PageCache中已经没有合适的span了,直接向堆上申请

处理大于256KB的内存申请

补充内容1

在RoundUp函数中新增用于处理大于256KB内存申请的内存对齐判断

(_RoundUp函数不变)

//用于内存对齐
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);//对齐数为当前规定的页的大小
	}
}

补充内容2

在NewSpan开始处中新增获取管理页数大于128的span的判断

//所需页数大于128PageCache无法分配,需要向堆申请
if (k > NPAGES - 1)
{
	void* ptr = SystemAlloc(k);//从堆获取一块内存

	Span* span = new Span;//申请一个span结点

	span->_PageId = (size_t)ptr >> PAGE_SHIFT;//新span中的页号为ptr指向的内存地址 / 页大小
	span->_n = k;//新span中的页数为n个

	_idSpanMap[span->_PageId] = span;//存放映射关系,即使该span不会被挂在PageCache上
	return span;
}

补充内容3 

在ConcurrentAlloc函数新增size > MAX_BYTES的判断

//申请内存
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);//向PageCache申请管理kpage个页的span,若kpage > 128则需要经过补充内容2中的内容,否则还是按照原NewSpan执行
	    void* ptr = (void*)(span->_PageId << PAGE_SHIFT);//通过页号计算地址
	    PageCache::GetInstance()->_pageMtx.unlock();

	    return ptr;//返回从PageCache分配给的内存空间的地址
    }
	else
	{
	    ...//获取TLS的那部分内容
	}
}

处理大于256KB的内存释放

新增内容1

在ReleaseSpanToPageCache开始处新增当归还的span的_n > 128的判断

//大于128页的span直接还给堆
if (span->_n > NPAGES - 1)
{
	void* ptr = (void*)(span->_PageId << PAGE_SHIFT);
	SystemFree(ptr);
	delete span;
	return; 
}

新增内容2

在ConcurrentFree中新增用于size > MAX_BYTES的判断

//释放内存
static void ConcurrentFree(void* ptr,size_t size)
{
	if (size > MAX_BYTES)
	{
		Span* span = PageCache::GetInstance()->MapObjectToSpan(ptr);//根据归还的内存地址获取要归还的span

		PageCache::GetInstance()->_pageMtx.lock();
		PageCache::GetInstance()->ReleaseSpanToPageCache(span);//将要归还的span挂在PageCache上,或者返还给堆
		PageCache::GetInstance()->_pageMtx.unlock();
	}
	else
	{
        ...//还是原来的那两行释放内容
	}
}

测试函数

//用于测试内存申请和释放大于256KB
void BigAlloc()
{
    //内存申请和释放大于256KB,但是小于128 * 8 * 1024KB
	void* p1 = ConcurrentAlloc(257 * 1024);
	ConcurrentFree(p1, 257 * 1024);

    //内存申请和释放大于128 * 8 * 1024KB
	void* p2 = ConcurrentAlloc(129 * 8 * 1024);
	ConcurrentFree(p2, 129 * 8 * 1024);
}

使用定长内存池替代new

基本概念:定长内存池在申请内存时是直接向堆申请的,没有使用malloc,效率得到提升,而目前我们在本项目中用到到了很多new的操作,其本质还是malloc,因此我们要用定长内存池中的New()和Delete()函数来代替new和delete,进行内存申请和释放,从而提高程序执行效率 

准备工作:在某个涉及new或者delete的类中新增ObjectPool< ?>类型的私有成员变量,下面以PageCache为例

class PageCache
{
public:
    ...
private:
    ...
	ObjectPool<Span> _spanPool;//<>中的类型根据需要进行更改
    ...
};

替换方式:将PageCache.cpp中所有使用new的地方都换成_spanPool.New(),将所有使用delet的位置都换为_spanPool.Delete( ? )(?表示要删除的对象的名称可能是span也可能是kspan之类的)

//Span* kSpan = new Span;
Span* kSpan = _spanPool.New();

//delete span;
_spanPool.Delete(span);

易忽略位置

1、ConcurrentAlloc.h中的new ThreadCache

//pTLSThreadCache = new ThreadCache;
static ObjectPool<ThreadCache> tcPool;//static修饰保证只在当前文件中可以被访问
pTLSThreadCache = tcPool.New();

!!!重点!!! 

注意事项:pTLSThreadCache是每个线程独有的一个对象,但是为其申请空间的tcPool不是,它是一个静态的对象,整个进程中独一份,被当前进程中的所有线程共享,多线程情况下会出现线程安全问题,所以这里也需要加锁(不加的话有小概率不崩溃,即轮到t2执行时_memory不为空)

解决办法:在ObjPool类中新增公有成员变量_poolMtx,同时在pTLSThreadCache = tcPool.New()的两侧加锁

tcPool._poolMtx.lock();
pTLSThreadCache = tcPool.New();
tcPool._poolMtx.unlock();

补充:SpanList类中为了创建头结点的new Span不用替换,因为头节点通常在 SpanList 对象的整个生命周期内存在,并且不会像其他 Span 对象那样频繁创建和销毁。使用 _spanPool 进行内存管理主要是为了优化频繁分配和回收的对象,而头节点的长生命周期使得使用 _spanPool 的优势不明显

释放对象时不传对象大小

基本概念:在之前释放内存时我们不仅要传入释放的内存的地址,还要存放要释放的内存的大小,过于麻烦,所以最好只传递一个指针即可释放内存

ConcurrentFree(p1, 6);

补充内容1

在span类中新增一个表示当前span中管理的内存的大小的成员变量_objSize

struct Span
{
    ...
	size_t _objSize = 0;//当前span管理的内存大小
};

补充内容2

在ConcurrentFree中,将MapObjectToSpan的位置进行移动,并获取当前span的_objSize

//释放内存
static void ConcurrentFree(void* ptr)
{
	Span* span = PageCache::GetInstance()->MapObjectToSpan(ptr);
	size_t size = span->_objSize;
	if (size > MAX_BYTES)
	{
        ...
	}
	else
	{
        ...
	}
}

 补充内容3

在ConcurrentAlloc中从堆上获取到一个span后,补充该span的_objSize

//申请内存
static void* ConcurrentAlloc(size_t size)
{
    if (size > MAX_BYTES)
    {
        ...
    	Span* span = PageCache::GetInstance()->NewSpan(kpage);
	    span->_objSize = size;
        ...
    }
	else
	{
        ...
	}
}

补充内容4

CentralCache中的NewSpan后,填充该span的_objSize

//为指定位置桶下的SpanList申请一个非空的span
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
    ...
	Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));//从PageCache中获取一个新的非空span
	span->_isUse = true;
	span->_objSize = size;

    ....
}

 注意事项:记得最后把ConcurrentFree的第二个形参删除

测试函数

void WithNoSize()
{
	void* p1 = ConcurrentAlloc(257 * 1024);
	ConcurrentFree(p1);
	
	void* p2 = ConcurrentAlloc(129 * 8 * 1024);
	ConcurrentFree(p2);
}

为哈希桶加锁

基本概念:C++的标准模板库(STL)提供的容器在多线程环境下并不保证线程安全,因此在多个线程同时访问或修改同一个容器时,通常需要自行添加同步机制(如互斥锁)以确保数据的一致性和避免竞态条件,因此当我们尝试在本项目中使用哈希桶记录span与页号的映射关系时,需要及时的加锁

加锁位置:参与读写哈希桶的函数有NewSpan、MapObjectToSpan和ReleaseSpanToPageCache,它们都在PageCache.cpp中,其中在CentralCache.cpp中使用这三个函数时,只有MapObjectToSpan没有添加锁,这就可能导致多个线程在CentralCache中同时访问MapObjectToSpan函数并同时访问哈希桶造成线程安全问题,所以要在MapObjectToSpan函数执行到访问哈希桶的操作前加锁

//地址->页号->span的映射
Span* PageCache::MapObjectToSpan(void* obj)
{
	size_t id = ((size_t)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;
	}
}

关于std::unique_lock<std::mutex> lock(_pageMtx):是一种通过RAII方式管理互斥锁的机制,确保在多线程环境中对共享资源的安全访问。它自动处理锁的获取和释放,减少了手动管理锁可能带来的错误风险,同时提供了较高的灵活性,适用于各种复杂的同步场景

多线程环境下对比malloc测试

新增Benchmark.cpp源文件

#include<cstdio>
#include<iostream>
#include<vector>
#include<thread>
#include<mutex>
#include"ConcurrentAlloc.h"
using namespace std;

void BenchmarkMalloc(size_t ntimes, size_t nworks, size_t rounds)//ntime一轮申请和释放内存的次数,round是跑多少轮,nworks是线程数
{
	std::vector<std::thread> vthread(nworks);
	std::atomic<size_t> malloc_costtime = 0;
	std::atomic<size_t> free_costtime = 0;
	for (size_t k = 0; k < nworks; ++k)
	{
		vthread[k] = std::thread([&, k]() {
			std::vector<void*> v;
			v.reserve(ntimes);
			for (size_t j = 0; j < rounds; ++j)
			{
				size_t begin1 = clock();
				for (size_t i = 0; i < ntimes; i++)
				{
					//v.push_back(malloc(16));
					v.push_back(malloc((16 + i) % 8192 + 1));
				}
				size_t end1 = clock();
				size_t begin2 = clock();
				for (size_t i = 0; i < ntimes; i++)
				{
					free(v[i]);
				}
				size_t end2 = clock();
				v.clear();
				malloc_costtime += (end1 - begin1);
				free_costtime += (end2 - begin2);
			}
			});
	}
	for (auto& t : vthread)
	{
		t.join();
	}
	printf("%u个线程并发执行%u轮次,每轮次malloc %u次: 花费:%u ms\n",
		nworks, rounds, ntimes, malloc_costtime.load());
	printf("%u个线程并发执行%u轮次,每轮次free %u次: 花费:%u ms\n",
		nworks, rounds, ntimes, free_costtime.load());
	printf("%u个线程并发malloc&free %u次,总计花费:%u ms\n",
		nworks, nworks * rounds * ntimes, malloc_costtime.load() + free_costtime.load());
}

// 单轮次申请释放次数 线程数 轮次
void BenchmarkConcurrentMalloc(size_t ntimes, size_t nworks, size_t rounds)
{
	std::vector<std::thread> vthread(nworks);
	std::atomic<size_t> malloc_costtime = 0;
	std::atomic<size_t> free_costtime = 0;
	for (size_t k = 0; k < nworks; ++k)
	{
		vthread[k] = std::thread([&]() {
			std::vector<void*> v;
			v.reserve(ntimes);
			for (size_t j = 0; j < rounds; ++j)
			{
				size_t begin1 = clock();
				for (size_t i = 0; i < ntimes; i++)
				{
					//v.push_back(ConcurrentAlloc(16));
					v.push_back(ConcurrentAlloc((16 + i) % 8192 + 1));
				}
				size_t end1 = clock();
				size_t begin2 = clock();
				for (size_t i = 0; i < ntimes; i++)
				{
					ConcurrentFree(v[i]);
				}
				size_t end2 = clock();
				v.clear();
				malloc_costtime += (end1 - begin1);
				free_costtime += (end2 - begin2);
			}
			});
	}
	for (auto& t : vthread)
	{
		t.join();
	}
	printf("%u个线程并发执行%u轮次,每轮次concurrent alloc %u次: 花费:%u ms\n",
		nworks, rounds, ntimes, malloc_costtime.load());
	printf("%u个线程并发执行%u轮次,每轮次concurrent dealloc %u次: 花费:%u ms\n",
		nworks, rounds, ntimes, free_costtime.load());
	printf("%u个线程并发concurrent alloc&dealloc %u次,总计花费:%u ms\n",
		nworks, nworks * rounds * ntimes, malloc_costtime.load() + free_costtime.load());
}
int main()
{
	size_t n = 10000;
	cout << "==========================================================" << endl;
	BenchmarkConcurrentMalloc(n, 10, 10);
	cout << endl << endl;
	BenchmarkMalloc(n, 10, 10);
	cout << "==========================================================" << endl;
	return 0;
}

性能瓶颈分析

针对性能瓶颈使用基数树进行优化

基数树代码

 ~over~

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

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

相关文章

Python(五)-函数

目录 函数的定义与调用 特点 语法格式 函数的参数 函数的返回值 函数嵌套调用 变量的作用域 局部变量 全局变量 函数的多种参数 位置参数 关键字参数 默认参数 可变参数 函数的定义与调用 python函数需要使用def关键字来定义,需要先定义,后调用 特点: 先定义…

课堂讨论:评价计算机性能的指标

**课堂讨论&#xff1a;评价计算机性能的指标** --- ### 课堂开始 **王老师**&#xff1a;同学们&#xff0c;今天我们来讨论如何评价计算机性能的指标。小明&#xff0c;你知道有哪些指标吗&#xff1f; **小明**&#xff1a;嗯...有吞吐率和响应时间吧&#xff1f;&#…

双链表的插入删除遍历

双链表的插入操作 双链表的删除操作 双链表的遍历操作

Watchdog Timers(WDT)

文章目录 1. 介绍2. Feature List3. 概述3.1. Safety Watchdog3.2. CPU Watchdog 4. 看门狗定时器功能5. Endinit Functions5.1 Password Access to WDTxCON05.1.1 Static Password5.1.2 Automatic Password Sequencing 5.2 Check Access to WDTxCON05.3 Modify Access to WDTx…

点餐小程序实战教程13餐桌管理

目录 1 创建数据源2 搭建管理后台3 生成餐桌码4 找到自己的appid和secret5 小程序里获取餐桌信息总结 我们上一篇介绍了点餐界面的菜品展示功能。现实中如果你去餐馆用餐&#xff0c;总是给餐桌贴一个二维码&#xff0c;服务员会告诉你扫码点餐。 扫码大家现在都已经非常熟练了…

“从零开始学排序:简单易懂的算法指南“

“一辈人有一辈人要做的事&#xff01;&#xff01;&#xff01;” 这一期的节目呢&#xff0c;是关于排序的内容&#xff0c;相信大家对此一定很熟悉吧&#xff01; 排序&#xff1a; 排序是将一组元素按照一定的规则或标准进行组织和排列的过程。 冒泡排序&#xff1a; 冒…

此连接非私人连接

当你手机浏览器输入网站打开提示“此连接非私人连接&#xff0c;此网站可能在冒充来窃取你的个人或财务信息。你应回到之前的页面”这是因为该网站的SSL数字证书到期导致&#xff0c;需要此网站的管理员重新申请数字证书替换之前的文件才可以实现。 注意&#xff1a;如果你不是…

Token: 数据库、存储系统和API安全的应用

一. Token Token是一种常见的计算机术语&#xff0c;它在不同的上下文中有不同的含义。在身份验证和授权的上下文中&#xff0c;Token通常指的是服务端生成的一串字符串&#xff0c;作为客户端进行请求的一个令牌。当用户登录后&#xff0c;服务器会生成一个Token并返回给客户…

【高阶数据结构】平衡二叉树(AVL)的删除和调整

&#x1f921;博客主页&#xff1a;醉竺 &#x1f970;本文专栏&#xff1a;《高阶数据结构》 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多《高阶数据结构》点击专栏链接查看&a…

记一次教学版内网渗透流程

信息收集 如果觉得文章写的不错可以共同交流 http://aertyxqdp1.target.yijinglab.com/dirsearch dirsearch -u "http://aertyxqdp1.target.yijinglab.com/"发现 http://aertyxqdp1.target.yijinglab.com/joomla/http://aertyxqdp1.target.yijinglab.com/phpMyA…

DialFRED基准:具有对话能力的具身智能Agent

目录 一、DialFRED数据集1.1 数据集规模与任务结构1.2 任务实例的构成1.3 人类标注的问答数据1.4 Oracle自动生成答案1.5 任务多样性与数据增强1.6 数据集的词汇多样性1.7 任务和环境的多样性 二、提问者-执行者框架2.1 框架概述2.2 提问者模型设计2.3 执行者模型设计2.4 强化学…

【读书笔记-《30天自制操作系统》-25】Day26

本篇仍然是围绕着命令行窗口做文章。首先优化命令行窗口的移动速度&#xff0c;然后增加多个命令行窗口功能。接着优化了命令行窗口的关闭&#xff0c;最后增加了两个命令start与ncst。 1. 优化命令行窗口移动速度 首先对命令行窗口的移动速度进行优化。主要的优化点有以下几…

WEB服务器——Tomcat

服务器是可以使用java完成编写&#xff0c;是可以接受页面发送的请求和响应数据给前端浏览器的&#xff0c;而在开发中真正用到的Web服务器&#xff0c;我们不会自己写的&#xff0c;都是使用目前比较流行的web服务器。 如&#xff1a;Tomcat 1. 简介 Tomcat 是一个开源的轻量…

二维数组的存放

今天我水的文章是二维数组的存放 二维数组的存放方式其实和一维数组没有区别&#xff0c;但如果想要更直观的了解&#xff0c;我们可以把它们的地址打印出来。 代码如下&#xff1a; #include <stdio.h> int main() {int arr[3][3];//二维数组&#xff0c;int数组类型…

【高效管理集合】并查集的实现与应用

文章目录 并查集的概念主要操作优化技术应用场景 并查集的实现基本框架并查集的主要接口总体代码 并查集的应用省份的数量等式方程的可满足性 总结 并查集的概念 并查集&#xff0c;也称为不相交集&#xff0c;是一种树形的数据结构&#xff0c;用于处理一些不相交集合的合并及…

ClickHouse | 查询

1 ALL 子句 2 ARRAY JOIN 使用别名 :在使用时可以为数组指定别名&#xff0c;数组元素可以通过此别名访问&#xff0c;但数组本身则通过原始名称访问 3 DISTINCT子句 DISTINCT不支持当包含有数组的列 4 FROM子句 FROM 子句指定从以下数据源中读取数据: 1.表 2.子…

建筑资质应该怎么选?

建筑资质是建筑企业承接工程项目的必备条件&#xff0c;它不仅关系到企业的市场竞争力&#xff0c;还直接影响到企业的经营效益。因此&#xff0c;选择适合自己企业的建筑资质至关重要。以下是一些选择建筑资质时需要考虑的关键因素&#xff1a; 1. 明确企业定位 首先&#x…

金融教育宣传月 | 平安养老险百色中心支公司开展金融知识“消保县域行”宣传活动

9月22日&#xff0c;平安养老险百色中心支公司积极落实国家金融监督管理总局关于开展金融教育宣传月活动的相关要求&#xff0c;联合平安人寿百色中心支公司共同组成了平安志愿者小队&#xff0c;走进百色市四塘镇百兰村开展了一场别开生面的金融消费者权益保护宣传活动。此次活…

如何给你的项目添加测试覆盖率徽章

看完我的测试教程之后&#xff0c;想必大家都能写出一个测试覆盖率极高的小项目了。测试覆盖率既然这么高&#xff0c;不秀一秀岂不是白瞎了&#xff0c;下面我们就来通过第三方服务来给你的项目加上测试覆盖率徽章&#xff0c;涉及到的内容有yaml配置&#xff0c;githubAction…

Vue下载pubsub-js中错误问题解决

错误&#xff1a; 解决方法&#xff1a; 执行&#xff1a; npm config set registry https://registry.npm.taobao.org我执行以上方法后安装成功