go-optimization について
アプリケーションの性能ボトルネックを調べる開発者向けのGo最適化スキルです。プロファイリング、ベンチマーク、メモリと並行処理の改善、ランタイム調整を実例とともに扱います。
主な機能
- 01CPU・メモリ・HTTPのプロファイリング
- 02ベンチマークと結果比較
- 03割り当てとメモリの最適化
- 04並行処理とロック競合の指針
- 05ガベージコレクションとランタイム調整
活用シーン
- 01CPUやメモリのボトルネック調査
- 02ホットパス変更のベンチマーク
- 03メモリ割り当てやロック競合の削減
- 04アプリケーションのガベージコレクション調整
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ geoffjay/ claude-plugins --skill go-optimizationインストール後、Claude Code で/go-optimizationを入力して読み込まれたか確認します。
詳細
Go 最適化スキル
このスキルでは、高パフォーマンスなアプリケーションを構築するためのプロファイリング、ベンチマーク、メモリ管理、ランタイムのチューニングなど、Go のパフォーマンス最適化に関する専門的なガイダンスを提供します。
使用するタイミング
次の場合にこのスキルを有効にします:
- アプリケーションのパフォーマンスをプロファイリングする
- CPU 負荷の高い処理を最適化する
- メモリ割り当てを削減する
- ガベージコレクションをチューニングする
- ベンチマークを作成する
- パフォーマンスのボトルネックを分析する
- ホットパスを最適化する
- ロック競合を削減する
プロファイリング
CPU プロファイリング
import (
"os"
"runtime/pprof"
)
func main() {
// Start CPU profiling
f, err := os.Create("cpu.prof")
if err != nil {
log.Fatal(err)
}
defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal(err)
}
defer pprof.StopCPUProfile()
// Your code here
runApplication()
}
// Analyze:
// go tool pprof cpu.prof
// (pprof) top10
// (pprof) list functionName
// (pprof) web
メモリプロファイリング
import (
"os"
"runtime"
"runtime/pprof"
)
func writeMemProfile(filename string) {
f, err := os.Create(filename)
if err != nil {
log.Fatal(err)
}
defer f.Close()
runtime.GC() // Force GC before snapshot
if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatal(err)
}
}
// Analyze:
// go tool pprof -alloc_space mem.prof
// go tool pprof -inuse_space mem.prof
HTTP プロファイリング
import (
_ "net/http/pprof"
"net/http"
)
func main() {
// Enable pprof endpoints
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// Your application
runServer()
}
// Access profiles:
// http://localhost:6060/debug/pprof/
// go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
// go tool pprof http://localhost:6060/debug/pprof/heap
実行トレース
import (
"os"
"runtime/trace"
)
func main() {
f, err := os.Create("trace.out")
if err != nil {
log.Fatal(err)
}
defer f.Close()
if err := trace.Start(f); err != nil {
log.Fatal(err)
}
defer trace.Stop()
// Your code
runApplication()
}
// View trace:
// go tool trace trace.out
ベンチマーク
基本的なベンチマーク
func BenchmarkStringConcat(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = "hello" + " " + "world"
}
}
func BenchmarkStringBuilder(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
sb.WriteString("hello")
sb.WriteString(" ")
sb.WriteString("world")
_ = sb.String()
}
}
// Run: go test -bench=. -benchmem
サブベンチマーク
func BenchmarkEncode(b *testing.B) {
data := generateTestData()
b.Run("JSON", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
json.Marshal(data)
}
})
b.Run("MessagePack", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
msgpack.Marshal(data)
}
})
}
並列ベンチマーク
func BenchmarkConcurrentAccess(b *testing.B) {
cache := NewCache()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
cache.Get("key")
}
})
}
ベンチマークの比較
# Run benchmarks and save results
go test -bench=. -benchmem > old.txt
# Make optimizations
# Run again and compare
go test -bench=. -benchmem > new.txt
benchstat old.txt new.txt
メモリの最適化
エスケープ解析
// Check what escapes to heap
// go build -gcflags="-m" main.go
// ✅ GOOD: Stack allocation
func stackAlloc() int {
x := 42
return x
}
// ❌ BAD: Heap escape
func heapEscape() *int {
x := 42
return &x // x escapes to heap
}
// ✅ GOOD: Interface without allocation
func noAlloc(w io.Writer, data []byte) {
w.Write(data)
}
// ❌ BAD: Interface causes allocation
func withAlloc() io.Writer {
var b bytes.Buffer
return &b // &b escapes
}
事前割り当て
// ❌ BAD: Growing slice
func badAppend(n int) []int {
var result []int
for i := 0; i < n; i++ {
result = append(result, i) // Multiple allocations
}
return result
}
// ✅ GOOD: Pre-allocate
func goodAppend(n int) []int {
result := make([]int, 0, n) // Single allocation
for i := 0; i < n; i++ {
result = append(result, i)
}
return result
}
// ✅ GOOD: Known length
func knownLength(n int) []int {
result := make([]int, n)
for i := 0; i < n; i++ {
result[i] = i
}
return result
}
// ❌ BAD: String concatenation
func badConcat(strs []string) string {
result := ""
for _, s := range strs {
result += s // New allocation each time
}
return result
}
// ✅ GOOD: strings.Builder
func goodConcat(strs []string) string {
var sb strings.Builder
sb.Grow(estimateSize(strs))
for _, s := range strs {
sb.WriteString(s)
}
return sb.String()
}
sync.Pool
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func processData(data []byte) []byte {
// Get buffer from pool
buf := bufferPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufferPool.Put(buf)
// Use buffer
buf.Write(data)
// Process...
return buf.Bytes()
}
// String builder pool
var sbPool = sync.Pool{
New: func() interface{} {
return &strings.Builder{}
},
}
func buildString(parts []string) string {
sb := sbPool.Get().(*strings.Builder)
sb.Reset()
defer sbPool.Put(sb)
for _, part := range parts {
sb.WriteString(part)
}
return sb.String()
}
ゼロコピー技法
// Use byte slices instead of strings
func parseHeader(header []byte) (key, value []byte) {
i := bytes.IndexByte(header, ':')
if i < 0 {
return nil, nil
}
return header[:i], header[i+1:]
}
// Reuse buffers
type Parser struct {
buf []byte
}
func (p *Parser) Parse(data []byte) error {
p.buf = p.buf[:0] // Reset length, keep capacity
p.buf = append(p.buf, data...)
// Process p.buf...
return nil
}
// Direct writing
func writeResponse(w io.Writer, data interface{}) error {
enc := json.NewEncoder(w) // Write directly to w
return enc.Encode(data)
}
ガベージコレクションのチューニング
GC 制御
import "runtime/debug"
// Adjust GC target percentage
debug.SetGCPercent(100) // Default
// Higher = less frequent GC, more memory
// Lower = more frequent GC, less memory
// Force GC (use sparingly!)
runtime.GC()
// Monitor GC stats
var stats runtime.MemStats
runtime.ReadMemStats(&stats)
fmt.Printf("Alloc = %v MB\n", stats.Alloc/1024/1024)
fmt.Printf("TotalAlloc = %v MB\n", stats.TotalAlloc/1024/1024)
fmt.Printf("Sys = %v MB\n", stats.Sys/1024/1024)
fmt.Printf("NumGC = %v\n", stats.NumGC)
GOGC 環境変数
# Default (100%)
GOGC=100 ./myapp
# More aggressive GC (uses less memory)
GOGC=50 ./myapp
# Less frequent GC (uses more memory)
GOGC=200 ./myapp
# Disable GC (for debugging)
GOGC=off ./myapp
並行処理の最適化
ロック競合の削減
// ❌ BAD: Single lock
type BadCache struct {
mu sync.Mutex
items map[string]interface{}
}
// ✅ GOOD: RWMutex
type GoodCache struct {
mu sync.RWMutex
items map[string]interface{}
}
func (c *GoodCache) Get(key string) interface{} {
c.mu.RLock()
defer c.mu.RUnlock()
return c.items[key]
}
// ✅ BETTER: Sharded locks
type ShardedCache struct {
shards [256]*shard
}
type shard struct {
mu sync.RWMutex
items map[string]interface{}
}
func (c *ShardedCache) Get(key string) interface{} {
shard := c.getShard(key)
shard.mu.RLock()
defer shard.mu.RUnlock()
return shard.items[key]
}
チャネルのバッファリング
// ❌ BAD: Unbuffered channel causes blocking
ch := make(chan int)
// ✅ GOOD: Buffered channel
ch := make(chan int, 100)
// Optimal buffer size depends on:
// - Producer/consumer rates
// - Memory constraints
// - Latency requirements
アトミック操作
import "sync/atomic"
type Counter struct {
value int64
}
func (c *Counter) Increment() {
atomic.AddInt64(&c.value, 1)
}
func (c *Counter) Value() int64 {
return atomic.LoadInt64(&c.value)
}
// ✅ Faster than mutex for simple operations
// ❌ Limited to basic types and operations
アルゴリズムの最適化
Map の事前サイズ指定
// ❌ BAD: Growing map
func badMap(items []Item) map[string]Item {
m := make(map[string]Item)
for _, item := range items {
m[item.ID] = item
}
return m
}
// ✅ GOOD: Pre-sized map
func goodMap(items []Item) map[string]Item {
m := make(map[string]Item, len(items))
for _, item := range items {
m[item.ID] = item
}
return m
}
不要な処理を避ける
// ❌ BAD: Repeated computation
func process(items []Item) {
for _, item := range items {
if isValid(item) {
result := expensiveComputation(item)
if result > threshold {
handleResult(result)
}
}
}
}
// ✅ GOOD: Early returns
func process(items []Item) {
for _, item := range items {
if !isValid(item) {
continue // Skip early
}
result := expensiveComputation(item)
if result <= threshold {
continue // Skip early
}
handleResult(result)
}
}
// ✅ BETTER: Fast path
func process(items []Item) {
for _, item := range items {
// Fast path for common case
if item.IsSimple() {
handleSimple(item)
continue
}
// Slow path for complex case
handleComplex(item)
}
}
ランタイムのチューニング
GOMAXPROCS
import "runtime"
// Set number of OS threads
runtime.GOMAXPROCS(runtime.NumCPU())
// For CPU-bound: NumCPU
// For I/O-bound: NumCPU * 2 or more
環境変数
# Max OS threads
GOMAXPROCS=8 ./myapp
# GC aggressiveness
GOGC=100 ./myapp
# Memory limit (Go 1.19+)
GOMEMLIMIT=4GiB ./myapp
# Trace execution
GODEBUG=gctrace=1 ./myapp
パフォーマンスパターン
関数のインライン化
// Compiler inlines small functions automatically
//go:inline
func add(a, b int) int {
return a + b
}
// Keep hot-path functions small for inlining
インターフェースの割り当てを避ける
// ❌ BAD: Interface allocation
func badPrint(value interface{}) {
fmt.Println(value) // value escapes
}
// ✅ GOOD: Type-specific functions
func printInt(value int) {
fmt.Println(value)
}
func printString(value string) {
fmt.Println(value)
}
バッチ処理
// ❌ BAD: Individual operations
for _, item := range items {
db.Insert(item) // N database calls
}
// ✅ GOOD: Batch operations
db.BatchInsert(items) // 1 database call
ベストプラクティス
- 最適化の前にプロファイリングする - 推測ではなく測定します
- ホットパスに集中する - 重要な 20% を最適化します
- 割り当てを削減する - オブジェクトを再利用し、事前に割り当てます
- 適切なデータ構造を使う - Map、スライス、配列を使い分けます
- ロック競合を最小限に抑える - RWMutex やシャーディングを使います
- 変更をベンチマークする - 比較には benchstat を使います
- 競合検出器を使ってテストする -
go test -race - 本番環境で監視する - プロファイリング用エンドポイントを使います
- 可読性とパフォーマンスのバランスを取る - 過剰な最適化は避けます
- PGO を使う - プロファイルに基づく最適化(Go 1.20 以降)
プロファイルに基づく最適化(PGO)
# 1. Build with profiling
go build -o myapp
# 2. Run and collect profile
./myapp -cpuprofile=default.pgo
# 3. Rebuild with PGO
go build -pgo=default.pgo -o myapp-optimized
# Performance improvement: 5-15% typical
リソース
その他のリソース:
assets/examples/- パフォーマンス最適化の例assets/benchmarks/- ベンチマークのテンプレートreferences/- プロファイリングガイドやパフォーマンス関連論文へのリンク
コメント
Markdown 対応