零拷貝讀取文件成go對象

我們觀察到從文件讀取到go對象,需要兩次拷貝:

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

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

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

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

var bytes = []byte{n16, 0, 0, 0, 0, 0, 0, 0, n5, 0, 0, 0, 0, 0, 0, 0, nh, e, l, l, o}n

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

var ptr = &bytes[0]n

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

type stringHeader struct {ntData uintptrntLen intn}nheader := (*stringHeader)(unsafe.Pointer(ptr))n

這樣我們就把這個內存重新解讀為了一個stringHeader了。利用stringHeader就可以構造出string來。

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

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

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

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

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

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

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

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

gocodec 200000t 10893 ns/opt 288 B/opt 2 allocs/op

json 300t 3746169 ns/opt 910434 B/opt 27 allocs/op

推薦閱讀:

豆瓣閱讀器通過 JSON 獲取文章內容,看到的數據格式是被混淆/加密的,這是通過什麼原理實現的?如何將其解碼?
「每日一題」JSON 是什麼?
json是什麼?
JSON适合大数据传输吗?跨语言JSON数据传输需要注意什么?

TAG:JSON | Go语言 | protobuf |