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


编译参数

  • -X importpath.name=value 编译期设置变量的值
  • -s disable symbol table 禁用符号表
  • -w disable DWARF generation 禁用调试信息
    ——《golang编译参数ldflags》
理论上说 -s -w加上后,代码段的长度会减小,理论上会提高CPU代码cache的利用率 。(还未亲自测试过)
使用runtime中的非导出函数runtime中有的底层函数是汇编实现的,性能很高,但是不是export类型 。
这时候可以用链接声明来使用这些函数:
//go:noescape//go:linkname memmove runtime.memmove//goland:noinspection GoUnusedParameterfunc memmove(to unsafe.Pointer, from unsafe.Pointer, n uintptr)func memmove(to, from unsafe.Pointer, n uintptr)// 通过上面的声明后,就可以在代码中使用底层的memmove函数了 。这个函数相当于c中的memcpy()具体的细节请看这篇文章:《Go的2个黑魔法技巧》(腾讯 pedrogao)
函数内联golang的小函数默认就是内联的 。
可以通过函数前的注释 //go:noinline来取消内联,不过似乎没有理由这么做 。
关于函数内联的深层知识还是值得学习的,推荐这篇文章:《详解Go内联优化》
可以关注文章中的这个内联优化技巧:
可通过-gcflags="-l"选项全局禁用内联,与一个-l禁用内联相反,如果传递两个或两个以上的-l则会打开内联,并启用更激进的内联策略 。
泛型golang 1.18正式发布了泛型 。
泛型可以让之前基于反射的代码变得更加简单,很多type assert的代码可以去掉;基于interface的运行期动态分发,也可以转成编译期决定 。
由于对具体的类型产生了具体的代码,理论上指令cache命中会提高,分支预测失败会降低,
不过,对于有一定体量的golang团队而言,泛型的引入要考虑的问题比较多:如何避免滥用,如何找到与之匹配的基础库?
在整个团队的能力还没准备好迎接泛型以前,使用工具生产代码的产生式编程或许是更容易驾驭的方法 。
API使用反射编译期决定当然是好于运行期决定的 。
我的建议是:
  • 能不用就不用,可以用下面的方法代替:
    • 泛型
    • 代码生成(产生式编程)
  • 非得要用
    • 缓存反射的到的结果
有的场景下,标准库提供的API不够好 。下面列举一些自己认识的fast-xx组件 。fasttime组件,低精度的time.Now()源码请见:https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/lib/fasttime/fasttime.go
原理就是创建协程每秒一次获取 time.Now(),然后一秒以内取时间戳就只是访问全局变量 。
我测试过:性能比直接使用time.Now()快三倍左右 。
fastrand,绕开rand库的锁源码请见:https://github.com/valyala/fastrand
超长字符串输出的优化:quicktemplate假设一次要输出几兆字节的JSON字符串,如何优化性能?
VictoriaMetrics中的vm-select就遇到了这个问题,当一个大查询需要返回很多的metrics数据的时候,其输出的json的体积非常可观 。
如果把数据先放到一个大数组,再使用json.Marsharl,则一方面要频繁申请释放内存,另一方面会带来内存使用量的剧烈抖动 。vm-select的解决方式是使用quicktemplate库——把json看成是字符串流的输出 。
具体代码请看:https://github.com/valyala/quicktemplate

经验总结扩展阅读