web-dev-qa-db-ja.com

Golangスライスの追加とパフォーマンスの割り当て

スライス追加操作を高速化するには、十分な容量を割り当てる必要があります。スライスを追加するには2つの方法があります。コードは次のとおりです。

_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
    }
}
_

そして結果は:

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] = i_はa = append(a, i)より高速であり、理由を知りたいですか?

13
Bryce

a[i] = iは、iという値をa[i]に割り当てるだけです。これはnot追加であり、単純な assignment です。

今、追加:

a = append(a, i)

理論的には次のことが起こります:

  1. これは組み込みの append() 関数を呼び出します。そのためには、最初にaスライス(スライスヘッダー、バッキング配列はヘッダーの一部ではありません)をコピーする必要があり、値iを含む可変引数パラメーターの一時スライスを作成する必要があります。

  2. 次に、a = a[:len(a)+1]のように十分な容量がある場合はaをスライスし直す必要があります(この場合、append()内のaに新しいスライスを割り当てる必要があります)。
    aに「インプレース」で追加を行うのに十分な容量がない場合、新しい配列を割り当て、スライスのコンテンツをコピーしてから、割り当て/追加を実行する必要がありますが、ここではそうではありません。)

  3. 次に、ia[len(a)-1]に割り当てます。

  4. 次に、append()から新しいスライスを返し、この新しいスライスがローカル変数aに割り当てられます。

ここでは、単純な割り当てに比べて多くのことが起こります。これらの手順の多くが最適化またはインライン化されている場合でも、iをスライスの要素に割り当てるための最低限の追加として、スライスタイプのローカル変数a(スライスヘッダー)- ループの各サイクルで更新する必要があります

推奨読書: Goブログ:配列、スライス(および文字列):「追加」のメカニズム

12
icza

この質問が投稿されてから、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()
    }
}

結果:

➜  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
6
Vitaly Isaev