IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> C++_项目高并发内存池——优化(定长内存池配合脱离new申请空间)(释放空间时不需要传大小) -> 正文阅读

[数据结构与算法]C++_项目高并发内存池——优化(定长内存池配合脱离new申请空间)(释放空间时不需要传大小)

1. 定长内存池配合脱离new申请空间

根据C++项目高并发内存池_定长内存池可知,使用定长内存池比直接new申请对象效率要高。

在PageCache向系统申请内存时我们使用new创建了许多Span对象来管理向系统申请的大块内存,这个Span对象是new出来的。以及在线程创建属于自己的ThreadCache时,也使用了new来申请ThreadCache对象,我们可以联系定长内存池,把这些申请过程用定长内存池替代,效率更高。

定长线程池:

#pragma once

#include"Common.h"

template<class T>//定长内存池
class ObjectPool {
private:
	char* _memory;//指向大块内存的指针
	//返回的内存用链式结构管理
	void* _freeList;
	size_t _overage;//大块内存剩余空间大小
public:
	ObjectPool() :_memory(nullptr), _freeList(nullptr),_overage(0) {}

	T* New() {
		T* obj = nullptr;
		if (_freeList != nullptr) {
			//优先把归还的内存重复利用
			//链表头删
			void* next = *((void**)_freeList);
			obj = (T*)_freeList;
			_freeList = next;
		}
		else
		{
			if (_overage < sizeof(T)) {
				_overage = 100 * 1024;
				_memory = (char*)SystemAlloc(_overage);
				if (_memory == nullptr) {
					throw std::bad_alloc();
				}
			}
			obj = (T*)_memory;
			size_t SizeT = sizeof(T) > sizeof(void*) ? sizeof(T) : sizeof(void*);
			_memory += SizeT;
			_overage -= SizeT;
		}
		//调用定位new进程空间初始化
		new(obj)T;
		return obj;
	}

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

所以在PageCache中需要新定义成员定长内存池

#pragma once

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

class PageCache {
private:
	SpanList _SpanList[NPAGE];
	static PageCache _sInst;

	//页号与Span链表的映射,方便归还内存时直接通过内存找页号找到这块内存是那个Span
	std::unordered_map<PAGE_ID, Span*>IdSpanMap;

	//定长内存池来申请Span对象
	ObjectPool<Span>_SpanPool;

	PageCache() {}
public:
	std::mutex _PageMtx;

	PageCache(const PageCache&) = delete;

	//获取这个内存是那个Span
	Span* MapObjToSpan(void* obj);

	static PageCache* GetInst() {
		return &_sInst;
	}

	//将CentralCache的Span回收,合并相邻页
	void RetrunPageCache(Span* span);

	//获取NumPage页的Span
	Span* NewSpan(size_t NumPage);
};

在PageCache中每当申请/释放Span时用定长内存池New/Delete

线程在获得自己的Thread Cache时也使用定长内存池来申请对象

#pragma once

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

//线程调用申请ThreadCache空间
static void* ConcurrentAlloc(size_t size) {
	if (size > MAX_BYTE) {//大于256KB内存
		size_t AlignSize = SizeClass::RoundUp(size);//计算对其大小
		//直接向PageCache索要K页的内存
		size_t K = AlignSize >> PAGESIZE;
		PageCache::GetInst()->_PageMtx.lock();
		Span*span=PageCache::GetInst()->NewSpan(K);
		PageCache::GetInst()->_PageMtx.unlock();
		void* ptr = (void*)(span->_PageID << PAGESIZE);//获取这块内存的地址
		return ptr;
	}
	else {
		//获取线程自己的ThreadCache
		if (tls_threadcache == nullptr) {
			static ObjectPool<ThreadCache>TcPool;//使用定长内存池来获取对象
			tls_threadcache = TcPool.New();
		}
		cout << std::this_thread::get_id() << " " << tls_threadcache << endl;
		return tls_threadcache->ApplySpace(size);
	}
}

static void ConcurrentFree(void* ptr, size_t size) {
	if(size>MAX_BYTE){
		Span* span = PageCache::GetInst()->MapObjToSpan(ptr);//计算要释放的大空间属于那个Span

		PageCache::GetInst()->_PageMtx.lock();
		PageCache::GetInst()->RetrunPageCache(span);//将内存挂到PageCache桶上,需要修改桶,所以要加锁
		PageCache::GetInst()->_PageMtx.unlock();
	}
	else {
		//释放时每个线程一定有tls_threadcache
		assert(tls_threadcache != nullptr);
		tls_threadcache->ReleaseSpace(ptr, size);
	}
}

因为修改代码变化不大,为了避免冗余这里贴出链接

Github
Gitee

2. 释放空间时不需要传大小

在之前,我们的高并发内存池释放空间时需要传大小,十分的不方便。

传大小来释放空间,为了判断这块空间是从堆上申请的还是通过三层缓存来申请的。还可以判断这块内存在ThreadCache和CentralCache哈希桶中的那个桶。

因为PageCache中,已经实现了页号到Span*的映射。
所以知道要释放的地址,根据地址计算页号,在根据页号找到Span,每个Span中自由链表上挂的空间大小都是相同的,所以在Span中添加属性,记录切好的内存块的大小。

//管理以页为单位的大块空间结构
struct Span {
	PAGE_ID _PageID;//记录是第几页
	size_t _Num;//记录Span里面有多少页
	Span* _next;//双向链表
	Span* _prev;

	size_t use_count;//记录分配了多少个对象给ThreadCahce

	void* FreeList;//切好的小块内存空间

	bool IsUse;//标记这块Span是否被使用

	size_t ObjectSize;//每个切好的小块内存空间大小

	Span() :_PageID(0), _Num(0), _next(nullptr), _prev(nullptr), use_count(0), FreeList(nullptr), IsUse(false), ObjectSize(0) {}
};
  • 在Central Cache中将PageCache的大块内存切分成小内存时,就将Span中 ObjectSize字段记录下来。等到最后释放时根据页号找Span在找 ObjectSize字段就知道要释放内存的大小了。
  • 如果申请大于256KB,没有经过CentralCache,直接向PageCache申请,此时需要在申请后手动填写 ObjectSize字段。

注意:
大于256KB时访问PageCache的哈希桶,而PageCache只能被一个线程访问,PageCache中的Span指针与页号的映射也是能由一个人访问,所以要对获取Span映射的函数添加锁。

Span* PageCache::MapObjToSpan(void* obj) {

	std::unique_lock<std::mutex>lock(_PageMtx);//映射被多个线程访问需要加锁防止线程安全,出了函数锁自动释放
	//计算obj的页号
	PAGE_ID pageId = (PAGE_ID)obj >> PAGESIZE;
	//获取这个内存是那个Span
	std::unordered_map<PAGE_ID,Span*>::iterator ret = IdSpanMap.find(pageId);
	if (ret != IdSpanMap.end()) {
		return ret->second;
	}
	else {
		assert(false);//不可能找不到
		return nullptr;
	}
}

因为修改代码变化不大,为了避免冗余这里贴出链接

Github
Gitee

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-03-17 22:25:52  更:2022-03-17 22:26:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 11:30:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码