go-performance について
Go のホットパスを最適化する開発者向けのスキルです。メモリー割り当てを意識した実装、文字列処理、ベンチマークを扱い、変更の効果を測定してから採用する方針を示します。
主な機能
- 01strconv による基本型変換
- 02文字列からバイト列への反復変換の回避
- 03マップとスライスの容量指定
- 04文字列連結の選択指針
- 05ベンチマークとプロファイリングの指針
活用シーン
- 01ホットパスのメモリー割り当て削減
- 02文字列変換と構築の最適化
- 03ベンチマークによる性能変化の測定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ cxuu/ golang-skills --skill go-performanceインストール後、Claude Code で/go-performanceを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/BENCHMARKS.mdSTRING-OPTIMIZATION.mdscripts/bench-compare.sh詳細
Go のパフォーマンスパターン
リソースの振り分け
scripts/bench-compare.sh- ベンチマーク結果の比較、ベースラインの保存、または JSON ベンチマークメタデータの生成を行う場合に実行します。references/BENCHMARKS.md- ベンチマークの作成、benchstat の使用、または pprof によるプロファイリングを行う場合に読みます。references/STRING-OPTIMIZATION.md- 文字列変換、連結、またはバイト列と文字列の境界を最適化する場合に読みます。
パフォーマンスに関するガイドラインは、ホットパスにのみ適用してください。時期尚早な最適化は避け、最も効果のある箇所でこれらのパターンに注目してください。
fmt より strconv を優先する
プリミティブ型を文字列に変換する場合、または文字列から変換する場合は、strconv のほうが fmt より高速です。
s := strconv.Itoa(rand.Int()) // ~2x faster than fmt.Sprint()
| 方法 | 速度 | アロケーション |
|---|---|---|
fmt.Sprint | 143 ns/op | 2 allocs/op |
strconv.Itoa | 64.2 ns/op | 1 allocs/op |
文字列からバイト列への変換の繰り返しを避ける
固定文字列はループの外で一度だけ []byte に変換します。
data := []byte("Hello world")
for b.Loop() { // Go 1.24+; use b.N loops only for older Go
w.Write(data) // ~7x faster than []byte("...") each iteration
}
コンテナの容量を指定する
可能な場合はコンテナの容量を指定して、メモリをあらかじめ割り当てます。これにより、要素の追加に伴うコピーやサイズ変更による後続のアロケーションを最小限に抑えられます。
map の容量ヒント
make() で map を初期化する際は、容量ヒントを指定します。
m := make(map[string]os.DirEntry, len(files))
注: スライスとは異なり、map の容量ヒントは事前割り当てを完全に保証するものではなく、必要なハッシュマップのバケット数を概算します。
スライスの容量
make() でスライスを初期化する際は、特に追加操作を行う場合に容量ヒントを指定します。
data := make([]int, 0, size)
map とは異なり、スライスの容量はヒントではありません。コンパイラーは指定された量のメモリを正確に割り当てます。容量に達するまでは、後続の append() 操作でアロケーションは発生しません。
| 方法 | 時間(1 億回の反復) |
|---|---|
| 容量指定なし | 2.48s |
| 容量指定あり | 0.21s |
append 中の再割り当てが発生しないため、容量指定版は約 12 倍高速です。
値渡しにする
数バイトを節約するためだけに、関数の引数としてポインターを渡さないでください。関数内で引数 x を一貫して *x としてのみ参照する場合、その引数はポインターにすべきではありません。
func process(s string) { // not *string — strings are small fixed-size headers
fmt.Println(s)
}
値渡しでよく使われる型: string、io.Reader、小さな構造体。
例外:
- コピーのコストが高い大きな構造体
- 将来的に大きくなる可能性がある小さな構造体
文字列の連結
複雑さに応じて適切な方法を選びます。
| 方法 | 最適な用途 |
|---|---|
+ | 文字列が少数の場合の単純な連結 |
fmt.Sprintf | 異なる型を含む書式付き出力 |
strings.Builder | ループ内での構築や、少しずつ組み立てる場合 |
strings.Join | スライスの結合 |
| バッククォートリテラル | 複数行の定数テキスト |
ベンチマークとプロファイリング
最適化の前後で必ず測定してください。Go に組み込まれているベンチマークフレームワークとプロファイリングツールを使用します。
go test -bench=. -benchmem -count=10 ./...
検証: 最適化を適用した後、
bash scripts/bench-compare.shを実行して実際の効果を測定してください。測定可能な改善がある最適化のみを残してください。
クイックリファレンス
| パターン | 悪い例 | 良い例 | 改善 |
|---|---|---|---|
| int から文字列への変換 | fmt.Sprint(n) | strconv.Itoa(n) | 約 2 倍高速 |
[]byte の繰り返し変換 | ループ内の []byte("str") | ループの外で一度だけ変換 | 約 7 倍高速 |
| map の初期化 | make(map[K]V) | make(map[K]V, size) | アロケーションの削減 |
| スライスの初期化 | make([]T, 0) | make([]T, 0, cap) | 約 12 倍高速 |
| 小さな固定サイズの引数 | *string、*io.Reader | string、io.Reader | 間接参照なし |
| 単純な文字列の結合 | s1 + " " + s2 | (すでに適切) | 文字列が少数なら + を使用 |
| ループ内での文字列構築 | += の繰り返し | strings.Builder | O(n²) に対して O(n) |
関連スキル
- データ構造:スライス、マップ、配列のどれを選ぶか、またはメモリ割り当てのセマンティクスを理解するには、go-data-structures を参照してください
- 宣言パターン:容量のヒントを指定して
makeを使用する場合や、マップとスライスを初期化する場合は、go-declarations を参照してください - 並行処理:ゴルーチン間で作業を並列化する場合や、バッファーを再利用するために sync.Pool を使用する場合は、go-concurrency を参照してください
- スタイルの原則:最適化に伴う可読性の低下が許容できるかどうかを判断する場合は、go-style-core を参照してください