为什么追加预分配切片比索引更快?

时间:2017-11-05 17:25:19

标签: go append

我写了以下Go基准测试(Go1.9.2,Linux amd64)来测试索引与追加:

func BenchmarkIndex(b *testing.B) {
    for i := 0; i < b.N; i++ {
        existing := make([]int64, 1000, 1000)
        init := make([]int64, 1000) // len 1000, cap 1000
        for index, element := range existing {
            init[index] = element
        }
    }
}

func BenchmarkAppend(b *testing.B) {
    for i := 0; i < b.N; i++ {
        existing := make([]int64, 1000, 1000)
        init := make([]int64, 0, 1000) // len 0, capacity 1000
        for _, element := range existing {
            init = append(init, element)
        }
    }
}

得到以下结果:

goos: linux
goarch: amd64
BenchmarkIndex-4         1000000          2183 ns/op
BenchmarkAppend-4        1000000          1933 ns/op
PASS

我对于为什么append似乎比一般情况下表现更好而不是索引切片有点困惑。省略范围内的index是否与此有关?如果不是,那么在引擎盖下做什么是为了使它更快,而不仅仅是索引?

供参考,here is append's source。谢谢!

1 个答案:

答案 0 :(得分:8)

在计算机上:

$ go test same_test.go -run=! -bench=. -benchmem -count=3
goos: linux
goarch: amd64
BenchmarkIndex-4     1000000    1290 ns/op    0 B/op    0 allocs/op
BenchmarkIndex-4     1000000    1290 ns/op    0 B/op    0 allocs/op
BenchmarkIndex-4     1000000    1288 ns/op    0 B/op    0 allocs/op
BenchmarkAppend-4    1000000    1299 ns/op    0 B/op    0 allocs/op
BenchmarkAppend-4    1000000    1302 ns/op    0 B/op    0 allocs/op
BenchmarkAppend-4    1000000    1301 ns/op    0 B/op    0 allocs/op
PASS
ok      command-line-arguments  7.868s
$ go version
go version devel +38c725b148 Sun Nov 5 17:30:11 2017 +0000 linux/amd64
$ 

BenchmarkAppendBenchmarkIndex之间的差异是:

((1299+1302+1301) - (1290+1290+1288)) * 100 / (1290+1290+1288) =  +0.879%

他们是一样的。

在计算机二上:

$ go test same_test.go -run=! -bench=. -benchmem -count=3
goos: linux
goarch: amd64
BenchmarkIndex-8     2000000    706 ns/op    0 B/op    0 allocs/op
BenchmarkIndex-8     2000000    713 ns/op    0 B/op    0 allocs/op
BenchmarkIndex-8     2000000    710 ns/op    0 B/op    0 allocs/op
BenchmarkAppend-8    2000000    711 ns/op    0 B/op    0 allocs/op
BenchmarkAppend-8    2000000    718 ns/op    0 B/op    0 allocs/op
BenchmarkAppend-8    2000000    718 ns/op    0 B/op    0 allocs/op
PASS
ok      command-line-arguments  12.945s
$ go version
go version devel +38c725b148 Sun Nov 5 17:30:11 2017 +0000 linux/amd64
$ 

BenchmarkAppendBenchmarkIndex之间的差异是:

((711+718+718) - (706+713+710)) * 100 / (706+713+710) =  +0.845%

他们是一样的。

让我们一次测量一件事,索引与附加在预分配切片上,这可以避免内存管理等混淆问题。

package main

import "testing"

func BenchmarkIndexing(b *testing.B) {
    existing := make([]int64, 1000, 1000)
    init := make([]int64, 1000) // len 1000, cap 1000
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        init = init[:cap(init)]
        for index, element := range existing {
            init[index] = element
        }
    }
}

func BenchmarkAppending(b *testing.B) {
    existing := make([]int64, 1000, 1000)
    init := make([]int64, 0, 1000) // len 0, capacity 1000
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        init = init[:0]
        for _, element := range existing {
            init = append(init, element)
        }
    }
}

输出:

$ go test same_test.go -run=! -bench=ing -benchmem -count=3
goos: linux
goarch: amd64
BenchmarkIndexing-4     2000000    736 ns/op    0 B/op    0 allocs/op
BenchmarkIndexing-4     2000000    735 ns/op    0 B/op    0 allocs/op
BenchmarkIndexing-4     2000000    735 ns/op    0 B/op    0 allocs/op
BenchmarkAppending-4    2000000    733 ns/op    0 B/op    0 allocs/op
BenchmarkAppending-4    2000000    733 ns/op    0 B/op    0 allocs/op
BenchmarkAppending-4    2000000    733 ns/op    0 B/op    0 allocs/op
PASS
ok      command-line-arguments  13.303s
$ go version
go version devel +38c725b148 Sun Nov 5 17:30:11 2017 +0000 linux/amd64
$

BenchmarkAppendBenchmarkIndex之间的差异是:

((733+733+733) - (736+735+735)) * 100 / (736+735+735) =  −0.317%

他们是一样的。