数组与切片:访问速度

问题描述 投票:0回答:3

这个问题是关于数组和切片的访问元素的速度,而不是关于将它们作为参数传递给函数的效率。

我希望在大多数情况下arraysslices更快,因为切片是描述数组的连续部分的数据结构,因此在访问切片的元素时可能会涉及额外的步骤(间接访问元素)其底层数组)。

所以我写了一个小测试来对一批简单的操作进行基准测试。有 4 个基准函数,前 2 个测试 global 切片和全局数组,另外 2 个测试 local 切片和局部数组:

var gs = make([]byte, 1000) // Global slice
var ga [1000]byte           // Global array

func BenchmarkSliceGlobal(b *testing.B) {
    for i := 0; i < b.N; i++ {
        for j, v := range gs {
            gs[j]++; gs[j] = gs[j] + v + 10; gs[j] += v
        }
    }
}

func BenchmarkArrayGlobal(b *testing.B) {
    for i := 0; i < b.N; i++ {
        for j, v := range ga {
            ga[j]++; ga[j] = ga[j] + v + 10; ga[j] += v
        }
    }
}

func BenchmarkSliceLocal(b *testing.B) {
    var s = make([]byte, 1000)
    for i := 0; i < b.N; i++ {
        for j, v := range s {
            s[j]++; s[j] = s[j] + v + 10; s[j] += v
        }
    }
}

func BenchmarkArrayLocal(b *testing.B) {
    var a [1000]byte
    for i := 0; i < b.N; i++ {
        for j, v := range a {
            a[j]++; a[j] = a[j] + v + 10; a[j] += v
        }
    }
}

我多次运行测试,这是典型的输出(

go test -bench .*
):

BenchmarkSliceGlobal      300000              4210 ns/op
BenchmarkArrayGlobal      300000              4123 ns/op
BenchmarkSliceLocal       500000              3090 ns/op
BenchmarkArrayLocal       500000              3768 ns/op

分析结果:

访问全局切片比访问全局数组稍慢,这正如我预期的那样:

4210
vs
4123
ns/op

但是访问本地切片比访问本地数组要快得多:

3090
vs
3768
ns/op

我的问题是:这是什么原因?

注释

我尝试改变以下事情,但没有改变结果:

  • 数组/切片的大小(尝试过 100、1000、10000)
  • 基准函数的顺序
  • 数组/切片的元素类型(尝试过
    byte
    int
arrays performance go benchmarking slice
3个回答
19
投票

比较 BenchmarkArrayLocal

BenchmarkSliceLocal
amd64 程序集
(太长,不适合这篇文章):

数组版本会多次从内存中加载

a
的地址,几乎在每次数组访问操作中:

LEAQ    "".a+1000(SP),BX

而切片版本在从内存加载一次后仅在寄存器上计算:

LEAQ    (DX)(SI*1),BX

这不是决定性的,但可能是原因。原因是这两种方法在其他方面实际上是相同的。另一个值得注意的细节是数组版本调用

runtime.duffcopy
,这是一个相当长的汇编例程,而切片版本则不然。


5
投票

Go 1.8 版本可以消除一些范围检查,因此差异变得更大。


BenchmarkSliceGlobal-4        500000          3220 ns/op
BenchmarkArrayGlobal-4       1000000          1287 ns/op
BenchmarkSliceLocal-4        1000000          1267 ns/op
BenchmarkArrayLocal-4        1000000          1301 ns/op

对于数组,我建议使用 2 的幂的大小并包含逻辑与运算。通过这种方式,您可以确定编译器消除了检查。因此

var ga [1024]byte
ga[j & 1023]


1
投票

在go1.18和M1上差别更大 我确信数组比切片更快,但现在我有证据证明情况并非总是如此

goos: darwin
goarch: arm64
BenchmarkSliceGlobal-8        926196          1257.0 ns/op         0 B/op          0 allocs/op
BenchmarkArrayGlobal-8       2110324           567.0 ns/op         0 B/op          0 allocs/op
BenchmarkSliceLocal-8        2275382           535.0 ns/op         0 B/op          0 allocs/op
BenchmarkArrayLocal-8        1802491           647.4 ns/op         0 B/op          0 allocs/op```
© www.soinside.com 2019 - 2024. All rights reserved.