【译文】Go语言性能从 1.0 版到 1.22 版

两年前,我比较了我的 GoAWK 解释器在 Go 1.2 到 1.18 所有版本上的两个不同基准测试。

在本文中,我重新运行了这些基准测试,并添加了缺失的 Go 版本(1.0 和 1.1)以及新版本(1.19 至 1.22)。我还加入了在 Go 1.20 中加入的配置文件引导优化(PGO)的结果。我将引用我原来文章中的相当一部分内容,这样您就不必重新阅读旧文章来理解设置了。

用 Go 编写的程序在很多方面都变得更快:Go 团队和外部贡献者改进了编译器,优化了运行时、垃圾回收器和标准库。在此,我们比较了 GoAWK 在使用 Go 1.0 至 1.22(本文撰写时的最新版本)各发布版本编译时的性能。

我在两个 AWK 程序上运行 GoAWK 进行了测试,这两个程序代表了使用 AWK 所能做的不同极端事情:I/O 与字符串处理,以及数字计算。

首先是 countwords,它是一个字符串处理任务,用于计算输入中单词的频率,并打印出单词及其计数。这是 AWK 脚本的典型任务。输入是《King James Bible》的 10 倍串联版本(我曾用它进行过性能比较)。代码如下

{
    for (i=1; i<=NF; i++)
        counts[tolower($i)]++
}

END {
    for (k in counts)
        print k, counts[k]
}

第二个程序是 sumloop,这是一个紧凑的循环,它将循环计数器加到一个变量上多次。这并不是 AWK 的典型用法,但却是 GoAWK 字节码解释器循环的良好测试:

BEGIN {
    for (i=0; i<10000000; i++)
        sum += i+i+i+i+i
}

我不得不对 GoAWK 的代码稍作调整,以使其能在较旧的 Go 版本上编译。特别是 Go 1.0,因为它没有 bufio.Scanner,而 GoAWK 大量使用了它。我在 1.0 版中使用了 Go 1.1 版的 bufio.Scanner 实现。

图表中的计时数字是在我的 x86-64 Linux 笔记本电脑上以秒为单位计算的时间(三次运行中最好的一次)。蓝线为 countwords,红线为 sumloop(顺便说一下,我上次把结果标错了)。请注意,这次的 Y 轴是对数轴,以便更清楚地看到最近版本中更细微的改进。

图表中还包括每个golang 版本的 GoAWK 二进制大小–即浅灰色线。

我再次使用 Python 脚本来运行它们并测量时序。下面是图表(如果您喜欢,也可以用表格):

最大的改进来自 1.3、1.5、1.7 和 1.12 版本。在此之后,速度将逐步提升,所有低垂的果实早已被摘取。

这次在go 1.2 中,数词的速度出现了奇怪的提升:从 1.1 版的 7.5 秒提升到 1.2 版的 25.5 秒(!),然后又下降到 1.3 版的 2.8 秒。这几乎可以肯定是堆栈 “热分割 “问题造成的,由于 Go 团队改变了 “goroutine 堆栈的实现,从旧的’分段’模型改为连续模型”,堆栈 “热分割 “问题在 1.3 中得到了解决

我通过剖析找出了 1.2 版异常的原因,并注意到运行时堆栈操作占了运行时间的很大比例。下面是 pprof 输出的前几行:

$ go tool pprof --text ./goawk_1.2 go12.prof 
Total: 1830 samples
     332  18.1%  18.1%      332  18.1% runtime.newstack
     296  16.2%  34.3%      296  16.2% runtime.memclr
     281  15.4%  49.7%      281  15.4% runtime.oldstack
     222  12.1%  61.8%      619  33.8% github.com/benhoyt/goawk/interp.(*interp).execute
      91   5.0%  66.8%       91   5.0% runtime.lessstack
      75   4.1%  70.9%      133   7.3% github.com/benhoyt/goawk/interp.(*interp).callBuiltin
      57   3.1%  74.0%       57   3.1% runtime.stackfree
      53   2.9%  76.9%       81   4.4% strings.FieldsFunc
      ...

在使用 Go 1.22 的情况下,PGO 只提高了几个百分点的性能,对 countwords 而言约为 2%,对 sumloop 而言约为 7%。我用 PGO 编译已发布的 GoAWK 二进制文件。

多年来,二进制文件的大小一直保持相当稳定,除了在 1.2 中出现了较大的增长。即使启用了 PGO,二进制文件也只增大了 5%,所以我认为这通常是值得的。

总的来说,countwords 现在的速度是 Go 1.0 的 8 倍,sumloop 是它的 24 倍。感谢 Go 团队多年来的辛勤工作!

本文文字及图片出自 Go performance from version 1.0 to 1.22

余下全文(1/3)
分享这篇文章:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注