searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享

三种内存分配的算法比较

2024-11-18 09:21:50
2
0

内存的管理,其实就是三个层面,一个是上层的应用层,这个基本上所有的程序员都可以接触到,一个数组的分配是分配在栈上还是堆上,都可以由开发人员来清晰的控制;第二层面是在库这个层面上,包括前面分析的golang的内存管理,涉及到的内存逃逸,可能你认为的栈上的内存其实是分配到了堆上。最后就是内核层面的内存管理了,它是直接和物理内存打交道的,所以真正的内存分配和释放归根到底还在这里。
什么叫核心,就是难,不好整。所以在OS的开发上,内存管理这一块仍然是硬骨头。电脑为什么有时候突然死机,有相当一部分就是内存管理出现了问题,该用的内存分配不出来,该回收的内存回收不了,或者说回收了但都是碎片。业界目前对内存的管理有三个库比较有名气也就是tcmalloc,jecmalloc和ptmalloc。
内存管理的细节比较不好控制,但整体却好理解,基本就是做生意的,从上游批发,向下游零卖,实现利润的最大化即可。而库做为应用和系统的中间层,就更要做好这种关系,把内存的应用效率最大化。

一、Tcmalloc

tcmalloc是Google的一种内存管理库,其把内存分为页Page,然后实现了三级缓存即ThreadCache(线程级缓存),Central Cache(中央缓存:CentralFreeeList),PageHeap(页缓存)(可以翻看前面的tcmalloc的源码分析系列),然后又通过Span和PageMap来处理页和其的相互映射,达到快速定位的目的。
tcmalloc在锁上使用了自旋锁,速度肯定比ptmalloc效率要高。它的主要优点是:小内存可以不用锁,大内存直接分配,使用自旋锁替代了互斥锁,内存碎片得到了进一步的控制。
但是,在某些场景下,比如大内存分配频繁,CPU占用率会暴增。

二、Jemalloc

jemalloc是facebook的一种内存管理库,其一个重点的特点是对多核多线程比较友好。换句话说越是好的硬件,越是复杂的软件,内存足够大的情况下,它的优势越明显。说到这里,通过前面分析多核和多线程编程其实就明白了,它一定是多核和多线程间的竞态做的好。回想前面的多核编程中提到的,缩小锁的精度,增加锁的数量等等。在jemalloc中肯定有所体现。
jemalloc把内存底层的管理分成多个arenas,每个arenas有多个chunk,一个arenas对应着一个线程,并最终将二者绑定。这不就是刚刚提到的减少了锁的粒度和范围。但是arenas数量有限,不可能每个线程独占一个arenas,这就需要内部的锁来控制arenas的内部的内存的分配回收的安全性。
chunk默认是4M,它同样以页(4K)为基础的管理单位。它和tcmalloc一样也有size class的概念,其实就是进一步细分内存颗粒度的一种方式。在chunk中默认前6个页是记录元数据的,后面是通过bin记录的runs,空闲的run由红黑树来管理。如同tcmalloc,切分内存是在chunk的基础上进行的,分把其它按run进行使用。
jemalloc同样也把内存分成三类 small object ( 1 ~57344)、 large object (57345 ~4MB )、 huge object(其它),它同样也有一层缓冲层tcache,用来提高内存的管理速度。

三、Ptmalloc

此库是GLIBC的内存分配的标准库。它是在Doug Lea的malloc的主分配区基础上增加了动态的内存分配管理,减少了内存分配过程的激烈的锁的竞争。ptmalloc通过环形链表来管理它们,但在多线程访问同一个分配区时,仍然需要加锁。在ptmalloc中一个进程只有一个主分配区并有几个动态分配区,动态分配区只可增长不可减少。主分配区通过sbrk从heap区域分配内存而动态分配区通过mmap分配内存。
在多线程中,ptmalloc是按照分块处理内存的,也就是说把内存划分成几类,再划分成块:
Fast bins:小于64字节的内存,可以理解为小内存块的高速缓存,回收和分配时会优先从这种链表中进行处理。
Usorted bin:一个,unsorted bin最先处理所有回收块,分配时也要先从此处查看有无合适块,找到直接返回否则将放入small bins或是large bins中。
Small bins:存放固定大小的块,共64个bin,小内存分配时从此处匹配。其块从16字节以相关8或16字节递增。
Large bins:管理大于等于512字节或1024字节的空闲块,这些chunk使用双向链表的形式按大小顺序排序,分配内存时按最近匹配方式从large bins中分配chunk。
ptmalloc有以下缺点:
top chunk的机制使得内存必须从后向前释放。
多线程多核是痛点,内存的开销相当大。
多线程中内存的分配回收无法做到很好平衡,即A线程的内存在用完成后无法在库内部向其它线程开放使用。
chunk中浪费8字节,且容易产生内存碎片。

四、总结

总体的看来,ptmalloc是一种取中间的态度,尽量多适应一些场景,但效率就有一些问题了。而tcmalloc改进了ptmalloc一些问题并对多核多线程提供了一些优化,但自身占用的内存有所增多并且在某些场景下CPU占用率会大涨。jemalloc主要是对多核和多线程支持的更好,但内存占用更多,而且在一些普通场景下反而表现不是很优秀。这在实际的一些测试中也得到了验证,一用来说,tcmalloc和jemalloc在多核和多线程下优势更明显,特别是在线程稳定且数量较多情况下,jemalloc更有优势。
谈起内存管理管理,大家不要怕,也不要觉得多么遥远。如果写过内存池,就可以说写过最简单的内存管理器,只不过没有实现具体的一些细节,比如重载new和delete等接口罢了。很多东西是知易行难,做简单容易,做精难。而内存管理就是这么一个方向。要想写好一个内存管理,首先要明白前人怎么做的,现在流行的算法有什么优缺点,适应性如何?想要做一个全场景适合的优秀的内存管理器,目前看来应该是不可能的。一般来说是适应主流的场景或者特定的某些重要场景。毕竟现在各种硬件,各种机器都各有千秋,不可能一套机制全部能达到最优。
这也是好事啊,没有最优,那就总有前进的方向。

0条评论
作者已关闭评论
赵****生
5文章数
0粉丝数
赵****生
5 文章 | 0 粉丝

三种内存分配的算法比较

2024-11-18 09:21:50
2
0

内存的管理,其实就是三个层面,一个是上层的应用层,这个基本上所有的程序员都可以接触到,一个数组的分配是分配在栈上还是堆上,都可以由开发人员来清晰的控制;第二层面是在库这个层面上,包括前面分析的golang的内存管理,涉及到的内存逃逸,可能你认为的栈上的内存其实是分配到了堆上。最后就是内核层面的内存管理了,它是直接和物理内存打交道的,所以真正的内存分配和释放归根到底还在这里。
什么叫核心,就是难,不好整。所以在OS的开发上,内存管理这一块仍然是硬骨头。电脑为什么有时候突然死机,有相当一部分就是内存管理出现了问题,该用的内存分配不出来,该回收的内存回收不了,或者说回收了但都是碎片。业界目前对内存的管理有三个库比较有名气也就是tcmalloc,jecmalloc和ptmalloc。
内存管理的细节比较不好控制,但整体却好理解,基本就是做生意的,从上游批发,向下游零卖,实现利润的最大化即可。而库做为应用和系统的中间层,就更要做好这种关系,把内存的应用效率最大化。

一、Tcmalloc

tcmalloc是Google的一种内存管理库,其把内存分为页Page,然后实现了三级缓存即ThreadCache(线程级缓存),Central Cache(中央缓存:CentralFreeeList),PageHeap(页缓存)(可以翻看前面的tcmalloc的源码分析系列),然后又通过Span和PageMap来处理页和其的相互映射,达到快速定位的目的。
tcmalloc在锁上使用了自旋锁,速度肯定比ptmalloc效率要高。它的主要优点是:小内存可以不用锁,大内存直接分配,使用自旋锁替代了互斥锁,内存碎片得到了进一步的控制。
但是,在某些场景下,比如大内存分配频繁,CPU占用率会暴增。

二、Jemalloc

jemalloc是facebook的一种内存管理库,其一个重点的特点是对多核多线程比较友好。换句话说越是好的硬件,越是复杂的软件,内存足够大的情况下,它的优势越明显。说到这里,通过前面分析多核和多线程编程其实就明白了,它一定是多核和多线程间的竞态做的好。回想前面的多核编程中提到的,缩小锁的精度,增加锁的数量等等。在jemalloc中肯定有所体现。
jemalloc把内存底层的管理分成多个arenas,每个arenas有多个chunk,一个arenas对应着一个线程,并最终将二者绑定。这不就是刚刚提到的减少了锁的粒度和范围。但是arenas数量有限,不可能每个线程独占一个arenas,这就需要内部的锁来控制arenas的内部的内存的分配回收的安全性。
chunk默认是4M,它同样以页(4K)为基础的管理单位。它和tcmalloc一样也有size class的概念,其实就是进一步细分内存颗粒度的一种方式。在chunk中默认前6个页是记录元数据的,后面是通过bin记录的runs,空闲的run由红黑树来管理。如同tcmalloc,切分内存是在chunk的基础上进行的,分把其它按run进行使用。
jemalloc同样也把内存分成三类 small object ( 1 ~57344)、 large object (57345 ~4MB )、 huge object(其它),它同样也有一层缓冲层tcache,用来提高内存的管理速度。

三、Ptmalloc

此库是GLIBC的内存分配的标准库。它是在Doug Lea的malloc的主分配区基础上增加了动态的内存分配管理,减少了内存分配过程的激烈的锁的竞争。ptmalloc通过环形链表来管理它们,但在多线程访问同一个分配区时,仍然需要加锁。在ptmalloc中一个进程只有一个主分配区并有几个动态分配区,动态分配区只可增长不可减少。主分配区通过sbrk从heap区域分配内存而动态分配区通过mmap分配内存。
在多线程中,ptmalloc是按照分块处理内存的,也就是说把内存划分成几类,再划分成块:
Fast bins:小于64字节的内存,可以理解为小内存块的高速缓存,回收和分配时会优先从这种链表中进行处理。
Usorted bin:一个,unsorted bin最先处理所有回收块,分配时也要先从此处查看有无合适块,找到直接返回否则将放入small bins或是large bins中。
Small bins:存放固定大小的块,共64个bin,小内存分配时从此处匹配。其块从16字节以相关8或16字节递增。
Large bins:管理大于等于512字节或1024字节的空闲块,这些chunk使用双向链表的形式按大小顺序排序,分配内存时按最近匹配方式从large bins中分配chunk。
ptmalloc有以下缺点:
top chunk的机制使得内存必须从后向前释放。
多线程多核是痛点,内存的开销相当大。
多线程中内存的分配回收无法做到很好平衡,即A线程的内存在用完成后无法在库内部向其它线程开放使用。
chunk中浪费8字节,且容易产生内存碎片。

四、总结

总体的看来,ptmalloc是一种取中间的态度,尽量多适应一些场景,但效率就有一些问题了。而tcmalloc改进了ptmalloc一些问题并对多核多线程提供了一些优化,但自身占用的内存有所增多并且在某些场景下CPU占用率会大涨。jemalloc主要是对多核和多线程支持的更好,但内存占用更多,而且在一些普通场景下反而表现不是很优秀。这在实际的一些测试中也得到了验证,一用来说,tcmalloc和jemalloc在多核和多线程下优势更明显,特别是在线程稳定且数量较多情况下,jemalloc更有优势。
谈起内存管理管理,大家不要怕,也不要觉得多么遥远。如果写过内存池,就可以说写过最简单的内存管理器,只不过没有实现具体的一些细节,比如重载new和delete等接口罢了。很多东西是知易行难,做简单容易,做精难。而内存管理就是这么一个方向。要想写好一个内存管理,首先要明白前人怎么做的,现在流行的算法有什么优缺点,适应性如何?想要做一个全场景适合的优秀的内存管理器,目前看来应该是不可能的。一般来说是适应主流的场景或者特定的某些重要场景。毕竟现在各种硬件,各种机器都各有千秋,不可能一套机制全部能达到最优。
这也是好事啊,没有最优,那就总有前进的方向。

文章来自个人专栏
linux 性能优化
5 文章 | 1 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0