零拷貝讀取文件成go對象
我們觀察到從文件讀取到go對象,需要兩次拷貝:
- 從文件拷貝到內存,成為[]byte
- 從[]byte,按照格式進行讀取,拷貝到go對象上
怎麼樣優化這個讀取速度呢?
- 利用mmap,把文件直接映射到內存,go允許把這片內存直接轉化成[]byte來使用
- 直接在這個[]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数据传输需要注意什么?