范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

教你用C来实现基于Mempool的内存池设计

  前言
  设计内存池的目标是为了保证服务器长时间高效地运行,通过对申请空间小而申请频繁的对象进行有效管理,减少内存碎片的产生,合理分配管理用户内存,从而减少系统中出现有效空间足够,而无法分配大块连续内存的情况。
  此次设计内存池的基本目标,需要满足线程安全性(多线程),适量的内存泄露越界检查,运行效率不太低于malloc/free方式,实现对4-128字节范围内的内存空间申请的内存池管理(非单一固定大小对象管理的内存池)。 内存池技术设计与实现
  本内存池的设计方法主要参考SGI的alloc的设计方案,为了适合一般的应用,并在alloc的基础上做一些简单的修改。
  Mempool的内存池设计方案如下(也可参考候捷《深入剖析STL》)
  从系统申请大块heap内存,在此内存上划分不同大小的区块,并把具有相同大小的区块连接起来,组成一个链表。比如A大小的块,组成链表L,当申请A大小 时,直接从链表L头部(如果不为空)上取到一块交给申请者,当释放A大小的块时,直接挂接到L的头部。内存池的原理比较简单,但是在具体实现过程中需要大量的 细节需要注意。
  1:字节对齐。
  为了方便内存池中对象的管理,需要对申请内存空间的进行调整,在Mempool中,字节对齐的大小为最接近8倍数的字节数。比如,用户申请5个字节,Mempool首先会把它调整为8字节。比如申请22字节,会调整为24,对比关系如下
  序号
  对齐字节
  范围
  0
  8
  1-8   1
  16
  9-16   2
  24
  17-24   3
  32
  25-32   4
  40
  33-40   5
  48
  41-48   6
  56
  49-56   7
  64
  57-64   8
  72
  65-72   9
  80
  73-80   10
  88
  81-88   11
  96
  89-96   12
  104
  97-104   13
  112
  105-112   14
  120
  113-120   15
  128
  121-128   对于超过128字节的申请,直接调用malloc函数申请内存空间。这里设计的内存池并不是对所有的对象进行内存管理,只是对申请内存空间小,而申请频繁的对象进行管理,对于超过128字节的对象申请,不予考虑。这个需要与实际项目结合,并不是固定不变的。   实现对齐操作的函数如下 static size_t round_up(size_t size) { return (((size)+7) &~ 7);// 按8字节对齐 }   大家如果还想了解更多Linux内核开发相关的更多知识点,请后台私信我【 内核 】免费领取,里面记录了许多的Linux内核知识点。   内核学习网站:   Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂   2:构建索引表   内存池中管理的对象都是固定大小,现在要管理0-128字节的范围内的对象申请空间,除了采用上面提到的字节对齐外,还需要变通一下,这就是建立索引表,做法如下; static _obj* free_list[16]; 创建一个包含16个_obj*指针的数组,关于_obj结构后面详细讲解。free_list[0]记录所有空闲空间为8字节的链表的首地 址;free_list[1]对应16字节的链表,free_list[2]对应24字节的列表。free_list中的下标和字节链表对应关系参考图1 中的"序号"和"对齐字节"之间的关系。这种关系,我们很容易用算法计算出来。如下 static size_t freelist_index(size_t size) { return (((size)+7)/7-1);// 按8字节对齐 }   所以,这样当用户申请空间A时,我们只是通过上面简单的转换,就可以跳转到包含A字节大小的空闲链表上,如下; _obj** p = free_list[freelist_index(A)]; 3:构建空闲链表   通过索引表,我们知道mempool中维持着16条空闲链表,这些空闲链表中管理的空闲对象大小分别为8,16,24,32,40…128。这些空闲链表链接起来的方式完全相同。一般情况下我们构建单链表时需要创建如下的一个结构体。 struct Obj { Obj *next; Char* p; Int iSize; }   next指针指向下一个这样的结构,p指向真正可用空间,iSize用于只是可用空间的大小,在其他的一些内存池实现中,还有更复杂的结构体,比如 还包括记录此结构体的上级结构体的指针,结构体中当前使用空间的变量等,当用户申请空间时,把此结构体添加的用户申请空间中去,比如用户申请12字节的空 间,可以这样做 Obj *p = (Obj*)malloc(12+sizeof(Obj)); p->next = NULL; p->p = (char*)p+sizeof(Obj); p->iSize = 12;   但是,我们并没有采用这种方式,这种方式的一个缺点就是,用户申请小空间时,内存池加料太多了。比如用户申请12字节时,而真实情况是内存池向内存 申请了12+ sizeof(Obj)=12+12=24字节的内存空间,这样浪费大量内存用在标记内存空间上去,并且也没有体现索引表的优势。Mempool采用的是 union方式 union Obj { Obj *next; char client_data[1]; }   这里除了把上面的struct修改为union,并把int iSize去掉,同时把char*p,修改为char client_data[1],并没有做太多的修改。而优势也恰恰体现在这里。如果采用struct方式,我们需要维护两条链表,一条链表是,已分配内存 空间链表,另一条是未分配(空闲)空间链表。而我们使用索引表和union结构体,只需要维护一条链表,即未分配空间链表。具体如下   索引表的作用有两条1:如上所说,维护16条空闲链表2:变相记录每条链表上空间的大小,比如下标为3的索引表内维持着是大小为24字节的空闲链表。这样我们通过索引表减少在结构体内记录p所指向空间大小的iSize变量。从而减少4个字节。   Union的特性是,结构内的变量是互斥存在的。在运行状态下,只是存在一种变量类型。所以在这里sizeof(Obj)的大小为4,难道这里我们也需要把这4字节也加到用户申请空间中去嘛?其实不是,如果这样,我们又抹杀了union的特性。   当我们构建空闲分配链表时,我们通过next指向下一个union结构体,这样我们不使用p指针。当把这个结构体分配出去时,我们直接返回 client_data的地址,此时client_data正好指向申请空间的首字节。所以这样,我们就不用在用户申请空间上添加任何东西。   Obj的连接方式如上所示,这样我们无需为用户申请空间添加任何内容。   4:记录申请空间字节数   如果采用面向对象方式,或者我们在释放内存池的空间时能够明确知道释放空间的大小,无需采用这种方式。   在C语言中的free没有传递释放空间大小,而可以正确释放,在这里也是模仿这种方式,采用这种记录申请空间大小的方式去释放内存。用户申请空 间+1操作将在字节对齐之前执行,找到合适空间后,把首字节改写为申请空间的大小,当然1个字节最多记录256个数,如果项目需要,可以设置为short 类型或者int类型,不过这样就需要占用用户比较大的空间。当释放内存空间时,首先读取这个字节,获取空间大小,进行释放。为了便于对大于128字节对象 的大小进行合适的释放,同时也对大于128字节的内存申请,添加1字节记录大小。所以现在这里限制了用户内存申请空间不得大于255字节,不过现在已经满 足项目要求。当然也可以修改为用short类型记录申请空间的大小。 // 申请 *(( unsigned char *)result) = (size_t)n; unsigned char * pTemp = (unsigned char*)result; ++pTemp; result = (_obj*)pTemp; return result; // 释放 unsigned char * pTemp = (unsigned char *)ptr; --pTemp; ptr = (void*)pTemp; n = (size_t)(*( unsigned char *)ptr); 5:内存池的分配原理   在内存池的设计中,有两个重要的操作过程1:chunk_alloc,申请大块内存,2:refill回填操作,内存池初始化化时并不是为索引表中 的每一项都创建空闲分配链表,这个过程会推迟到,只有用户提取请求时才会创建这样的分配链表。详细参考如下代码(在sgi中stl_alloc.h文件中 你也可以看到这两个函数),主要步骤在注释中已经说明。 /** * @bri: 申请大块内存,并返回size*(*nobjs)大小的内存块 * @param: size,round_up对齐后的大小,nobjs * @return: 返回指向第一个对象内存指针 */ static char* chunk_alloc(size_t size, int *nobjs) { /**< 返回指针 */ char* __result; /**< 申请内存块大小 */ size_t __total_bytes = size *(*nobjs); /**< 当前内存可用空间 */ size_t __bytes_left = _end_free - _start_free; /**< 内存池中还有大片可用内存 */ if (__bytes_left >= __total_bytes) { __result = _start_free; _start_free += __total_bytes; return (__result); } /**< 至少还有一个对象大小的内存空间 */ else if (__bytes_left >= size) { *nobjs = (int)(__bytes_left/size); __total_bytes = size * (*nobjs); __result = _start_free; _start_free += __total_bytes; return (__result); } /**< 内存池中没有任何空间 */ else { /**< 重新申请内存池的大小 */ size_t __bytes_to_get = 2 * __total_bytes + round_up(_heap_size >> 4); /**< 把内存中剩余的空间添加到freelist中 */ if(__bytes_left > 0) { _obj *VOLATILE* __my_free_list = _free_list + freelist_index(__bytes_left); ((_obj*)_start_free)->free_list_link = *__my_free_list; *__my_free_list = (_obj*)_start_free; } // 申请新的大块空间 _start_free = (char*)malloc(__bytes_to_get); /*=======================================================================*/ memset(_start_free,0,__bytes_to_get); /*=======================================================================*/ // 系统内存已经无可用内存,那么从内存池中压缩内存 if(0 == _start_free) { size_t __i; _obj *VOLATILE* __my_free_list; _obj *__p; /**< 从freelist中逐项检查可用空间(此时只收集比size对象大的内存空间) */ for (__i = size; __i <= (size_t)__MAX_BYTES; __i += __ALIGN) { __my_free_list = _free_list + freelist_index(__i); __p = *__my_free_list; /**< 找到空闲块 */ if (__p != 0) { *__my_free_list = __p->free_list_link; _start_free = (char*)__p; _end_free = _start_free + __i; return (chunk_alloc(size,nobjs)); } } _end_free = 0; /**< 再次申请内存,可能触发一个异常 */ _start_free = (char*)malloc(__bytes_to_get); } /**< 记录当前内存池的容量 */ _heap_size += __bytes_to_get; _end_free = _start_free + __bytes_to_get; return (chunk_alloc(size,nobjs)); } } /*=======================================================================*/ /** * @bri: 填充freelist的连接,默认填充20个 * @param: __n,填充对象的大小,8字节对齐后的value * @return: 空闲 */ static void* refill(size_t n) { int __nobjs = 20; char* __chunk = (char*)chunk_alloc(n, &__nobjs); _obj *VOLATILE* __my_free_list; _obj *VOLATILE* __my_free_list1; _obj * __result; _obj * __current_obj; _obj * __next_obj; int __i; // 如果内存池中仅有一个对象 if (1 == __nobjs) return(__chunk); __my_free_list = _free_list + freelist_index(n); /* Build free list in chunk */ __result = (_obj*)__chunk; *__my_free_list = __next_obj = (_obj*)(__chunk + n); __my_free_list1 = _free_list + freelist_index(n); for (__i = 1;; ++__i) { __current_obj = __next_obj; __next_obj = (_obj*)((char*)__next_obj+n); if(__nobjs - 1 == __i) { __current_obj->free_list_link = 0; break; }else{ __current_obj->free_list_link = __next_obj; } } return(__result); }   经过上面操作后,内存池可能会成为如下的一种状态。从图上我们可以看到,已经构建了8,24,88,128字节的空闲分配链表,而其他没有分配空闲 分配链表的他们的指针都指向NULL。我们通过判断索引表中的指针是否为NULL,知道是否已经构建空闲分配表或者空闲分配表是否用完,如果此处指针为 NULL,我们调用refill函数,重新申请20个这样大小的内存空间,并把他们连接起来。在refill函数内,我们要查看大内存中是否有可用内存, 如果有,并且大小合适,就返回给refill函数。   6:线程安全 采用互斥体,保证线程安全。 内存池测试   内存池的测试主要分两部分测试1:单线程下malloc与mempool的分配速度对比2:多线程下malloc和mempool的分配速度对比,我们分为4,10,16个线程进行测试了。 测试环境:操作系统:windows2003+sp1,VC7.1+sp1,硬件环境:intel(R) Celeron(R) CPU 2.53GHz,512M物理内存。 申请内存空间设定如下 #define ALLOCNUMBER0 4 #define ALLOCNUMBER1 7 #define ALLOCNUMBER2 23 #define ALLOCNUMBER3 56 #define ALLOCNUMBER4 10 #define ALLOCNUMBER5 60 #define ALLOCNUMBER6 5 #define ALLOCNUMBER7 80 #define ALLOCNUMBER8 9 #define ALLOCNUMBER9 100   Malloc方式和mempool方式均使用如上数据进行内存空间的申请和释放。申请过程,每次循环申请释放上述数据20次 我们对malloc和mempool,分别进行了如下申请次数的测试(单位为万)   2
  10
  20
  30
  40
  50
  80
  100
  150
  200
  malloc和mempool在单线程,多线程,release,debug版的各种测试数据,形成如下的统计图   可以看到mempool无论在多线程还是在单线程情况下,mempool的速度都优于malloc方式的直接分配。   Malloc方式debug模式下,在不同的线程下,运行时间如下,通过图片可知,malloc方式,在debug模式下,申请空间的速度和多线程的关系不大。多线程方式,要略快于单线程的运行实现。   Malloc方式release模式测试结果如下:   多线程的优势,逐渐体现出来。当执行200w次申请和释放时,多线程要比单线程快1500ms左右,而4,10,16个线程之间的差别并不是特别大。不过整体感觉4个线程的运行时间要稍微高于10,16个线程的情况下,意味着进程中线程越多用在线程切换上的时间就越多。   下面是mempool在debug测试结果:   下面是mempool在release模式下的测试结果   以上所有统计图中所用到的数据,是我们测试三次后平均值。   通过上面的测试,可以知道mempool的性能基本上超过直接malloc方式,在200w次申请和释放的情况下,单线程release版情况 下,mempool比直接malloc快110倍。而在4个线程情况下,mempool要比直接malloc快7倍左右。以上测试只是申请速度的测试,在 不同的压力情况下,测试结果可能会不同,测试结果也不能说明mempool方式比malloc方式稳定。 小结:内存池基本上满足初期设计目标,但是她并不是完美的,有缺陷,比如,不能申请大于256字节的内存空间,无内存越界检查,无内存自动回缩功能等。只是这些对我们的影响还不是那么重要。

长钱护航多元主体参与住房租赁中国模式加速成型中国经济网北京12月8日讯(记者李方)随着Z世代成为住房租赁的核心客群,个性化主题性设计感等都成为当前租住需求的标签,高质量发展住房租赁行业,提供品质化租住产品,是住房租赁中国模式苹果收费模式引厂商不满,Epic称要把其告到最高法院近年来,苹果因为独特的AppStore的收费模式,使得不少App厂商感到不满,知名游戏厂商EpicGames就是其中之一。据悉,双方的争端起始于2020年,2020年8月,Epic让RPA人人可用,实在智能IPA模式及智能屏幕语义理解技术全网首发2022年3月31日,春至。实在智能召开RPA行业2022年首场产品发布会。如同007系列电影一样,AI你所爱新益求新的发布会结束语中已悄然埋下伏笔不说再见,下一次发布会,与您在更跨境电商广州模式作为中国跨境电商的排头兵,广州是全球企业家瞩目的城市。广州港希音背倚的广州,是一座物流通达度领先全国的跨境电商之城。据广州海关统计,每天有近17万件跨境电商包裹从广州发往全国30多Technics与小川理子不做黑胶唱机,还能成为真正的Technics吗?时隔13年之久,小川理子又回到了最初的地方,这时她的身份已经是家庭娱乐事业部的音响发展战略责任理事。重启Technics摆在她面前的首要任务,就是当年九月份的德国消费电子交易会IF可视化设计如何解读科技感风格?一科技感的界面有哪些特征?二同展示条件下不同应用场景,又会有什么样的差异?三三维的表现形式,是否真的跟科技感成正比?四面对段落文本需求,列表需求等如何把页面做出科技感?1。科技感的星球大战绝地幸存者Steam开启预购国区298元星球大战绝地幸存者现已开放预购,Steam国区标准版298元,豪华版418元。Steam版支持简体中文,2023年3月17日发售。这款跨银河系第三人称动作冒险游戏中,卡尔凯斯提斯的光遇欧若拉联动重磅炸弹,千人同屏演唱会开启在欧若拉季节联动到一半的时候,也就是12月9日会开启线上演唱会,挪威精灵小曦会在游戏内,用她的向导建模来给我们献唱。本次演唱会的重点是突破了人数限制,不再是八个人一个房间,最多可容TGA2022自杀小队战胜正义联盟新预告5月26日发售今日(12月9日),自杀小队战胜正义联盟于TGA颁奖礼上公布最新预告,预计于2023年5月26日发售,登陆PS5XSXS以及PC平台,感兴趣的玩家可以进入商店页面。宣传片加入自杀小绝地求生无法启动steam界面解决办法一览许多玩家对这款大型战术竞技射击游戏绝地求生并不陌生,该游戏采用了虚幻4引擎制作,玩家们在游戏中开展一场赢家通吃的生存竞赛,最后只会有1个人存活。这款游戏自发布之后吸引了许多玩家去下古人发明的关于时间的神秘魔法,就在这套岁末献给孩子们的书里我们的职责是平整土地,而非焦虑时光。你做三四月的事,在八九月自有答案。余世存每当岁末,我们都会跟孩子讨论一个特别重要的关键词,那就是时间。特别在岁末年初时,大家总是喜欢盘点,总是想
湖人2换1报价再次被拒,詹姆斯宣布续约,安吉索要7换1交易要价北京时间8月19日,NBA自由市场正在进行,其中安德鲁维金斯表示,斯蒂芬库里克莱汤普森与追梦格林一直督促他担起责任,我非常珍惜与他们的日子,佩林卡目前非常积极,试图引进神射手或者是强势来袭!张伟丽vs卡拉埃斯帕扎,11月13日就在今天,国外知名MMA记者阿里尔赫尔瓦尼(ArielHelwani)透露张伟丽将于北京时间11月13日,在美国纽约举行的UFC281期中,挑战UFC现任女子草量级冠军卡拉埃斯帕扎英媒三家财团有意收购曼联俱乐部英国首富静待挂牌据英格兰媒体报道,目前至少有三家财团有意收购曼联俱乐部,而格雷泽家族有可能以60亿美元(约合50亿英镑)的价格出售。独立报表示,外界越来越相信格雷泽家族将会以60亿美元的价格出售曼你们还记得他们都是那一年选秀进入NBA的么德里克罗斯泰森钱德勒3。埃里克戈登4。布兰登罗伊5。昌西比卢普斯这些人中有的人职业生涯,取得了属于美职篮篮球运动员的荣誉,有的人可能输给了伤病其他,但是他们都把最好看的篮球比赛奉献NBA打球脏的组一队,打架的组一队,哪一组会获得最终的比赛胜利首发后卫保罗对隆多,论起小动作,恐怕只有斯托克顿可以跟保罗有得一比,但是斯托克顿离我太远,还是把保罗选为第一位,保罗的小动作真的很隐蔽,让你防不胜防。而隆多比起保罗正派很多,但是你米切尔科比和詹姆斯无法相提并论,两人不在一个层次上面在进入21世纪之后,要说哪2人是最受球员们关注的,那么应该就是科比以及詹姆斯。科比是00年代的第一人,詹姆斯则是10年代的第一人,这2人都被认为是NBA历史前10的球星,很多球迷都队长年薪不如他的零头,张常宁接受男篮的求婚,还会参加女排比赛在东京奥运会之后,中国女排的主要主攻和江苏女排前队长张常宁从未离开比赛。实际上,除了女排运动员比篮球和足球运动员更有名外,其他待遇远不及男篮和男性球员。张常宁的江苏女排队友惠若琪在5。32亿!历史第一人!2年9710万!正式续约,终老湖人?前言号外,号外。大洋彼岸传来重磅消息,詹姆斯续约了!超级大赢家没有你侬我侬,也没有欲擒故纵。在这个炎热的夏天,阿King续约的速度比博尔特还快。。据名记Shams报道,38岁的詹姆三消息!朱芳雨谈周鹏转会原因,马尚提前回归,广厦引超级外援第一,朱芳雨谈周鹏转会原因,这个休赛期,要说最大的引援消息,莫非是周鹏离开广东。按照道理来说,任何一支队伍想要冲击总冠军,都需要保留核心球员,而周鹏便是队伍的灵魂,攻防两端的轴。可队史最贵新援来了!英超中资队创2大纪录,买4人花掉1。19亿欧元北京时间8月18日早上,狼队官方宣布从葡萄牙体育引进23岁中场努内斯,转会费为4500万欧元500万欧元浮动奖金。只算4500万欧元的固定转会费,努内斯都已经打破了狼队的队史最高转无语!中超尴尬1幕过掉门将打空门,竟然高出横梁,没脸见人了中超联赛第14轮一场焦点战,沧州雄狮对阵大连人。开场没多久,场上就发生了让人尴尬的一幕,沧州雄狮获得一次很好的机会,可惜过掉门将之后打空门,竟然高出横梁,简直不可思议,没脸见人了。3小时营业额过万,获千万级融资,柠檬茶又跑出一个品牌最近,一个柠檬茶品牌活跃度很高完成千万级融资,以糯米香柠檬茶突围出圈,火爆澳门长沙等地。招牌产品爆柠糯米香,推出首月,5家店销量超10万杯。这个品牌如何打出差异化?柠檬茶品类未来的奥迪Q4etron上市,或将成为特斯拉modleY强有力的竞争者随着新能源车如同井喷一样的上市,传统的老牌车企怎么可能坐以待毙,bba也都纷纷推出了自家的新能源车型,今天说的是奥迪推出的新能源SUVQ4etron,这也让大家在买新能源SUV时有奔驰EQC我都跌破30万了,你们还盯着特斯拉ModelY?如今,很多传统车企都在进行电气化转型,像豪华品牌奔驰就是其中之一,品牌旗下有着多款纯电车型,比如奔驰EQC,这款车型的指导售价为49。1957。98万,价格不低,不过终端让利非常大游戏王规则胜利外的特殊获胜方式,貘良大爷的通灵盘必不可少各位决斗者,大家好,这里是社长!上一期我们看了游戏王的几种特殊获得胜利的方式,这一期我们继续来看一些决斗胜利的特殊方式。通灵盘当通灵盘和死之信息4种类在场上集齐时自己则会获得游戏的为什么CCD相机突然火了?能不能买?这两个月,不止一个粉丝,让村长推荐几款CCD相机。我就纳闷了,这些老古董,电子垃圾,不是应该在柜子的角落,怎么又被人提及,而且价格还炒上来了?然后我去闲鱼看了一下,个别老型号要好几男子带老婆旅行,200公里的路程开5个多小时你能少去2趟厕所吗暑假是出门旅游的高峰期,很多家庭选择在这个时候来一场自驾游,一来是行程方便,二来可以自行安排旅游时间,比跟团游自由许多。当然自驾游也有一定的缺点,尤其对于开车的司机来说,爬山涉水的24工作室藏龙卧虎?Dota2CROW冠军组永劫战队,要暴揍idol?近日,24工作室旗下的游戏永劫无间官宣了第二届永劫无间全明星周末的消息,网友们发现这第二届的含金量那可是真不低。本次参赛名单中有Mike清歌这样的永劫大神,有PDD大司马这样的游戏喝茶和喝白开水哪种更健康?喝茶喝白开水有区别吗,哪种更健康?我们都知道应该多喝水,对于我们的身体健康有好处,但是很多人都觉得白开水的味道寡淡,所以就喜欢在水里加一些东西泡着喝,比如很多人就非常喜欢喝茶水。中你还在为你家小孩子打扰你工作烦恼么?以前我家小孩老是在我跟领导开会或者专注做事情的时候跑过来打扰我,有种让人特别讨厌,让人急躁感觉,越烦躁越做不好自己的事情你是不是试过各种方法,比如爸爸开完会后好好陪你玩,给你买玩具标题生活类评测东菱布艺清洗机使用体验拥有11项专利填补布艺清洗市场空白,东菱布艺清洗机使用体验说到布艺清洗,相信大家在脑海中会浮现出很多形容词,麻烦耗时洗不干净。尤其面对大件物品的清洗更是头疼不已,例如布艺沙发地毯床美国人会在旅行时做什么粗鲁的事?在旅行越来越方便的今天,相信大家或多或少也在大街上见过外国人的面孔,但是否遇到过外国人旅行时的不礼貌行为呢?今天小锦在美版知乎Quora上看到了这个问题,忍不住分享给大家看看。接下