1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Golangのスライスのパフォーマンス比較(事前割当 vs append)

Posted at

こんにちは。アドベントカレンダー2日目ということで、本日は前から気になっていたGolangのスライスのパフォーマンスについて執筆します。

本内容では、Golangのスライスのベンチマークを比較する方法、その結果を共有します。
また、Golangのベンチマークの実行方法についても備忘録としてまとめます。

そもそもベンチマークとは?

恥ずかしながら、ベンチマークという単語すら聞いたことがありませんでした。

辞書でひくと:

比べる同類物との差が分かるような、数量的や質的な性質(の組)。

つまり今回はGolangのsliceのappendと事前に割り当てたときのパフォーマンスを定量的に評価するということになります。

実装

ファイルは _test.go という形式で保存してください。

今回は2つの方法を比較します:

  1. slicelengthcapを事前に割り当てて要素を代入していく方法
  2. 空sliceを定義して逐一appendしていく方法
slice_bench_test.go
package main

import "testing"

const Length = 100_000

func appendPreAllocate() []string {
	slice := make([]string, 0, Length)
	for j := 0; j < Length; j++ {
		slice = append(slice, "elem")
	}
	return slice
}

func appendNoPreAllocate() []string {
	slice := []string{}
	for j := 0; j < Length; j++ {
		slice = append(slice, "elem")
	}
	return slice
}

// 1. 割り当てる
func BenchmarkPreAllocate(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = appendPreAllocate()
	}
}

// 2. 逐一追加
func BenchmarkAppendNoCap(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = appendNoPreAllocate()
	}
}

実行方法

ターミナルで以下を実行します

go test -bench=. -benchmem

出力結果

goos: darwin
goarch: arm64
pkg: github.com/kikudesuyo/sandbox/golang-performance
cpu: Apple M3
BenchmarkPreAllocate-8              3686            319839 ns/op      1605642 B/op          1 allocs/op
BenchmarkAppendNoCap-8               327           3627580 ns/op      8923529 B/op         28 allocs/op
PASS
ok      github.com/kikudesuyo/sandbox/golang-performance  

結果の見方

ベンチマーク関数	        実行回数	   実行時間 (ns/op)	使用メモリ (B/op)	  割当回数(allocs/op)
BenchmarkPreAllocate	3686	    319,839	        1,605,642	           1
BenchmarkAppendNoCap	 327	  3,627,580	        8,923,529	          28

結果

  • 実行時間は事前割当が約10倍高速
  • 使用メモリは事前割当が約5倍少ない
  • メモリ割当は事前割当が1回、逐次追加は28回

考察

  • 要素数が事前に分かっている場合は、appendを使うよりも事前にlengthcapを定義する方が圧倒的に効率的 であることがわかった
  • b.ReportAllocs() を使うことで、メモリ割当の回数やバイト数 も確認できるため、パフォーマンス改善の定量的な評価が可能である

本記事で身についた知識(備忘録)

  • 要素数が分かっている場合はappendを使わないようにする(最重要!!)
  • Benchmark接頭辞をつけることでベンチマークを実行できる
  • ベンチマークは _test.go 内で記述し、go testコマンドで実行する
  • テスト関数の接頭辞にはTest, Benchmark, Exampleがある

さいごに

今回Benchmarkの使い方を理解しました。どうやら、Exampleもあることが分かり、今後はExampleの使い方も調べてまとめたいと思っています。
明日も何かしら投稿する予定なのでフォローしてお待ち下さい。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?