golang-performance について
ボトルネックを調べる開発者向けに、Go アプリケーションの性能改善を案内するスキルです。プロファイリング、メモリ、並行処理、データ構造、ベンチマークを扱い、測定してから最適化する方針を示します。
主な機能
- 01CPU・メモリのプロファイリング
- 02アロケーションと GC 負荷の削減
- 03並行処理の最適化
- 04エスケープ解析の指針
- 05ベンチマーク手法
- 06一般的な性能上の落とし穴
活用シーン
- 01CPU またはメモリのボトルネック調査
- 02ホットパスでのアロケーション削減
- 03並行処理の改善
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ 89jobrien/ steve --skill golang-performanceインストール後、Claude Code で/golang-performanceを入力して読み込まれたか確認します。
詳細
Golang のパフォーマンス
このスキルでは、プロファイリング、メモリ管理、並行処理の最適化、一般的なパフォーマンス上の落とし穴の回避など、Go アプリケーションのパフォーマンスを最適化するためのガイダンスを提供します。
このスキルを使用する場面
- Go アプリケーションの CPU またはメモリの問題をプロファイリングするとき
- メモリ割り当てを最適化し、GC の負荷を軽減するとき
- 効率的な並行処理パターンを実装するとき
- エスケープ解析の結果を分析するとき
- 本番コードのホットパスを最適化するとき
pprof によるプロファイリング
HTTP サーバーでプロファイリングを有効にする
import (
"net/http"
_ "net/http/pprof"
)
func main() {
// pprof endpoints available at /debug/pprof/
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
// Main application
}
CPU プロファイリング
# Collect 30-second CPU profile
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# Interactive commands
(pprof) top10 # Top 10 functions by CPU
(pprof) list FuncName # Show source with timing
(pprof) web # Open flame graph in browser
メモリプロファイリング
# Heap profile
go tool pprof http://localhost:6060/debug/pprof/heap
# Allocs profile (all allocations)
go tool pprof http://localhost:6060/debug/pprof/allocs
# Interactive commands
(pprof) top10 -cum # Top by cumulative allocations
(pprof) list FuncName # Show allocation sites
プログラムによるプロファイリング
import (
"os"
"runtime/pprof"
)
func profileCPU() {
f, _ := os.Create("cpu.prof")
defer f.Close()
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// Code to profile
}
func profileMemory() {
f, _ := os.Create("mem.prof")
defer f.Close()
runtime.GC() // Get accurate stats
pprof.WriteHeapProfile(f)
}
メモリの最適化
割り当てを減らす
// BAD: Allocates on every call
func Process(items []string) []string {
result := []string{}
for _, item := range items {
result = append(result, transform(item))
}
return result
}
// GOOD: Pre-allocate with known capacity
func Process(items []string) []string {
result := make([]string, 0, len(items))
for _, item := range items {
result = append(result, transform(item))
}
return result
}
頻繁な割り当てには sync.Pool を使用する
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func ProcessRequest(data []byte) []byte {
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
// Use buffer
buf.Write(data)
return buf.Bytes()
}
ループ内での文字列連結を避ける
// BAD: O(n^2) allocations
func BuildString(parts []string) string {
result := ""
for _, part := range parts {
result += part
}
return result
}
// GOOD: Single allocation
func BuildString(parts []string) string {
var builder strings.Builder
for _, part := range parts {
builder.WriteString(part)
}
return builder.String()
}
スライスによるメモリリーク
// BAD: Keeps entire backing array alive
func GetFirst(data []byte) []byte {
return data[:10]
}
// GOOD: Copy to release backing array
func GetFirst(data []byte) []byte {
result := make([]byte, 10)
copy(result, data[:10])
return result
}
エスケープ解析
# Show escape analysis decisions
go build -gcflags="-m" ./...
# More verbose
go build -gcflags="-m -m" ./...
ヒープへのエスケープを避ける
// ESCAPES: Returned pointer
func NewUser() *User {
return &User{} // Allocated on heap
}
// STAYS ON STACK: Value return
func NewUser() User {
return User{} // May stay on stack
}
// ESCAPES: Interface conversion
func Process(v interface{}) { ... }
func main() {
x := 42
Process(x) // x escapes to heap
}
並行処理の最適化
ワーカープールパターン
func ProcessItems(items []Item, workers int) []Result {
jobs := make(chan Item, len(items))
results := make(chan Result, len(items))
// Start workers
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for item := range jobs {
results <- process(item)
}
}()
}
// Send jobs
for _, item := range items {
jobs <- item
}
close(jobs)
// Wait and collect
go func() {
wg.Wait()
close(results)
}()
var output []Result
for r := range results {
output = append(output, r)
}
return output
}
スループット向上のためのバッファ付きチャネル
// SLOW: Unbuffered causes blocking
ch := make(chan int)
// FAST: Buffer reduces contention
ch := make(chan int, 100)
ロックの競合を避ける
// BAD: Global lock
var mu sync.Mutex
var cache = make(map[string]string)
func Get(key string) string {
mu.Lock()
defer mu.Unlock()
return cache[key]
}
// GOOD: Sharded locks
type ShardedCache struct {
shards [256]struct {
mu sync.RWMutex
items map[string]string
}
}
func (c *ShardedCache) getShard(key string) *struct {
mu sync.RWMutex
items map[string]string
} {
h := fnv.New32a()
h.Write([]byte(key))
return &c.shards[h.Sum32()%256]
}
func (c *ShardedCache) Get(key string) string {
shard := c.getShard(key)
shard.mu.RLock()
defer shard.mu.RUnlock()
return shard.items[key]
}
特定のケースでは sync.Map を使用する
// Good for: keys written once, read many; disjoint key sets
var cache sync.Map
func Get(key string) (string, bool) {
v, ok := cache.Load(key)
if !ok {
return "", false
}
return v.(string), true
}
func Set(key, value string) {
cache.Store(key, value)
}
データ構造の最適化
構造体フィールドの順序(メモリアライメント)
// BAD: 24 bytes (padding)
type Bad struct {
a bool // 1 byte + 7 padding
b int64 // 8 bytes
c bool // 1 byte + 7 padding
}
// GOOD: 16 bytes (no padding)
type Good struct {
b int64 // 8 bytes
a bool // 1 byte
c bool // 1 byte + 6 padding
}
可能な場合は interface{} を避ける
// SLOW: Type assertions, boxing
func Sum(values []interface{}) float64 {
var sum float64
for _, v := range values {
sum += v.(float64)
}
return sum
}
// FAST: Concrete types
func Sum(values []float64) float64 {
var sum float64
for _, v := range values {
sum += v
}
return sum
}
ベンチマークのパターン
func BenchmarkProcess(b *testing.B) {
data := generateTestData()
b.ResetTimer() // Exclude setup time
for i := 0; i < b.N; i++ {
Process(data)
}
}
// Memory benchmarks
func BenchmarkAllocs(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = make([]byte, 1024)
}
}
// Compare implementations
func BenchmarkComparison(b *testing.B) {
b.Run("old", func(b *testing.B) {
for i := 0; i < b.N; i++ {
OldImplementation()
}
})
b.Run("new", func(b *testing.B) {
for i := 0; i < b.N; i++ {
NewImplementation()
}
})
}
次のコマンドで実行します。
go test -bench=. -benchmem ./...
go test -bench=. -benchtime=5s ./... # Longer runs
よくある落とし穴
ホットループ内での defer
// BAD: Defer overhead per iteration
for _, item := range items {
mu.Lock()
defer mu.Unlock() // Defers stack up!
process(item)
}
// GOOD: Explicit unlock
for _, item := range items {
mu.Lock()
process(item)
mu.Unlock()
}
// BETTER: Extract to function
for _, item := range items {
processWithLock(item)
}
func processWithLock(item Item) {
mu.Lock()
defer mu.Unlock()
process(item)
}
JSON エンコードのパフォーマンス
// SLOW: Reflection on every call
json.Marshal(v)
// FAST: Reuse encoder
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
encoder.Encode(v)
// FASTER: Code generation (easyjson, ffjson)
ベストプラクティス
- 最適化の前に計測する - プロファイリングで実際のボトルネックを見つけます
- スライスを事前に確保する - サイズが分かっている場合は
make([]T, 0, capacity)を使用します - 頻繁に割り当てるオブジェクトをプールする - バッファには
sync.Poolを使用します - ホットパスでの割り当てを最小限にする - オブジェクトを再利用し、インターフェースを避けます
- チャネルのサイズを適切にする - メモリを無駄にせず、バッファによってブロッキングを減らします
- 時期尚早な最適化を避ける - まず明瞭さを重視し、計測で問題を確認してから最適化します
- 小さな構造体には値レシーバーを使用する - ポインターの間接参照を避けます
- 構造体フィールドをサイズ順に並べる - 大きいものから小さいものへ並べることで、パディングを減らします
コメント
Markdown 対応