深入理解伙伴算法及其改进

zqlucky zqlucky     2022-10-28     635

关键词:

       今天遇到很好的一个腾讯面试官,进一步探讨了伙伴算法,面试官非常nice,对伙伴算法的优缺点详细给我讲了一下,发现这个算法值得深入研究一波~

看了很多资料,下面整理资料,然后谈谈自己的理解。

 

  • 体会

  Linux操作系统主要的内存分配算法是伙伴系统(Buddy算法),机制是按照2的幂次方进行分块,然后根据需求分配差不多的内存块给使用者,伙伴系统是一个结合了2的方幂个分配器和空闲缓冲区合并计技术的内存分配方案。分配和释放机制十分强大。

  不过缺点是会分配多余的空间,释放的时候是释放相邻的大小相同的内存块,如果中间有一个小碎片,那么就不能合并,针对这种情况,提出了一种辅助算法slab算法。机制:其工作是针对一些经常分配并释放的对象,如进程描述符等,这些对象的大小一般比较小,如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内碎片,而且处理速度也太慢。而slab分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免这些内碎片。slab分配器并不丢弃已分配的对象,而是释放并把它们保存在内存中。当以后又要请求新的对象时,就可以从内存直接获取而不用重复初始化。 

 

  • 伙伴系统和slab机制[1]

伙伴系统

  Linux内核中采用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系统中,用到了四级页表。四级页表分别为:
   页全局目录(Page Global Directory)
  页上级目录(Page Upper Directory)
  页中间目录(Page Middle Directory)
  页表(Page Table)
  页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址,每一个页表项指向一个页框。Linux中采用4KB大小的页框作为标准的内存分配单元。
   在实际应用中,经常需要分配一组连续的页框,而频繁地申请和释放不同大小的连续页框,必然导致在已分配页框的内存块中分散了许多小块的空闲页框。这样,即使这些页框是空闲的,其他需要分配连续页框的应用也很难得到满足。
   为了避免出现这种情况,Linux内核中引入了伙伴系统算法(buddy system)。把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块。最大可以申请1024个连续页框,对应4MB大小的连续内存。每个页框块的第一个页框的物理地址是该块大小的整数倍。
   假设要申请一个256个页框的块,先从256个页框的链表中查找空闲块,如果没有,就去512个页框的链表中找,找到了则将页框块分为2个256个页框的块,一个分配给应用,另外一个移到256个页框的链表中。如果512个页框的链表中仍没有空闲块,继续向1024个页框的链表查找,如果仍然没有,则返回错误。

   页框块在释放时,会主动将两个连续的页框块合并为一个较大的页框块。 

 Buddy算法的优缺点:

  1)尽管伙伴内存算法在内存碎片问题上已经做的相当出色,但是该算法中,一个很小的块往往会阻碍一个大块的合并,一个系统中,对内存块的分配,大小是随机的,一片内存中仅一个小的内存块没有释放,旁边两个大的就不能合并。
技术分享图片
  2)算法中有一定的浪费现象,伙伴算法是按2的幂次方大小进行分配内存块,当然这样做是有原因的,即为了避免把大的内存块拆的太碎,更重要的是使分配和释放过程迅速。但是他也带来了不利的一面,如果所需内存大小不是2的幂次方,就会有部分页面浪费。有时还很严重。比如原来是1024个块,申请了16个块,再申请600个块就申请不到了,因为已经被分割了。

  3)另外拆分和合并涉及到 较多的链表和位图操作,开销还是比较大的。

Buddy(伙伴的定义):

这里给出伙伴的概念,满足以下三个条件的称为伙伴:
  1)两个块大小相同;
  2)两个块地址连续;
  3)两个块必须是同一个大块中分离出来的;

Buddy算法的分配原理:

  假如系统需要4(2*2)个页面大小的内存块,该算法就到free_area[2]中查找,如果链表中有空闲块,就直接从中摘下并分配出去。如果没有,算法将顺着数组向上查找free_area[3],如果free_area[3]中有空闲块,则将其从链表中摘下,分成等大小的两部分,前四个页面作为一个块插入free_area[2],后4个页面分配出去,free_area[3]中也没有,就再向上查找,如果free_area[4]中有,就将这16(2*2*2*2)个页面等分成两份,前一半挂如free_area[3]的链表头部,后一半的8个页等分成两等分,前一半挂free_area[2]
的链表中,后一半分配出去。假如free_area[4]也没有,则重复上面的过程,知道到达free_area数组的最后,如果还没有则放弃分配。

 

技术分享图片

 

 Buddy算法的释放原理:

  内存的释放是分配的逆过程,也可以看作是伙伴的合并过程。当释放一个块时,先在其对应的链表中考查是否有伙伴存在,如果没有伙伴块,就直接把要释放的块挂入链表头;如果有,则从链表中摘下伙伴,合并成一个大块,然后继续考察合并后的块在更大一级链表中是否有伙伴存在,直到不能合并或者已经合并到了最大的块(2*2*2*2*2*2*2*2*2个页面)。 

slab机制

  slab是Linux操作系统的一种内存分配机制。其工作是针对一些经常分配并释放的对象,如进程描述符等,这些对象的大小一般比较小,如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内碎片,而且处理速度也太慢。而slab分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免这些内碎片。slab分配器并不丢弃已分配的对象,而是释放并把它们保存在内存中。当以后又要请求新的对象时,就可以从内存直接获取而不用重复初始化。 


Linux 的slab 可有三种状态:
   满的:slab 中的所有对象被标记为使用。
   空的:slab 中的所有对象被标记为空闲。
   部分:slab 中的对象有的被标记为使用,有的被标记为空闲。
slab 分配器首先从部分空闲的slab 进行分配。如没有,则从空的slab 进行分配。如没有,则从物理连续页上分配新的slab,并把它赋给一个cache ,然后再从新slab 分配空间。

与传统的内存管理模式相比, slab 缓存分配器提供了很多优点。
  1、内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配。
  2、slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能,从而避免了常见的碎片问题。
  3、slab 分配器还支持通用对象的初始化,从而避免了为同一目的而对一个对象重复进行初始化。
  4、slab 分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,从而提高缓存的利用率并获得更好的性能。

 

 

 

 

[1]伙伴系统和slab机制。https://blog.csdn.net/zhouwei1221q/article/details/48242535

[2]Randal E.Bryant,深入理解计算机系统,[M].机械工业出版社


























c语言的罗盘——指针!深入理解c语言指针及其应用(代码片段)

...;字符串库函数和指针等内容,在这篇文章我们将继续深入了解有关字符串库函数和指针的探索。📒博客主页:https://blog.csdn.net/m0 查看详情

ambari深入学习(iii)-开源使用及其改进思考

Ambari采用的不是一个新的思想和架构,也不是完成了软件的新的革命,而是充分利用了一些已有的优秀开源软件,巧妙地把它们结合起来,使其在分布式环境中做到了集群式服务管理能力、监控能力、展示能力。这些优秀开源软... 查看详情

深入理解java虚拟机--垃圾收集及故障诊断

1.垃圾收集算法  1.1标记-清除算法     算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,标记过程上一篇博客说过,后续的几种算法都是基于这个算法对其不足进行改进.... 查看详情

深入理解java虚拟机--垃圾收集及故障诊断

1.垃圾收集算法  1.1标记-清除算法     算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,标记过程上一篇博客说过,后续的几种算法都是基于这个算法对其不足进行改进.... 查看详情

kmp及其改进算法

...进方法。若发现不正确的地方,欢迎交流指出,谢谢!KMP算法的基本思想: KMP的算法流程:每当一趟匹配过程中出现字符比较不等时,不需回溯i指针,而是利用已经得到的部分匹配的结果将模式向右滑动尽可能远的一段距离后... 查看详情

深入理解:linearregression及其正则方法

  这是最近看到的一个平时一直忽略但深入研究后发现这里面的门道还是很多,LinearRegression及其正则方法(主要是Lasso,Ridge,ElasticNet)这一套理论的建立花了很长一段时间,是很多很多人的论文一点点堆起来的一套理论体系.如果你... 查看详情

从tcp拥塞本质看bbr算法及其收敛性(附cubic的改进/ncl机制)

...又要扯泊松到达和排队论了。事实上,TCP拥塞的本质要好理解的多!TCP拥塞绝大部分是由于其”加性增,乘性减“的特性造成的!       也就是说,是TCP自己造成了拥塞!TCP加性增乘性减的特性引发了丢... 查看详情

jvm,深入理解java虚拟机,垃圾收集算法与垃圾收集器

垃圾收集算法由于垃圾收集算法的实现涉及大量的程序细节,而且各个平台的虚拟机操作内存的方法又各不相同,因此本节不打算过多地讨论算法的实现,只是介绍几种算法的思想及其发展过程。标记-清除算法最基... 查看详情

深入理解tcp协议及其源代码-拥塞控制算法分析(代码片段)

这是我的第五篇博客,鉴于前面已经有很多人对前四个题目如三次握手等做了很透彻的分析,本博客将对拥塞控制算法做一个介绍。首先我会简要介绍下TCP协议,其次给出拥塞控制介绍和源代码分析,最后结合源代码具体分析拥... 查看详情

关于dijkstra算法的一点理解

...。今天看的是Dijkstra算法,这个算法有点难理解,如果不深入想的话想要搞明白还是不容易的。弄了一个晚自习,先看书大致明白了原理,就根据书上的代码敲,边敲边深入思考,第一遍敲完运行失败,然后回过头在分析代码,... 查看详情

yolov3算法的详细说明

...以v1和v2为基础,关于YOLO1和YOLO2的部分析请移步​​YOLOv1深入理解​​​ 和 ​​YOLOv2/YOLO9000深入理解​​。YOLO3主要的改进 查看详情

深度强化学习-策略梯度算法深入理解

...式比较疑惑,本文就带领大家剖析其中的原理,深入理解策略梯度算法的公式。本文主要参考了百度飞桨的视频PolicyGradient算法有兴趣的小伙伴可以看看,我觉得讲的非常透彻。2手写数字识别我们先来看一下手写数字... 查看详情

深入理解dataset及其用法

  DataSet是ADO.NET的中心概念。可以把DataSet当成内存中的数据库,DataSet是不依赖于数据库的独立数据集合。所谓独立,就是说,即使断开数据链路,或者关闭数据库,DataSet依然是可用的,DataSet在内部是用XML来描述数据的,由于... 查看详情

深入理解es6之《改进的数组功能》(代码片段)

Array.of方法由于Array构造函数创建数组时的怪异行为,比方说如下:letitems=newArray(2)console.log(items.length)//2items=newArray("2")console.log(items.length)//1Array.of方法总会创建一个包含所有参数的数组letitems=Array.of(1,2)console.log(items.le 查看详情

yolov3算法的详细说明

...于是以v1和v2为基础,关于YOLO1和YOLO2的部分析请移步YOLOv1深入理解和YOLOv2/YOLO9000深入理解。YOLO3主要的改进有:调整了网络结构;利用多尺度特征进行对象检测;对象分类用Logis 查看详情

jvm,深入理解java虚拟机,垃圾收集算法与垃圾收集器

垃圾收集算法由于垃圾收集算法的实现涉及大量的程序细节,而且各个平台的虚拟机操作内存的方法又各不相同,因此本节不打算过多地讨论算法的实现,只是介绍几种算法的思想及其发展过程。标记-清除算法最基... 查看详情

最短路求两点间最短路径的改进的dijkstra算法及其matlab实现

代码来源:《图论算法及其matlab实现》(北京航空航天出版社)P18 书中提出了基于经典Dijkstra算法改进的两种算法。其中算法Ⅱ的效率较高。代码如下:1functiona=Dijk(a)2%a(输入量)表示图的邻接矩阵3%a(输出量)表示所求最短路径... 查看详情

深入理解lambda函数及其用法

Lambda函数又称匿名函数,匿名函数就是没有名字的函数,函数没有名字也行?当然可以啦。有些函数如果只是临时一用,而且它的业务逻辑也很简单时,就没必要非给它取个名字不可。先来看个简单lambda函数>>>lambdax,y:x+y&l... 查看详情