以下の内容はhttps://devlights.hatenablog.com/entry/2025/10/07/073000より取得しました。


Goメモ-624 (スライスのコピー方法あれこれ)(どれが一番速いのか?)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

スライスをコピーする方法っていくつかあります。最近は、slicesパッケージにClone関数が追加されたので、今後はそっち使っていくべきなのでしょう。

んで、ちょっと気になったので、どのやり方が一番速いのか自分の環境でベンチマークしてみました。

サンプル

slice_clone_test.go

package main

import (
    "slices"
    "testing"
)

const (
    NumItems = 5 * 1024 * 1024
)

func voidfn(_ []int) {}

// 方法1: 事前に容量を確保してからappendする方法
func BenchmarkPreAllocatedAppend(b *testing.B) {
    var (
        items = make([]int, 0, NumItems)
    )
    for i := range NumItems {
        items = append(items, i)
    }

    for b.Loop() {
        var (
            dst = make([]int, 0, NumItems)
        )
        dst = append(dst, items...)
        voidfn(dst)
    }
}

// 方法2: 空スライスに直接appendする方法
func BenchmarkEmptySliceAppend(b *testing.B) {
    var (
        items = make([]int, 0, NumItems)
    )
    for i := range NumItems {
        items = append(items, i)
    }

    for b.Loop() {
        // 空スライスに直接append
        dst := append([]int{}, items...)
        voidfn(dst)
    }
}

// 方法3: 容量をリセットしてからappendする方法
func BenchmarkCapacityResetAppend(b *testing.B) {
    var (
        items = make([]int, 0, NumItems)
    )
    for i := range NumItems {
        items = append(items, i)
    }

    for b.Loop() {
        var (
            dst = make([]int, 0, NumItems)
        )
        // スライスの長さと容量を0にリセットしてからappend
        dst = append(dst[:0:0], items...)
        voidfn(dst)
    }
}

// 方法4: 従来のcopy()を使った方法
func BenchmarkBuiltinCopy(b *testing.B) {
    var (
        items = make([]int, 0, NumItems)
    )
    for i := range NumItems {
        items = append(items, i)
    }

    for b.Loop() {
        dst := make([]int, len(items))
        copy(dst, items)
        voidfn(dst)
    }
}

// 方法5: slices.Clone()を使った方法
func BenchmarkSlicesClone(b *testing.B) {
    var (
        items = make([]int, 0, NumItems)
    )
    for i := range NumItems {
        items = append(items, i)
    }

    for b.Loop() {
        dst := slices.Clone(items)
        voidfn(dst)
    }
}

Taskfile.yml

# https://taskfile.dev

version: '3'

tasks:
  default:
    cmds:
      - go test -bench . -benchmem -count 3 | tee bench.txt
      - defer: rm -f bench.txt
      - benchstat bench.txt

実行

$ task
task: [default] go test -bench . -benchmem -count 3 | tee bench.txt
goos: linux
goarch: amd64
pkg: github.com/devlights/try-golang/examples/benchmarks/03.slice_clone
cpu: Intel(R) Core(TM) Ultra 5 125H
BenchmarkPreAllocatedAppend-18               134           8116946 ns/op        41943090 B/op          1 allocs/op
BenchmarkPreAllocatedAppend-18               150           7897353 ns/op        41943049 B/op          1 allocs/op
BenchmarkPreAllocatedAppend-18               153           7697424 ns/op        41943048 B/op          1 allocs/op
BenchmarkEmptySliceAppend-18                 234           5234496 ns/op        41943072 B/op          1 allocs/op
BenchmarkEmptySliceAppend-18                 246           4907417 ns/op        41943081 B/op          1 allocs/op
BenchmarkEmptySliceAppend-18                 174           6013573 ns/op        41943054 B/op          1 allocs/op
BenchmarkCapacityResetAppend-18              153           7743873 ns/op        83886086 B/op          2 allocs/op
BenchmarkCapacityResetAppend-18              152           7804002 ns/op        83886092 B/op          2 allocs/op
BenchmarkCapacityResetAppend-18              150           7891077 ns/op        83886088 B/op          2 allocs/op
BenchmarkBuiltinCopy-18                      151           7796522 ns/op        41943046 B/op          1 allocs/op
BenchmarkBuiltinCopy-18                      152           7813188 ns/op        41943050 B/op          1 allocs/op
BenchmarkBuiltinCopy-18                      158           7525531 ns/op        41943050 B/op          1 allocs/op
BenchmarkSlicesClone-18                      255           5556701 ns/op        41943047 B/op          1 allocs/op
BenchmarkSlicesClone-18                      187           6222304 ns/op        41943047 B/op          1 allocs/op
BenchmarkSlicesClone-18                      204           5850473 ns/op        41943044 B/op          1 allocs/op
PASS
ok      github.com/devlights/try-golang/examples/benchmarks/03.slice_clone      17.955s
task: [default] benchstat bench.txt
goos: linux
goarch: amd64
pkg: github.com/devlights/try-golang/examples/benchmarks/03.slice_clone
cpu: Intel(R) Core(TM) Ultra 5 125H
                       │  bench.txt   │
                       │    sec/op    │
PreAllocatedAppend-18    7.897m ± ∞ ¹
EmptySliceAppend-18      5.234m ± ∞ ¹
CapacityResetAppend-18   7.804m ± ∞ ¹
BuiltinCopy-18           7.797m ± ∞ ¹
SlicesClone-18           5.850m ± ∞ ¹
geomean                  6.816m
¹ need >= 6 samples for confidence interval at level 0.95

                       │   bench.txt   │
                       │     B/op      │
PreAllocatedAppend-18    40.00Mi ± ∞ ¹
EmptySliceAppend-18      40.00Mi ± ∞ ¹
CapacityResetAppend-18   80.00Mi ± ∞ ¹
BuiltinCopy-18           40.00Mi ± ∞ ¹
SlicesClone-18           40.00Mi ± ∞ ¹
geomean                  45.95Mi
¹ need >= 6 samples for confidence interval at level 0.95

                       │  bench.txt  │
                       │  allocs/op  │
PreAllocatedAppend-18    1.000 ± ∞ ¹
EmptySliceAppend-18      1.000 ± ∞ ¹
CapacityResetAppend-18   2.000 ± ∞ ¹
BuiltinCopy-18           1.000 ± ∞ ¹
SlicesClone-18           1.000 ± ∞ ¹
geomean                  1.149
¹ need >= 6 samples for confidence interval at level 0.95
task: [default] rm -f bench.txt

結果として、何度試しても空スライスにAppendする方法が最も速かったです。

つまり、

   dst := append([]int{}, items...)

が私の環境では最も速い。

実際に、slices.Clone()は、内部で上記を行っています。

   // Avoid s[:0:0] as it leads to unwanted liveness when cloning a
    // zero-length slice of a large array; see https://go.dev/issue/68488.
    return append(S{}, s...)

なので、今後は slices.Clone を使うべきですね。

参考情報

個人的Goのおすすめ書籍

個人的に読んでとても勉強になった書籍さんたちです。


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。




以上の内容はhttps://devlights.hatenablog.com/entry/2025/10/07/073000より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14