大家好,我是炸鱼。在Go1.17发布后,我们惊喜地发现Go语言再次得到了优化。编译器改进后,性能提升了5%左右,没有破坏性的变化,保证了向前兼容。他做了什么?好像没看到有人提过。为此,今天剑鱼就带大家解读两个新提案:《Proposal: Register-based Go calling convention[1]》《Proposal: Create an undefined internal calling convention[2]》本文将根据提案进行解读和拆解。毕竟,分享新知识必须从官方信息作为事实依据。背景在以前的Go版本中,Go的调用约定很简单并且几乎是跨平台通用的。原因是选择了基于Plan9ABI的栈调用约定,即在栈上传递函数的参数和返回值。这里提到了Plan9和ABI,这是两个关键概念:Plan9:Go语言使用的汇编程序,RobPike是贝尔实验室的猛男。ABI:ApplicationBinaryInterface(应用程序二进制接口),ABI包含了应用程序在操作系统下运行时必须遵守的编程约定(例如:二进制接口)。该方案的优缺点如下:优点:实施简单,降低实施成本。缺点:在性能方面付出了很多代价。按照我的理解,在Go语言的初级阶段,简单的先实现,再运行。也有道理,性能不是TOP1的要求。Go1.17优化了调用约定。在新版本的优化中,提到了调用约定(callingconvention)的概念,它指的是调用者和被调用者之间对函数调用的共识约定。这些共识包括:函数参数、返回值、参数传递顺序、传递方式等。只有双方都必须遵守这个约定,程序的功能才能正常运行。如果不遵循,该功能将不起作用。什么是优化?从Go1.17开始,正式开始基于Go内部ABI规范(在Go函数之间使用),由原来基于栈的函数参数和结果传递方式变为基于寄存器的函数参数和结果传递方式。在性能方面,现在直接存储和计算都在寄存器上进行。与之前基于栈的存储和重新计算相比,这种模式势必有更好的性能。本次修改涉及项较多,优化持续进行。原本预计在Go1.16实现,后来推迟到Go1.17。目前已实现对amd64和arm64架构的支持。Go1.18还有很??多支持会继续完成,具体进度可以查看issues#40724[3]。Go1.17发行说明[4]中如何指定性能,以一组具有代表性的Go包和程序为基准。官方数据显示,围棋程序的运行性能提升了5%左右。Go编译的二进制文件大小减少了大约2%。民间数据方面,是在推特上看到@Achille[5]表示从Go1.15.7升级到Go1.17后展示的。Go1.17在大规模数据处理系统上的升级产生了惊人的效果,来看看他的真实数据吧。CPU、Malloc调用时间减少了大约15%:图来自@Achille图来自@AchilleRSS大小更接近于堆的大小:图来自@Achille内存从原来的1.6GB下降到1GB。结合官方和私人数据,优化效果清晰有效。有兴趣的朋友也可以自己测试一下。不过需要注意的是@Achille的数据包含了Go1.16和Go1.17的优化,所以不能直接比较,但可以作为参考。综上所述,在Go1.17新版??本中,我们只需要简单的升级Go版本,就可以获得一定的性能优化,非常不错。然而,随着这一变化,Go的编译又发生了变化。恐怕市面上很多文章或书籍的某些内容又要失效了。从以前的基于栈的函数参数和结果传递方式,到Go1.17~Go1.18基于寄存器的函数参数和结果传递方式,Go语言一步步变好!你怎么认为?参考[1]提案:基于寄存器的Go调用约定:https://go.googlesource.com/proposal/+/master/design/40724-register-calling.md[2]提案:创建一个未定义的内部调用约定:https://go.googlesource.com/proposal/+/master/design/27539-internal-abi.md[3]问题#40724:https://github.com/golang/go/issues/40724[4]Go1.17发行说明:https://golang.org/doc/go1.17[5]推特:https://twitter.com/Achille/status/1431014148800802819
