126

零拷贝读取文件成go对象

 6 years ago
source link: https://zhuanlan.zhihu.com/p/31765927?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

零拷贝读取文件成go对象

problem solver

我们观察到从文件读取到go对象,需要两次拷贝:

  1. 从文件拷贝到内存,成为[]byte
  2. 从[]byte,按照格式进行读取,拷贝到go对象上

怎么样优化这个读取速度呢?

  1. 利用mmap,把文件直接映射到内存,go允许把这片内存直接转化成[]byte来使用
  2. 直接在这个[]byte上“展开”go对象

所谓”展开“就是一个reinterpret cast,对一个指针的类型重新解读。

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数据部分。

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.go

设计了一个编解码格式叫 github.com/esdb/gocodec

和protobuf的对比还没有测,和json相比,毫无悬念地不在一个量级上。

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK