零拷贝读取文件成go对象

taowen · · 3419 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

我们观察到从文件读取到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


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:taowen

查看原文:零拷贝读取文件成go对象

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3419 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传