【问题标题】:for loop speed comparisonfor 循环速度比较
【发布时间】:2019-05-21 05:07:49
【问题描述】:

我想知道 Go 中的 len 运算符有多快,我编写了一个简单的基准测试。我的期望是通过避免在每次循环迭代期间调用len,代码会运行得更快,但实际上恰恰相反。

这是基准:

func sumArrayNumber(input []int) int {
    var res int
    for i, length := 0, len(input); i < length; i += 1 {
        res += input[i]
    }
    return res
}

func sumArrayNumber2(input []int) int {
    var res int
    for i := 0; i < len(input); i += 1 {
        res += input[i]
    }
    return res
}

var result int
var input = []int{3, 6, 22, 68, 11, -7, 22, 5, 0, 0, 1}

func BenchmarkSumArrayNumber(b *testing.B) {
    var r int
    for n := 0; n < b.N; n++ {
        r = sumArrayNumber(input)
    }
    result = r
}

func BenchmarkSumArrayNumber2(b *testing.B) {
    var r int
    for n := 0; n < b.N; n++ {
        r = sumArrayNumber2(input)
    }
    result = r
}

结果如下:

goos: windows
goarch: amd64
BenchmarkSumArrayNumber-8       300000000                4.75 ns/op
BenchmarkSumArrayNumber2-8      300000000                4.67 ns/op
PASS
ok      command-line-arguments  4.000s

我通过以下操作确认了抵抗是一致的:

  • 将输入数组大小加倍大约会使每个操作的执行时间加倍。速度差异随输入数组的长度而变化。
  • 交换测试订单不会影响结果。

为什么每次循环迭代时检查 len() 的代码更快?

【问题讨论】:

  • 这是无法回答的:编译器没有义务不重写您的代码,并且可能会或可能不会将两个版本优化为相同的代码,甚至将您认为较慢的版本优化为较快的版本。微基准测试非常难做,甚至更难分析(因此大多是浪费时间)。
  • 对 len 的调用编译为切片头中的字段访问。
  • 编译器可能会检测到input 的值没有改变,因此len(input) 也没有改变,并且可能只评估一次,根本没有性能损失,就像你经历过。

标签: performance go performance-testing


【解决方案1】:

有人可能会争辩说,0.08ns 的差异在统计上并不相关,不能说一个 for 循环比另一个快。您可能需要多次运行相同的测试(至少超过 20 次),此时您应该能够得出平均值和标准偏差。

此外,有许多因素可以加快len() 运算符的速度。像 CPU 缓存和编译器优化。我认为您的具体示例中最相关的因素是切片和数组的len() 运算符仅读取slice 数据结构中的len 字段。因此,它是 O(1)。

【讨论】:

  • 我确实运行了多次,可能不清楚但速度差异与数组的长度有关
  • 这很清楚。但是性能评估是关于统计的,需要足够的样本才能正常工作。单次运行基准测试,即使数组大小增加,也无法判断哪个 for 循环是最快的,尤其是当差异仅为 0.08ns 时。
  • 数组没有像切片这样的标题。 len() 用于数组是编译时评估,因为长度是数组类型的一部分。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-25
相关资源
最近更新 更多