• 用golang开发系统软件的一些细节


    🚀 优质资源分享 🚀

    学习路线指引(点击解锁) 知识定位 人群定位
    🧡 Python实战微信订餐小程序 🧡 进阶级 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
    💛Python量化交易实战💛 入门级 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

    用golang开发系统软件的一些细节

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


    (本文的pdf版本)

    众所周知,golang非常适合用于开发后台应用,但也通常是各种各样的应用层软件。

    开发系统软件, 目前的首选还是C++, C, rust等语言。相比应用软件,系统软件需要更加稳定,更加高效。其维持自身运行的资源消耗要尽可能小,然后才可以把更多CPU、内存等资源用于业务处理上。简单来说,系统软件在CPU、内存、磁盘、带宽等计算机资源的使用上要做到平衡且极致。

    **golang代码经过写法上的优化,是可以达到接近C的性能的。**现在早已出现了很多用golang完成的系统软件,例如很优秀的etcd, VictoriaMetrics等。VictoriaMetrics是Metric处理领域优秀的TSDB存储系统, 在阅读其源码后,结合其他一些golang代码优化的知识,我将golang开发系统软件的知识总结如下:

    golang的第一性能杀手:GC

    个人认为GC扫描对象、及其GC引起的STW,是golang最大的性能杀手。本小节讨论优化golang GC的各种技巧。

    压舱物ballast

    下面一段神奇的代码,能够减少GC的频率,从而提升程序性能:

    func main(){
        ballast := make([]byte, 10*1024*1024*1024)
        runtime.KeepAlive(ballast)
        // do other things
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    其原理是扩大golang runtime的堆内存,使得实际分配的内存不容易超过堆内存的一定比例,进而减少GC的频率。GC的频率低了,STW的次数和时间也就更少,从而程序的性能也提升了。

    具体的细节请参考文章:

    堆外内存

    众所周知,golang中分配太多对象,会给GC造成很大压力,从而影响程序性能。
    那么,我在golang runtime的堆以外分配内存,就可以绕过GC了。
    可以通过mmap系统调用来使用堆外内存,具体请见:《Go Mmap 文件内存映射简明教程
    对于堆外内存的应用,在此推荐一个非常经典的golang组件:fastcache。具体请看这篇我对fastcache的分析文章:《介绍一个golang库:fastcache 》。

    也需要注意,这里有个坑:
    如果使用mmap去映射一个文件,则某个虚拟地址没有对应的物理地址时,操作系统会产生缺页终端,并转到内核态执行,把磁盘的内容load到page cache。如果此时磁盘IO高,可能会长时间的阻塞……进一步地,导致了golang调度器的阻塞。
    
    
    • 1
    • 2
    • 3

    对象复用

    对象太多会导致GC压力,但又不可能不分配对象。因此对象复用就是减少分配消耗和减少GC的释放消耗的好办法。

    下面分别通过不同的场景来讨论如何复用对象。

    海量微型对象的情况

    假设有很多几个字节或者几十个字节的,数以万计的对象。那么最好不要一个个的new出来,会有两个坏处:

    • 对象的管理会需要额外的内存,考虑内存对齐等因素又会造成额外的内存浪费。因此海量微型对象需要的总内存远远大于其自身真实使用的字节数;
    • GC的压力源于对象的个数,而不是总字节数。海量微型对象必然增大GC压力。

    海量微型对象的影响,请看我曾经遇到过的这个问题:《【笔记】对golang的大量小对象的管理真的是无语了……

    因此,海量微型对象的场景,这样解决:

    • 分配一大块数组,在数组中索引微型对象
    • 考虑fastcache这样的组件,通过堆外内存绕过GC

    当然,也有缺点:不好缩容。

    大量小型对象的情况

    对于大量的小型对象,sync.Pool是个好选择。

    推荐阅读这篇文章:《Go sync.Pool 保姆级教程

    sync.Pool不如上面的方法节省内存,但好处是可以缩容。

    数量可控的中型对象

    有的时候,我们可能需要一些定额数量的对象,并且对这些对象复用。

    这时可以使用channel来做内存池。需要时从channel取出,用完放回channel。

    slice的复用

    fasthttp, VictoriaMetrics等组件的作者 valyala可谓是把slice复用这个技巧玩上了天,具体可以看fasthttp主页上的Tricks with []byte buffers这部分介绍。

    概要的总结起来就是:[]byte这样的数组分配后,不要释放,然后下次使用前,用slice=slice[:0]来清空,继续使用其上次分配好的cap指向的空间。

    这篇中文的总结也非常不错:《fasthttp对性能的优化压榨

    valyala大神还写了个 bytebufferpool,对[]byte重用的场景进行了封装。

    避免容器空间动态增长

    对于slice和map而言,在预先可以预估其空间占用的情况下,通过指定大小来减少容器操作期间引起的空间动态增长。特别是map,不但要拷贝数据,还要做rehash操作。

    func xxx(){
      slice := make([]byte, 0, 1024)  // 有的时候,golangci-lint会提示未指定空间的情况
      m := make(map[int64]struct{}, 1000)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    大神技巧:用slice代替map

    此技巧源于valyala大神。

    假设有一个很小的map需要插入和查询,那么把所有key-value顺序追加到一个slice中,然后遍历查找——其性能损耗可能比分配map带来的GC消耗还要小。

    1. map变成slice,少了很多动态调整的空间
    2. 如果整个slice能够塞进CPU cache line,则其遍历可能比从内存load更加快速

    具体请见这篇:《

  • 相关阅读:
    openFeign引入失败
    php 提取word 的内容 必须是docx格式
    【阿克曼运动控制】
    MySQL日志管理、备份与恢复
    Leetcode之第299场周赛小记
    【牛客 - 剑指offer】JZ10 斐波那契数列(入门难度)三种方案 Java实现
    谈一谈在两个商业项目中使用MVI架构后的感悟
    【云原生】Kubernetes核心技术(中)
    ​python 的字符串转为字典​
    Unity shader time
  • 原文地址:https://blog.csdn.net/u013190417/article/details/127131034