Golang切片附加vs分配性能

Bry*_*yce 11 performance go slice

为了使切片附加操作更快,我们需要分配足够的容量.有两种方法可以追加切片,这是代码:

func BenchmarkSliceAppend(b *testing.B) {
    a := make([]int, 0, b.N)
    for i := 0; i < b.N; i++ {
        a = append(a, i)
    }
}

func BenchmarkSliceSet(b *testing.B) {
    a := make([]int, b.N)
    for i := 0; i < b.N; i++ {
        a[i] = i
    }
}
Run Code Online (Sandbox Code Playgroud)

结果是:

BenchmarkSliceAppend-4 200000000 7.87 ns/op 8 B/op 0 allocs/op

BenchmarkSliceSet-4 300000000 5.76 ns/op 8 B/op

a[i] = ia = append(a, i)我快,我想知道为什么?

icz*_*cza 12

a[i] = i只需将值赋值ia[i].这不是追加,只是一个简单的任务.

现在附加:

a = append(a, i)
Run Code Online (Sandbox Code Playgroud)

理论上会发生以下情况:

  1. 这会调用内置append()函数.为此,它首先必须复制a切片(切片标头,支持数组不是标头的一部分),并且必须为包含该值的可变参数创建临时切片i.

  2. 然后,a如果它有足够的容量(在你的情况下),它必须重新组合a = a[:len(a)+1]- 这涉及将新切片分配到a内部append().
    (如果a没有足够的容量来执行"就地"附加,则必须分配一个新数组,复制切片的内容,然后执行assign/append - 但这不是这种情况.)

  3. 然后分配ia[len(a)-1].

  4. 然后从中返回新切片append(),并将此新切片分配给局部变量a.

与简单的任务相比,这里发生了很多事情.即使这些步骤中的许多步骤被优化和/或内联,作为分配i给切片的元素的最小添加,也必须在循环的每个循环中更新切片类型的局部变量a(其是切片报头).

推荐阅读:Go Blog:数组,切片(和字符串):'追加'的机制

  • @mh-cbon 这实际上取决于性能的关键程度。`append()` 可能会更清晰、更具可读性,这也很重要。如果每一纳秒都很重要,那么也许吧。如果选择了可读性较差的版本,则应进行测量并正确记录。 (2认同)

Vit*_*aev 6

自从发布此问题以来,似乎已经引入了对Go编译器或运行时的一些改进,因此,现在(Go 1.10.1append与按索引直接分配之间没有显着差异。

另外,由于OOM恐慌,我不得不稍微更改您的基准。

package main

import "testing"

var result []int

const size = 32

const iterations = 100 * 1000 * 1000

func doAssign() {
    data := make([]int, size)
    for i := 0; i < size; i++ {
        data[i] = i
    }
    result = data
}

func doAppend() {
    data := make([]int, 0, size)
    for i := 0; i < size; i++ {
        data = append(data, i)
    }
    result = data
}

func BenchmarkAssign(b *testing.B) {
    b.N = iterations
    for i := 0; i < b.N; i++ {
        doAssign()
    }
}

func BenchmarkAppend(b *testing.B) {
    b.N = iterations
    for i := 0; i < b.N; i++ {
        doAppend()
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

?  bench_slice_assign go test -bench=Bench .
goos: linux
goarch: amd64
BenchmarkAssign-4       100000000           80.9 ns/op
BenchmarkAppend-4       100000000           81.9 ns/op
PASS
ok      _/home/isaev/troubles/bench_slice_assign    16.288s
Run Code Online (Sandbox Code Playgroud)

  • 这是个好消息,但请记住,如果“size”更大且未指定初始阵列容量,则情况大不相同。 (2认同)
  • 调整数组大小是一项成本高昂的操作。如果您不知道数组的初始大小,这些重新分配将使整个操作花费更长的时间。 (2认同)
  • 这个基准的衡量标准值得怀疑。我认为它衡量的是分配成本,而不是两种不同模式的速度比较。 (2认同)
  • 无法在 go1.16.4 上以 100K“大小”重现结果。 (2认同)