Monibuca v5 實現優雅內存分配器

背景

v4 中使用了鏈表存儲了不同大小的內存塊的方式進行內存池的實現(參考這篇v4內存複用機制),實際測試中發現內存浪費比較嚴重,因此如何設計出使用效率高,操作簡潔的內存池就成了 v5 的一個任務。

使用 make

使用 go 原生的內存分配,意味着交給 GC 來回收,在m7s中測試發現gc 佔據非常大的開銷。

自定義內存分配

C 風格的內存分配

void * mem = malloc(100)
free(mem)

這種分配方式最廣爲人知,也是最簡潔易懂的,因此如果能實現這種方式,是最佳的。

設想一下

func (ma *MemoryAllocator) Malloc(size int) (memory []byte) {
	
}

func (ma *MemoryAllocator) Free(memory []byte) {

}

問題:如何在 Free 的時候知道是哪塊內存?如果把這個字節數組直接存儲就會回到 v4 的版本,顯然不是我們想要的。 我們想要的是在一塊大的數組中切割分配,這樣纔能有效利用內存。

切片分配

假設有一個大數組,用來緩存內存,防止 GC

var mem = make([]byte,65535)

分配內存,就是切片

s1 := mem[0:1024]

分配第二塊內存s2

s2 := mem[1024:2048]

最簡單的方式就是記錄一個已經分配的索引,第一次爲 0,第二次爲 1024

type MemoryAllocator struct {
	start  int64
	memory []byte
}
func (ma *MemoryAllocator) Malloc(size int) (memory []byte) {
	memory = ma.memory[ma.start:size]
  ma.start+= size
  return
}

回收

內存切出去容易,如何回收呢?

ma.Free(s2)

如何知道 s2 屬於哪一部分呢?即使知道,如何修改原來的結構體使得下次分配可以利用回收過的內存呢?

使用附加信息

這種方式,就和 v4 一樣,將額外的信息隨同分配的內存給出去,回收的時候再一起帶回來,但是不夠簡潔,我們希望回收的時候就是傳[]byte

判斷指針

我們知道同一塊內存的底層的指針值肯定是相同的,即使切片被淺複製也一樣。

ptr := uintptr(unsafe.Pointer(&mem[0]))

當然,有可能傳入的切片長度爲 0,可以用下面的方法規避

ptr := uintptr(unsafe.Pointer(&mem[:1][0]))

有了這個指針值,我只需要和內存池的起始指針進行比較,就可以得到在內存池中的偏移。從而爲回收奠定基礎。

標記

爲了回收再利用,需要對可以分配的內存信息進行存儲,這個信息就是標記偏移地址段,即 start:end。 可以用鏈表存儲,[start,end],[start,end],[start,end],[start,end]

每一段代表可用的內存。當回收內存時,只需按照大小順序插入這個鏈表即可

用數組也可以,但是由於數組對隨機插入性能較差,因此用鏈表更合適

當然如果前一個 end 等於下一個 start,就可以合併: 例如[1:1024],[1024,2048] 就可以合併成 [1:2048],相當於碎片整理。

實現

type MemoryAllocator struct {
	start  int64
	memory []byte
	Size   int
	blocks *List[Block]
}

func NewMemoryAllocator(size int) (ret *MemoryAllocator) {
	ret = &MemoryAllocator{
		Size:   size,
		memory: make([]byte, size),
		blocks: NewList[Block](),
	}
	ret.start = int64(uintptr(unsafe.Pointer(&ret.memory[0])))
	ret.blocks.PushBack(Block{0, size})
	return
}

具體代碼可以到倉庫 github.com/langhuihui/monibucav5 分支裏面找到

進階

單個內存分配器可分配的內存有限,那麼一個可以不斷增長的需求如何滿足呢? 可以實現動態創建內存分配器的高階內存分配器就可以解決了

type ScalableMemoryAllocator []*MemoryAllocator

原理也很簡單,不夠就創建,Free 的時候就挨個查找。 優化後內存不再呈現鋸齒狀了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章