go cmd/compile:合并连续的print/println调用

zlhcx6iw  于 4个月前  发布在  Go
关注(0)|答案(7)|浏览(39)

CLs 55095–55098更改了编译器(和运行时)实现内置函数print和println的方式,以减少它们生成的代码量。(目标是既减少运行时的二进制占用空间,又增加重要运行时例程中的指令密度。)
这个问题进一步明确了一个明显的优化。考虑以下代码:

println("a")
println("b")

在CL 55098之后,这段代码将被编译成:printlock、print "a
"、printunlock、printlock、print "b
"、printunlock。但它也可以被编译成:printlock、print "a
b
"、printunlock。简而言之,将连续调用print/println合并为一个受printlock保护的调用序列,尽可能地组合字符串常量,并注意正确处理println引入的空格和换行符。
优先级较低,但可能对对编译器中层感兴趣的人来说是一个有趣的学习练习。这并不是非常直接,因为walkprint一次查看一个节点,但它也不应该太困难。

cbjzeqam

cbjzeqam1#

尽管在评估第二个println的参数时需要特别小心,否则可能会导致恐慌。也许这并不是一个好的开始问题。

sc4hvdpw

sc4hvdpw2#

我来这里是为了说,“不要这样做!”因为这是关于参数评估的争论。
我使用多个println语句来诊断某些类型的故障。将它们合并成一个print语句将完全消除这种可能性。
无论如何,我们不应该关心println是否特别高效。你不应该在生产代码中使用它。

rpppsulh

rpppsulh3#

我来这里是为了说,"不要这样做!"因为这是关于函数调用的。
当然;我们只能在用户代码无法检测到的情况下这样做。(这在SSA形式中会更容易,但仍然可能,使用包gc的安全表达式。)
无论如何,我们不应该关心println是否特别高效。你不应该在生产代码中使用它。
我在这里的目标是缩小使用println的运行时例程,以获得更小的二进制文件和更好的指令密度。如果它使调用站点变小或使用更少的堆栈,我会很高兴地让println变慢。

apeeds0o

apeeds0o4#

这是个次要的问题,但如果在函数末尾发出包含print的块,作为不太可能的块?这将增加从未调用print的很可能路径的密度。

pkwftd7m

pkwftd7m5#

这并不完全消除了进行这种调试的能力,尽管它确实使它变得更加令人讨厌:

//go:noinline
dbgPrintln(s string) {
   println(s)
}

但是我们从这样做中获得了多少好处?

snz8szmq

snz8szmq6#

clang和GCC都会对C++程序进行这种优化,这是有价值的。例如:

printf("a");
  printf("b");
  printf("c\n");

被折叠成一个单独的'puts("abc")'。
Q: 为什么只对println进行这种优化?为什么不将相同的合并应用到fmt.Printf调用上?

eni9jsuy

eni9jsuy7#

关于在函数末尾包含print的发射块,作为不太可能的块,这会增加从未调用print的很可能路径的密度。
好主意。
如果做得稳妥(这是我的意图),这并不会完全消除这种调试的能力。它只是限制了优化的范围。
但是我们从这样做中能获得多少好处呢?
我的简单优化将hello world减少了0.5%。考虑到所有这些都是来自运行时,似乎优先级较低,但仍然值得(正如我最初所说)。
问:为什么只对println进行这样的优化?为什么不将相同的合并应用于fmt.Printf调用?
几个原因。fmt对于编译器来说有点高级。fmt的打印方法返回值。一般来说,fmt的打印方法会确保只进行一次底层写入调用。

相关问题