零拷貝讀取文件成go對象

咱們觀察到從文件讀取到go對象,須要兩次拷貝:git

  1. 從文件拷貝到內存,成爲[]byte
  2. 從[]byte,按照格式進行讀取,拷貝到go對象上

怎麼樣優化這個讀取速度呢?github

  1. 利用mmap,把文件直接映射到內存,go容許把這片內存已經轉化成[]byte來使用
  2. 直接在這個[]byte上「展開」go對象

所謂」展開「就是一個reinterpret cast,對一個指針的類型從新解讀。json

var bytes = []byte{
16, 0, 0, 0, 0, 0, 0, 0, 
5, 0, 0, 0, 0, 0, 0, 0, 
'h', 'e', 'l', 'l', 'o'}

假設有這樣一個[]byte數組。這個是直接用mmap讀取出來的。數組

var ptr = &bytes[0]

這個ptr就是這片內存區域的指針,指向了開頭的第一個元素緩存

type stringHeader struct {
    Data uintptr
    Len  int
}

header := (*stringHeader)(unsafe.Pointer(ptr))

這樣咱們就把這個內存從新解讀爲了一個stringHeader了。利用stringHeader就能夠構造出string來。優化

header.Data = uintptr(unsafe.Pointer(&bytes[16]))

把stringHeader的指針指向實際的hello數據部分。ui

str := (*string)(unsafe.Pointer(ptr))
fmt.Println(str) // "hello"

最後再把同一片內存區域解讀爲string類型,就獲得了"hello"字符串了。整個解碼過程只作了一次header.Data的更新,沒有作任何內存分配。設計

相比Java來講,go容許咱們使用go本身的heap外的內存。甚至容許把go的對象直接在這片內存上構造出來。這使得咱們的應用能夠和文件系統的緩存共享一片內存,達到內存利用率的最大化。同時相比protobuf/thrift來講,gocodec就是把cpu對值的內存表示(little endian的integer等),以及go語言對象的內存表示(stringHeader,sliceHeader)直接拷貝了,減小了編解碼的計算成本。指針

完整的代碼,歡迎star:bloomfilter_test.gocode

設計了一個編解碼格式叫 github.com/esdb/gocodec

和protobuf的對比尚未測,和json相比,毫無懸念地不在一個量級上。

gocodec 200000 10893 ns/op 288 B/op 2 allocs/opjson 300 3746169 ns/op 910434 B/op 27 allocs/op

相關文章
相關標籤/搜索