zoukankan      html  css  js  c++  java
  • CockroachDB学习笔记——[译]如何优化Go语言中的垃圾回收

    几个星期之前,我们分享了一篇关于为什么选择Go语言作为CockroachDB的开发语言的 文章 ,在编写那篇文章那篇文章时候我们收到了很多问题和反馈,主要是关于Go鱼焉已知的问题,特别是与性能、垃圾收集和死锁相关的问题。

    在这篇文章中,我们将分享一些强大的优化,以减轻Go鱼焉在垃圾回收中常见的许多性能问题
    (后续我们将跟进“死锁的乐趣”)。
    特别的,我们将分享如何使用同步来嵌入结构。
    池(Pool)和重用后备数组(Reusing backing arrays)可以最小化内存分配并减少垃圾收集开销。

    最小化内存分配 & 优化垃圾回收

    Go鱼焉区别于其他一些语言(比如Java)的一个特性是:它允许你手动管理你的内存分配。
    以往一些对象之类的东西的内存都是单独分配的,但是使用Go,你可以把一些单独分配的内存合并到一起。

    看一下下面的代码片段,它是从CockroachDB源码中提取的一段代码,它从磁盘读取数据并对其进行解码:

    metaKey := mvccEncodeMetaKey(key)
    var meta MVCCMetadata
    if err := db.GetProto(metaKey, &meta); err != nil {
        // Handle err
    }
    ...
    valueKey := makeEncodeValueKey(meta)
    var value MVCCValue
    if err := db.GetProto(valueKey, &value); err != nil {
        // Handle err
    }
    

    在这里,我们声明了一个类型 getBuffer ,它包括两个不同的结构: MVCCMetadata 和 MVCCValue (两者都是protobuf对象)。
    第三个成员是一个数组,虽然你在Go语言中看到数据不如看到分片(slice)那么频繁。

    当你声明了一个固定大小(1024字节)的数组后,对这个数组进行的所有操作将不会占用额外的内存。
    所以我们可以把三个对象全部放到这个getBuffer格式的数组里,这样操作将使得我们原本需要使用四个对象的内存,现在只需要一个。
    注意,我们这里为两个键分配同一个数组的方法很好,因为这两个键不会同时使用到。
    (译者注:在这个使用场景下,我是IO没兑取一组数据,存到数组中,再进行导入数据库的操作,流水式的操作,所以不会同时使用到一个数组。)

    (同步池)sync.Pool:

    var getBufferPool = sync.Pool{
           New: func () interface{} {
                  return &getBuffer{}
           },
    }
    

    说实话,我们花了一段时间才知道同步池(sync.Pool)实际上是什么,以及为什么我们要使用它。
    这是一个空闲列表,它重用垃圾回收周期之间的分配,这样你就不必再分配另一个垃圾回收器收集的对象。
    每次垃圾收集循环开始时,它清除池中的内容。

    举一个使用同步池的例子:

    buf := getBufferPool.Get().(*getBuffer)
    defer getBufferPool.Put(buf)
    
    key := append(buf.key[0:0], …)
    

    首先,用工厂函数声明一个全局同步池对象,在这种情况下将分配一个getBuffer结构并返回它。
    我们可以从池中获得一个新的getBuffer,而不是创建一个新的getBuffer。
    Pool.Get返回一个空的接口(interface),然后我们将断言键入正确的指针类型。
    当我们完成这部操作时,我们将它放回池(Pool)中。
    最终的结果是,我们甚至不必做一个分配来获得缓冲结构(the Buffer struct)。

    数组与切片(Arrays & Slices)

    值得注意的是数组和切片在Go中是不同的类型,几乎所有的事物都是以切片而不是数组来处理的。
    只需使用方括号语法[:0],就可以从数组中获得一个切片。

    key := append (buf.key[0:0], …)
    

    这将创建一个由数组支持的零长度切片。
    事实上,这个切片已经有一个后备存储器,它意味着任何追加将实际进入该数组,而不是创建一个新的分配。
    因此,当我们对键进行解码时,我们可以将其附加到从该缓冲区创建的切片中。
    只要这个键不超过 1 KB 大小,我们不用再去分配任何额外空间。
    它只是重复使用我们已经分配的数组的内存空间。

    很少出现键所需内存大于 1 KB 的情况,但是即使在这种情况下,它(译者觉得是Go语言底层的数组和切片机制)也会
    透明地为这个键分配一个新的备用数组,所以我们不用担心这种情况的出现。
    (译者注:就是他有足够的备胎,即使你这个轮胎爆了也可以马上换,而且他会自动帮你换,你只需要开车就可以了)

    Gogoprotobuf 对比 Google protobuf(Gogoprotobuf vs Google protobuf)

    最终,我们使用 protocol buffers 来在硬盘中存储数据。
    然而,我们不推荐使用谷歌自己的原 protobuf 库,
    而是使用一个称为 gogoprotobuf 的分支。

    Gogoprotobuf遵循我们上面概述的许多原则来避免不必要的分配。
    特别地,它允许编组到字节切片中,该字节切片可以由数组支持以避免分配。
    此外,非可空注释允许嵌入没有分配的消息,当已知的消息总是存在时,这是有用的。

    gogoprotobuf 的最后一点优化是它使用了生成的编组和解组协程(the generated marshalling and unmarshalling routines),
    在标准的谷歌protobuf库中,它提供了基于反射机制的编组和解组的良好性能提升。

    总结

    通过结合上述技术,我们已经能够最小化Go的垃圾收集的性能开销并进行优化以获得更好的性能。
    当我们接近beta版本并更加关注内存分析时,我们将在后续的文章中分享我们的结果。
    当然,如果你已经学习了Go的其他性能优化,我们会洗耳恭听。

    插图: Mei-Li Nieuwland
    (译者注:点进去看了之后,发现是一个很可爱的妹子)

  • 相关阅读:
    特征可视化技术(CAM)
    特征可视化技术(CAM)
    attention
    attention
    Aggregated Residual Transformations for Deep Neural Networks(ResNeXt)
    attention
    attention
    位姿估计
    attention
    通过几个例子看下seajs模块标识符(有图有真相)
  • 原文地址:https://www.cnblogs.com/zifeiy/p/9266372.html
Copyright © 2011-2022 走看看