【问题标题】:Performance influence of the condition expressions in "for" statement"for" 语句中条件表达式的性能影响
【发布时间】:2021-10-23 05:14:02
【问题描述】:

以下两个示例之间是否存在性能差异:

1.

var slice []int{ ... huge list of items... }

for i:=0; i<len(slice); i++ { .... do something ....}

2.

var slice []int{ ... huge list of items... }
sliceLen := len(slice)

for i:=0; i<sliceLen; i++ { .... do something ....}

for”语句中的条件表达式是在每次迭代时评估还是只评估一次?

【问题讨论】:

  • 测量。这是告诉的唯一方法。
  • 这两个sn-ps可能编译成相同的机器码。
  • @Volker 我怀疑测试能否提供可靠的答案。我在下面写下了我的思考过程,作为对 Meny 回答的评论。
  • @CeriseLimón,是的,我怀疑这一点,但想听听比我更有知识的人的意见。老实说,我对实现背后的设计选择很好奇。

标签: performance for-loop go


【解决方案1】:

TLDR:几乎没有区别

如何测试您的问题的好方法是标准testing 库提供的benchmarking

创建测试文件 例如:forcycle_test.go

package perftest

import (
    "testing"
)

func BenchmarkLenInside(b *testing.B) {
    testData := make([]int, 1000000)

    for i := 0; i < b.N; i++ {
        // Benchmarked code start
        for j := 0; j < len(testData); j++ {
            doSth(testData[j])
        }
        // end
    }
}

func BenchmarkLenOutside(b *testing.B) {
    testData := make([]int, 1000000)

    for i := 0; i < b.N; i++ {
        // Benchmarked code start
        sliceLen := len(testData)
        for j := 0; j < sliceLen; j++ {
            doSth(testData[j])
        }
        // end
    }
}

func doSth(n int) {
    _ = n + n
}

运行基准

go test -bench .

示例输出

goos: linux
goarch: amd64
pkg: forperf
BenchmarkLenInside-6                4543            259117 ns/op
BenchmarkLenOutside-6               4620            258069 ns/op
PASS
ok      forperf 3.811s

基准函数必须运行目标代码 b.N 次。在基准执行期间,会调整 b.N,直到基准函数持续足够长的时间以可靠地计时。

当您将len 函数作为 for 循环的一部分编写时,您可以在此基准测试运行中看到,它只是比版本外循环稍慢。

Type b.N before timed Avg time per b.N iteration
Len in 4543 259117 ns
Len out 4620 258069 ns

请注意,在 len inAlen outB 时,多次运行会给您不同的结果

A &lt; BA ≈ B 甚至 A &gt; B 都是可能的。

【讨论】:

  • 我怀疑他们应该有类似的表现,但没有可靠的来源来确认。我有点怀疑测试能否为我的问题提供可靠的答案。因为查看数组/切片的长度是常数时间,所以在测试性能中可能无法观察到。我隐约记得,其他一些语言在第一次迭代时会查看数组的长度并将其保存在内存中。我不记得是哪个。我想,找出答案的唯一方法是等待一个精通源代码并能教我们内部如何工作的人。
  • 我喜欢你的解释,虽然有所有的提示和技巧。
【解决方案2】:

“for”语句中的条件表达式是在每次迭代时计算还是只计算一次?

specification says 在每次迭代之前评估条件。

因为i 的值在每次迭代中都会发生变化,因此在每次迭代之前评估条件i&lt;len(slice)i&lt;sliceLen 是有意义的。

只要生成的程序像每次都对表达式求值一样执行,编译器就可以将条件表达式求值的部分提升出循环。例如,编译器可以在循环之前将len(slice)sliceLen 加载到一个寄存器中,并在循环中使用该寄存器。

以下两个示例之间是否存在性能差异

两个代码 sn-ps 将变量 i 与从变量中读取的值进行比较。在第一个 sn-p 中,该值是从切片头长度字段中读取的。如果您不熟悉切片的实现方式,请参阅Slices: usage and internals

如果不相同,性能应该相似。

【讨论】:

  • 感谢您的清晰解释和进一步阅读的链接。通过条件表达式,我真正的意思是“len(slice)”部分,但我猜措辞不佳。所以我想知道的是 len(slice) 部分是否会比 sliceLen 部分造成更多的惩罚。在这种特殊情况下,len(slice) 是常数时间,但它可能更复杂。如果是这种情况,for-loop 是否会在每次迭代中一次又一次地计算第二部分。请注意,我不是要与 i 进行比较。我问的是比较的第二部分。
  • 不仅表达式len(slice) 是常数时间,表达式的计算结果也是存储在变量slice 中的值。这不是更复杂的事情,也永远不会。
  • @Kolom 这两个程序在我的台式计算机上编译成相同的代码。变量sliceLen 在程序外进行了优化。
  • 谢谢,很高兴知道。我想“不要过早优化代码”的原则再次成立。
猜你喜欢
  • 2015-04-23
  • 1970-01-01
  • 2012-10-05
  • 2012-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多