Treasure / zmalloc

Created Thu, 21 Apr 2022 00:00:00 +0000
795 Words

无论是对于C版本还是Go版本的skynet-x而言,一个高效的内存分配器可以提高内存的使用效率,这里效率无论是对于内存碎片亦或是GC而言,都是一种更高效的手段

基于 SLAB 算法的分段内存分配器

SLAB 最开始是阅读 linux 源码学习的算法,在skynet-x中它确实有更优秀的性能,因为它直接分配了一块大共用内存,所以不会产生任何GC和真实分配,但在业务开发过程中,一旦忘记释放 那么这段内存将不能再被使用和获取了(也就是野指针),直到程序结束。最后的保守策略依然会向runtime申请内存,将会导致内存占用过高。

而且内置的Debug模式也无法定位到这个指针,原因在于 golang 堆栈伸缩会导致指针地址变动,所以 Debug 只能定位到存在 memory-leak,而无法知道具体位置。若需要具体位置则需要hook这个调用栈,性能方面得不偿失

基于 sync.Pool 的分段内存分配器

将不同size的buffer放入不同的池中,按需进行分配,减少race的开销,这个方法虽然简单,但是性能是低于 slab-allocator

但它确实能减轻心智负担,代价就是牺牲了部分性能以及gc压力,但这也是skynet-x默认使用的策略。如果需要使用可以在编译指令中指明slab

Zmalloc

zmlloc 本身也是 slab的升级版本,增加可伸缩链表实现对于预分配内存额外部分的缓冲池。

在24个线程的cpu条件测试结果如下,zmalloc保持了一种稳定的时间复杂度,额外产生的内存分配也很少

- time(ns) alloc/op(B)
slab-128 23.5 1
sync.Pool-128 2490 65562
zmalloc-128 43.31 16
slab-256 24 256
sync.Pool-256 2204 65562
zmalloc-256 44 16
slab-1024 92 1024
sync.Pool-1024 2490 65562
zmalloc-1024 49 17
slab-4096 365 4096
sync.Pool-4096 2210 65562
zmalloc-4096 45 23

API

  • skynet.zalloc(n) 用以分配指定大小的内存块,考虑到 64在go中为tiny-size,直接会从P上分配,所以zmalloc分配块从128开始

  • skynet.zrealloc(buf,n) realloc函数会先检查buf,确保是否需要重新分配内存

  • skynet.zmemmove(buf,data)

  • skynet.zfree(buf) 释放一段内存,对于slab 而言,它会检查buf的地址是否属于当前class,然后判断是否属于其中的某个segment,否则会继续使用系统的gc。