四、高并发内存池整体框架设计

四、高并发内存池整体框架设计

现代很多的开发环境都是多核多线程,在申请内存的场景下,必然存在激烈的锁竞争问题。malloc本身其实已经很优秀,那么我们项目的原型TCmalloc就是在多线程高并发的场景下更胜一筹,所以这次我们实现的内存池需要考虑以下几方面的问题。

  1. 性能问题。
  2. 多线程环境下,锁竞争问题。
  3. 内存碎片问题。

concurrent memory pool主要由以下3个部分构成:
1、thread cache:线程缓存是每个线程独有的,用于分配小于256KB的内存的,线程从这里申请内存不需要加锁,每个线程独享一个thread cache,这也就是这个并发线程池高效的地方。
2、central cache:中心缓存是所有线程所共享,thread cache是按需从central cache中获取的对象。central cache合适的时机回收thread cache中的对象,避免一个线程占用了太多的内存,而其他线程的内存不够用,达到内存分配在多个线程中更均衡的按需调度的目的。central cache是存在竞争的,所以从这里取内存对象是需要加锁,首先这里用的是桶锁,其次只有thread cache的没有内存对象时才会找central cache,所以这里竞争也就不会很激烈。
3、page cache:页缓存是在central cache缓存上面的一层缓存,存储的内存是以页为单位存储及分配的,central cache没有内存对象时,从page cache分配出一定数量的page,并切割成定长大小的小块内存,分配给central cache。当一个span的几个跨度页的对象都回收以后,page cache会回收central cache满足条件的span对象,并且合并相邻的页,组成更大的页,缓解内存碎片的问题,当central cache再一次向PageCache申请更大块内存的时候PageCache也能满足。

在这里插入图片描述

内存池三层模型中函数需要用到的公共的头文件–common.h


//能够申请的内存的最大值
static const size_t MAX_BYTES = 256 * 1024;
//thread_cache的哈希桶的数目
static const size_t NFREELIST = 208;
//PageCache中哈希桶的数量,每一个桶按照页数映射
//每一个页数对应的桶的span对象的大小就是页数,即第一页的span对象的大小是一页
static const size_t NPAGES = 129;
//页大小转换偏移, 即一页定义为2^13,也就是8KB
static const size_t PAGE_SHIFT = 13;//条件编译,为了适应不同平台
#ifdef _WIN64
typedef unsigned long long PAGE_ID;
#elif _WIN32
typedef size_t PAGE_ID;//页号
#endif#ifdef _WIN32
#include <windows.h>
#endif // _WIN32
//系统调用接口,直接向系统堆申请k页内存,脱离malloc函数
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32void* ptr = VirtualAlloc(0, kpage * (1 << PAGE_SHIFT),MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else// brk mmap等
#endifif (ptr == nullptr)throw std::bad_alloc();return ptr;
}
//系统调用接口,直接向系统堆释放内存,脱离free
inline static void SystemFree(void* ptr)
{
#ifdef _WIN32VirtualFree(ptr, 0, MEM_RELEASE);
#else// sbrk unmmap等
#endif
}//obj对象的前4个或者8个字节,存放的是obj的下一个对象的地址,相当于obj->next
static void*& NextObj(void* obj)
{return *(void**)obj;
}//管理切分好的小对象的自由链表
struct FreeList
{
public:void Push(void* obj){assert(obj);//头插NextObj(obj) = _freeList;_freeList = obj;_size++;}void PushRange(void* start,void* end,size_t n){assert(start);assert(end);NextObj(end) = _freeList;_freeList = start;调试技巧:1、条件断点2、调用堆栈3、中断程序//int i = 0;//while (start)//{//	start = NextObj(start);//	i++;//}//if (i != n)//{//	int x = 0;//}_size += n;}//n表示删除了多少个,这里的删除并不是真的把空间释放了,而是把小对象从自由链表中解掉//即可,删除掉的这些小对象本质也是被外面调用方函数拿到了的,注意这里start和end是引用//输出型参数void PopRange(void*& start, void*& end, size_t n){assert(n <= _size);start = _freeList;end = start;//end走n-1步是为了修改end的next和更新_freeListfor (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;}size_t& MaxSize(){return _maxSize;}size_t Size(){return _size;}bool Empty(){return _freeList == nullptr;}public:void* _freeList = nullptr;//自由链表
private: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)//向上对齐计算对象大小//由于这里是按照一定规则对齐的,所以其实是会有内存碎片的(内碎片),即申请1字节也是给8字节,申请2字节//也是给8字节的,但是根据以上计算可以控制最多10%左右的内碎片浪费。static inline size_t RoundUp(size_t bytes){if (bytes <= 128){return _RoundUp(bytes, 8);}else if (bytes <= 1024){return _RoundUp(bytes, 16);}else if (bytes <= 8*1024){return _RoundUp(bytes, 128);}else if (bytes <= 64*1024){return _RoundUp(bytes, 1024);}else if (bytes <= 256*1024){return _RoundUp(bytes, 8 * 1024);}else{//以页为单位对齐return _RoundUp(bytes, 1 << PAGE_SHIFT);}}//计算某一对象映射的哈希桶的下标static inline size_t Index(size_t alignSize){assert(alignSize <= MAX_BYTES);//不同的对齐数对应的区间中有多少个哈希桶static int group[] = { 16,56,56,56 };if (alignSize <= 128){return _Index(alignSize, 3);}else if (alignSize <= 1024){return _Index(alignSize - 128, 4) + group[0];}else if (alignSize <= 8 * 1024){return _Index(alignSize - 1024, 7) + group[0] + group[1];}else if (alignSize <= 64 * 1024){return _Index(alignSize - 8 * 1024, 10) + group[0] + group[1] + group[2];}else if (alignSize <= 256 * 1024){return _Index(alignSize - 64 * 1024, 13) + group[0] + group[1] + group[2] + group[3];}else{//alignSize太大就无法映射到哈希桶中了,这时应该直接断言错误即可assert(false);return -1;}}//计算当有线程申请一个size大小的内存块时,threadcache对应哈希桶没有内存块//时一次向central cache申请多少个size大小的内存块,多的挂到threadcache//对应的哈希桶中,这个是大佬们研究之后得出来的算法static size_t NumMoveSize(size_t size){int n = MAX_BYTES / size;if (n < 2){return 2;}else if (n > 512){return 512;}else{return n;}}//计算当CentralCache对应位置的哈希桶中没有空闲的span的时候向PageCache//申请多少页大小的span对象,这个是别人通过测试得出来的算法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;}private:static inline size_t _RoundUp(size_t bytes, size_t alignNum/*对齐数*/){//对齐之后的大小//size_t alignSize;//if (bytes % alignNum == 0)//{//	alignSize = bytes;//}//else//{//	alignSize = (bytes / alignNum + 1) * alignNum;//}//return alignSize;return (bytes + alignNum - 1) & ~(alignNum - 1);}//static inline size_t _Index(size_t alignSize, size_t align)//{//	int index;//	if (alignSize % (1 << align) != 0)//	{//		index = alignSize / align;//	}//	else//	{//		index = alignSize / align - 1;//	}//	return index;//}static inline size_t _Index(size_t bytes, size_t align_shift){return ((bytes + (1 << align_shift) - 1) >> align_shift) - 1;}
};//以页为单位的大块内存,1页=8k=8*1024字节
struct Span
{PAGE_ID _pageId = 0;//Span大块内存的起始页号,页号相当于一个地址,描述这个大块内存的位置size_t _n = 0;//页数,代表这个Span有多少页内存,表示这个Span的大小Span* _next = nullptr;//用双向链表结构组织起来Span* _prev = nullptr;void* _freeList = nullptr;//用Span切好的小块内存的自由链表size_t _useCount = 0;//记录Span切好的小块内存被使用的数量,方便后续归还内存size_t _objSize = 0;//记录该span被切成的小块内存的大小,让释放内存时不用传大小//表示该span是否正在被使用,如果span在PageCache,则认为该span没有被使用//可以在PageCache和前后的空闲页合并,如果span被分到CentralCache,则说明//该span被使用,不能被合并bool _isUse = false;};//用带头双向链表结构把多个Span组织起来
class SpanList
{
public:SpanList(){//哨兵位的头节点_head = new Span;_head->_next = _head;_head->_prev = _head;}void PushFront(Span* newSpan){Insert(Begin(), newSpan);}bool Empty(){return _head->_next == _head;}Span* PopFront(){Span* front = Begin();Erase(Begin());return front;}Span* Begin(){return _head->_next;}Span* End(){return _head;}void Insert(Span* pos, Span* newSpan){assert(pos);assert(newSpan);Span* prev = pos->_prev;prev->_next = newSpan;newSpan->_prev = prev;newSpan->_next = pos;pos->_prev = newSpan;}void Erase(Span* pos){assert(pos != _head);Span* prev = pos->_prev;Span* next = pos->_next;prev->_next = next;next->_prev = prev;}
private:Span* _head;public://桶锁,因为全局只有一个central cache,如果有多个线程同时访问同一个桶的时候//会有线程安全的问题,所以需要加锁,但是多个线程同时访问到同一个桶的概率不大//所以锁竞争没有那么激烈std::mutex _mtx;
};

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

centos 7的超详细安装教程

打开虚拟机&#xff0c;创建一个新电脑 我们选择经典&#xff0c;然后选择下一步 我们选择稍后安装&#xff0c;我们在后面进行改设备 因为centos系统是linux系统的一个版本&#xff0c;所有我们选择linux&#xff0c;版本选择centos 7 64位&#xff0c;然后就是点击下一步 这一…

HTML <template> 标签

实例 使用 <template> 保留页面加载时隐藏的内容。使用 JavaScript 来显示: <button οnclick="showContent()">显示被隐藏的内容</button><template><h2>Flower</h2><img src="img_white_flower.jpg" width=&q…

2023年03月 C/C++(五级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;拼点游戏 C和S两位同学一起玩拼点游戏。有一堆白色卡牌和一堆蓝色卡牌&#xff0c;每张卡牌上写了一个整数点数。C随机抽取n张白色卡牌&#xff0c;S随机抽取n张蓝色卡牌&#xff0c;他们进行n回合拼点&#xff0c;每次两人各出一张卡牌&#xff0c;点数大者获…

Word导出创建Adobe PDF其中emf图片公式马赛克化及文字缺失

软件版本 Word 2021 Visio 2019 Adobe Acrobat Pro 2020 问题描述 公式马赛克化&#xff0c;是指在Word中使用MathType编辑的公式&#xff0c;然后在Visio中使用图片(增强型图元文件)形式得到的粘贴对象&#xff0c;效果如下 文字缺失&#xff0c;是指Word导出→创建Adobe P…

源码安装cv_bridge

1. 下载源码 1去github上下载GitHub - ros-perception/vision_opencv&#xff0c;进去后注意选择与自己的ros对应的版本&#xff1a;&#xff08;我的为noetic&#xff09; 如果你直接使用 git clone https://github.com/ros-perception/vision_opencv.git 来拉取的话cmake的…

MySQL 8 数据清洗总结

MySQL 8 数据清洗三要素&#xff1a; 库表拷贝和数据备份数据清洗SQL数据清洗必杀技-存储过程 前提&#xff1a;数据库关联库表初始化和基础数据初始化&#xff1a; -- usc.t_project definitionCREATE TABLE t_project (id varchar(64) NOT NULL COMMENT 主键,tid varchar(…

网络基础之重中之重

目录 IP协议 ​编辑 基本概念&#xff1a; 协议头格式&#xff1a; ​编辑 网段划分 DHCP &#xff1a; CIDR&#xff1a; 特殊的IP地址&#xff1a; IP地址的数量限制&#xff1a; 私有IP和公网IP 路由 路由的过程&#xff1a; 数据链路层 认识以太网&#x…

GAN原理 代码解读

模型架构 代码 数据准备 import os import time import matplotlib.pyplot as plt import numpy as np import torchvision.transforms as transforms from torch.utils.data import DataLoader from torchvision import datasets import torch.nn as nn import torch# 创建文…

I IntelliJ IDEA 2023.2 最新解锁方式,支持java20

在 IntelliJ IDEA 2023.1 中&#xff0c;我们根据用户的宝贵反馈对新 UI 做出了大量改进。 我们还实现了性能增强&#xff0c;从而更快导入 Maven&#xff0c;以及在打开项目时更早提供 IDE 功能。 新版本通过后台提交检查提供了简化的提交流程。 IntelliJ IDEA Ultimate 现在支…

C语言每日一练------------Day(7)

本专栏为c语言练习专栏&#xff0c;适合刚刚学完c语言的初学者。本专栏每天会不定时更新&#xff0c;通过每天练习&#xff0c;进一步对c语言的重难点知识进行更深入的学习。 今日练习题关键字&#xff1a;两个数组的交集     双指针 &#x1f493;博主csdn个人主页&#xf…

【MySQL】存储引擎

1.MySQL体系结构 1). 连接层 最上层是一些客户端和链接服务&#xff0c;包含本地 sock 通信和大多数基于客户端 / 服务端工具实现的类似 于TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入 了线程池的概念&#xff0c;为通过认证安全接…

Ubuntu 启动出现grub rescue

​ 一&#xff0c;原因 原因&#xff1a;出现 “grub rescue” 错误通常表示您的计算机无法正常引导到操作系统&#xff0c;而是进入了 GRUB&#xff08;Grand Unified Bootloader&#xff09;紧急模式。这可能是由于引导加载程序配置错误、硬盘驱动器损坏或其他引导问题引起…

js定位到元素底部

文字的一行一行添加的&#xff0c;每次添加要滚动条自动定位到元素底部 <div class"An">//要父元素包裹&#xff0c;父元素设置max-height&#xff0c;overflow啥的<div class"friendly_pW"></div></div>//添加文字时找子元素的高…

Windows右键添加用 IDEA 打开

1.安装IDEA时 安装时会有个选项来添加&#xff0c;如下&#xff1a; 勾选即可 2.修改注册表 安装时未勾选&#xff0c;可以把下面代码中程序路径改为自己的&#xff0c;保存为对应的 idea.reg文件&#xff0c;双击即可 Windows Registry Editor Version 5.00[HKEY_CLASSES…

Screaming Frog SEO Spider,为您的网站提供全方位的优化解决方案

Screaming Frog SEO Spider是一款适用于Mac的软件&#xff0c;它可以帮助用户分析网站的优化信息。该软件可以模拟蜘蛛爬行的方式&#xff0c;抓取网站的各种信息&#xff0c;并将这些信息整理成易于理解的报告。这些报告可以帮助用户评估网站的优化情况&#xff0c;发现链接的…

pyqt5-快捷键QShortcut

import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import *""" 下面示例揭示了&#xff0c;当关键字绑定的控件出现的时候&#xff0c;快捷键才管用&#xff0c; 绑定的控件没有出现的时候快捷键无效 """…

微前沿 | 第1期:强可控视频生成;定制化样本检索器;用脑电重建视觉感知;大模型鲁棒性评测

欢迎阅读我们的新栏目——“微前沿”&#xff01; “微前沿”汇聚了微软亚洲研究院最新的创新成果与科研动态。在这里&#xff0c;你可以快速浏览研究院的亮点资讯&#xff0c;保持对前沿领域的敏锐嗅觉&#xff0c;同时也能找到先进实用的开源工具。 本期内容速览 01. 强可…

Vue2里监听localstorage里值的变化

有的时候,我们需要根据本地缓存在localstorage里值的变化做出相应的操作,这就需要我们监听localstorage: 首先,我们在src下的libs文件夹下新建一个stroage.js用于重写setItem事件,当使用setItem的时候,触发,window.dispatchEvent派发事件 const Stroage = {// 重写set…

python基础之miniConda管理器

一、介绍 MiniConda 是一个轻量级的 Conda 版本&#xff0c;它是 Conda 的精简版&#xff0c;专注于提供基本的环境管理功能。Conda 是一个流行的开源包管理系统和环境管理器&#xff0c;用于在不同的操作系统上安装、管理和运行软件包。 与完整版的 Anaconda 相比&#xff0c…

安卓版yolo-fastest

安卓版本yolofastest效果测试 安卓配置OPENCV4ANDROID&#xff0c;见我的博客一篇文章opencv4dandroid配置 这个不需要使用JNI&#xff0c;十分简单的配置 说真的&#xff0c;其实只调用OPENCV的函数&#xff0c;自己写的代码不多&#xff0c;使用OPENCV4ANDROID和JNI的时间差…