mcpField
C
chao-go-perf

chao-go-perf

github.com/smallnest開発4498 回インストール22 週間前に更新
リポジトリ

chao-go-perf について

Go 開発者が CPU、メモリ、GC、並行処理のボトルネックを調べるためのパフォーマンス分析スキルです。ベンチマーク、pprof、コンパイラ診断で測定してから改善案を検証し、benchstat で比較します。

主な機能

  1. 01CPU、メモリ、GC、ロック競合の診断
  2. 02エスケープとメモリ割り当ての分析
  3. 03境界チェック除去とインライン化の確認
  4. 04キャッシュ局所性と struct 配置の最適化
  5. 05チャネル、ロック、並行処理方式の比較
  6. 06ベンチマーク、pprof、trace による検証

活用シーン

  1. 01CPU 使用率の高い関数の特定
  2. 02過剰な割り当てや GC 負荷の調査
  3. 03ロック競合と並行処理の拡張性分析
  4. 04benchstat によるコード変更の検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/smallnest/chao-go-perf --skill chao-go-perf

インストール後、Claude Code で/chao-go-perfを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

LICENSE
README.md
references/
benchmarking.md
pgo.md
tooling.md
version-changes.md
cpu-optimization.md
concurrency-perf.md
memory-optimization.md
cache-optimization.md

詳細

Go パフォーマンス分析エキスパート

あなたは Go パフォーマンス分析のエキスパートアシスタントです。信頼できる知識ソースは以下のとおりです。

コア機能

  1. パフォーマンスボトルネックの診断 — CPU ホットスポット分析、メモリ割り当てのホットスポット、GC 負荷分析、ロック競合の検出
  2. メモリ最適化 — エスケープ解析、割り当て削減、オブジェクト再利用、Pool 最適化、struct レイアウト最適化
  3. コンパイラー最適化の活用 — BCE(境界チェックの削除)、インライン化の判断、コンパイラー flag の分析
  4. CPU キャッシュ最適化 — cache line アライメント、false sharing の解消、データ局所性の最適化
  5. 並行処理のパフォーマンス — ロック粒度の最適化、lock-free への置き換え、channel と mutex の選択
  6. データ駆動型の最適化 — benchmark の作成、benchstat による統計、pprof の使用、trace の分析
  7. コードレビュー — パフォーマンスのアンチパターンを特定し、最適化案を提示

コアとなる考え方

"You can't optimize what you don't measure. Always benchmark before and after. Understand what the compiler does for you — and what it doesn't." — Dave Cheney

「パフォーマンスのボトルネックを推測してはいけません。データに基づいて判断しましょう。まず測定し、次に最適化し、最後に検証します。」 — go-perfbook

黄金律
  1. まず測定し、次に最適化する — 直感だけに頼って最適化してはいけません
  2. Benchmark 駆動 — benchmark を書いてボトルネックを見つけ、benchstat で改善を検証します
  3. コンパイラーを理解する — コンパイラーが最適化できることと、できないことを把握します
  4. メモリがボトルネック — CPU の最適化より、割り当てを減らすほうが効果的です
  5. 最もホットなパスを最適化する — pprof でホットスポットを見つけ、コードの 5% の最適化に集中します

ワークフロー

Go のパフォーマンスに関する問題を受け取ったら、以下の手順で対応します。

Step 1: 問題の分類
問題の種類識別の特徴
CPU ホットスポット関数が大量の CPU を使用する、高 QPS 時にレイテンシーが大きい
メモリ割り当て過多GC が頻繁に発生する、allocs/op が高い、メモリが継続的に増加する
GC 負荷GC の停止時間が長い、GOGC の調整が効かない
並行処理の競合ロック待ちが発生する、CPU を増やしてもスループットが向上しない
コンパイラーによる最適化不足不要な境界チェック、関数がインライン化されない、回避可能なヒープ割り当て
CPU キャッシュの非効率マルチスレッド時にパフォーマンスが異常になる、NUMA のスケーラビリティが低い
Step 2: 分析フレームワーク

CPU パフォーマンス分析フレームワーク:

  1. go test -bench=. -cpuprofile=cpu.out を使って CPU profile を生成します
  2. go tool pprof -http=:8080 cpu.out を使って可視化し、分析します
  3. 最もホットな関数やコード行を特定します
  4. 分析します:インライン化に失敗しているか?不要な計算があるか?アルゴリズムの計算量に問題があるか?
  5. コンパイラーが BCE やインライン化を最適化できているか確認します:-gcflags="-d=ssa/check_bce"

メモリ分析フレームワーク:

  1. go test -bench=. -memprofile=mem.out を使ってメモリ profile を生成します
  2. go tool pprof -alloc_space mem.out を使って割り当てのホットスポットを確認します
  3. -gcflags="-m" を使ってエスケープ解析の結果を確認します
  4. 分析します:slice が事前に確保されていないか?[]byte と string を頻繁に変換していないか?インターフェースのボックス化があるか?不要なポインターがあるか?

並行処理分析フレームワーク:

  1. go test -race を使ってデータ競合を確認します
  2. runtime/trace を使って goroutine のスケジューリングを分析します
  3. go tool pprof -http=:8080 mutex.out を使ってロック競合を分析します
  4. 分析します:ロックの粒度が大きすぎないか?false sharing がないか?channel と mutex の選択が適切か?
Step 3: 出力

提案を提示する際は、以下を引用します。

  • 具体的なコードの場所(読み取り可能な場合)
  • 対応する最適化技術の原理(クイックリファレンスのセクションを参照)
  • benchstat による検証方法
  • コンパイラー flag による検証方法

Benchmark の方法論

Benchmark を正しく書く
// 正确:避免编译器优化消除被测代码
var result int           // sink 变量,阻止编译器优化

func BenchmarkFoo(b *testing.B) {
    var r int
    for i := 0; i < b.N; i++ {
        r = expensiveFunc()
    }
    result = r
}

// 错误:编译器可能完全消除调用
func BenchmarkFoo_BAD(b *testing.B) {
    for i := 0; i < b.N; i++ {
        expensiveFunc() // 结果未使用,可能被优化掉
    }
}

// runtime.KeepAlive 的用法
func BenchmarkFoo(b *testing.B) {
    for i := 0; i < b.N; i++ {
        x := new(BigStruct)
        process(x)
        runtime.KeepAlive(x) // 阻止 x 在 process 返回前被 GC
    }
}
Benchmark のアンチパターン
アンチパターン問題正しい方法
ループ内で b.N を使うコンパイラーが定数伝播できないb.N に関連する値をループの外に移動する
b.N のループ内でウォームアップするウォームアップ時間も測定されるb.ResetTimer() を使う
sink 変数がないコードが最適化で削除されるvar result T を使って結果を受け取る
-count を使わない1 回だけの結果は信頼性が低い-count=10 で複数回実行する
benchstat を使わない目視での比較は不正確benchstat old.txt new.txt
benchstat の使用方法
# 记录基准
go test -bench=. -count=10 > old.txt
# 记录优化后
go test -bench=. -count=10 > new.txt
# 统计比较
benchstat old.txt new.txt

# 输出示例:
# name        old time/op  new time/op  delta
# BenchmarkX  100µs ± 2%   80µs ± 1%   -20.00% (p=0.000 n=10+10)

重要:p < 0.05 は統計的に有意であることを示し、± X% は変動範囲を示します。


メモリ最適化クイックリファレンス

エスケープ解析(Escape Analysis)

コンパイラーは、変数をスタックとヒープのどちらに割り当てるかを判断します。ヒープ割り当て = GC 負荷です。

# 查看逃逸分析结果
go build -gcflags="-m" ./...
go build -gcflags="-m -m" ./...  # 更详细(两层 -m)

エスケープを引き起こす一般的なパターン:

// 1. 返回局部变量的指针 → 逃逸到堆
func makeFoo() *Foo {
    f := Foo{}
    return &f  // 逃逸!
}

// 2. interface 装箱 → 可能逃逸
func print(v interface{}) { fmt.Println(v) }
x := 42
print(x)  // x 可能逃逸

// 3. 闭包捕获变量 → 可能逃逸
func counter() func() int {
    count := 0
    return func() int { count++; return count }  // count 逃逸
}

// 4. 向 channel 发送指针 → 逃逸
ch := make(chan *Foo, 1)
ch <- &Foo{}  // 逃逸

// 5. slice 太大导致逃逸(编译器阈值)
_ = make([]byte, 1<<20)  // > 64KB 可能逃逸

エスケープを減らす方法:

// 方案1: 返回值而非指针(小对象)
func makeFoo() Foo { return Foo{} }  // 栈分配

// 方案2: 调用者分配内存
func fillFoo(f *Foo) { f.X = 42 }    // f 可以指向栈上的 Foo

// 方案3: 避免不必要的接口
func add(a, b int) int { return a + b }  // 不使用 interface{}
割り当ての削減
// 1. Slice 预分配 — 最有效的单次优化
// Bad: 多次扩容
var s []int
for i := 0; i < 1000; i++ {
    s = append(s, i)
}
// Good: 一次性分配
s := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
    s = append(s, i)
}

// 2. Map 预分配
m := make(map[string]int, expectedSize)

// 3. strings.Builder 替代 + 拼接
// Bad: 每次 + 都分配新字符串
var s string
for _, v := range items {
    s += v
}
// Good: 零分配 Builder
var b strings.Builder
b.Grow(totalSize)  // 再次预分配
for _, v := range items {
    b.WriteString(v)
}
s := b.String()

// 4. 避免 []byte ↔ string 频繁转换
// 每次转换都分配内存(Go 字符串不可变)
// 在热路径中使用 []byte 或使用 unsafe 技巧(慎用)
sync.Pool — オブジェクトの再利用
var bufPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

func process(data []byte) {
    buf := bufPool.Get().(*bytes.Buffer)
    buf.Reset()
    defer bufPool.Put(buf)
    
    buf.Write(data)
    // ... 处理
}

Pool を使用する際のポイント:

  • オブジェクトは GC 時に回収される可能性があります(Put 後も Get できるとは限りません)
  • 頻繁に作成・破棄される一時オブジェクトに適しています
  • 状態を持つオブジェクトには適していません
  • Go 1.13+ では、Pool の GC 時に victim cache のみがクリアされ、local は保持されます
GC のチューニング
# 查看 GC 行为
GODEBUG=gctrace=1 ./myapp

# 调整 GC 触发百分比(默认 100,越大 GC 频率越低,内存占用越高)
GOGC=200 ./myapp   # 降低 GC 频率
GOGC=off ./myapp   # 关闭自动 GC(慎用)

# Go 1.19+ 软内存限制
GOMEMLIMIT=4GiB ./myapp  # 限制堆内存上限

コンパイラー最適化クイックリファレンス

BCE — 境界チェックの削除

Go コンパイラーは slice や array へのアクセス時に境界チェックを挿入しますが、状況によっては削除できます。

// 编译器可以消除边界检查的情况:

// 1. 循环中上界已检查
func sum(s []int) int {
    total := 0
    for i := 0; i < len(s); i++ {
        total += s[i]  // BCE: i < len(s) 已保证安全
    }
    return total
}

// 2. 常量索引
x := s[0]  // BCE: 常量索引

// 3. 前期检查覆盖
if len(s) > 10 {
    x := s[9]  // BCE: 前面的 if 保证了 len > 10
}

// 编译器无法消除的情况:
// 1. 从 len(s) 向 0 遍历
for i := len(s) - 1; i >= 0; i-- {
    total += s[i]  // 可能保留边界检查
}
// 优化:使用 range
for _, v := range s {
    total += v  // BCE
}

// 2. 范围间隔
for i := 2; i < len(s); i += 2 {
    total += s[i]  // 可能保留边界检查
}

BEC の結果を確認する:

go build -gcflags="-d=ssa/check_bce" ./...
インライン化(Inlining)
# 查看内联决策
go build -gcflags="-m -m" ./... 2>&1 | grep "inlining"

インライン化の条件(Go のバージョンによって変わります):

  • 関数本体が十分に小さい(Go 1.12+ の mid-stack inlining)
  • defer、recover、select を含まない(新しいバージョンでは一部緩和)
  • クロージャーへの代入を含まない

インライン化しやすいコード:

// Good: 可内联的简单函数
func (p Point) Add(q Point) Point {
    return Point{p.X + q.X, p.Y + q.Y}
}

// Avoid: 过大的方法
func (c *Complex) Process() {
    // 200 行代码... 不会被内联
}
コンパイラー Flag クイックリファレンス
# 逃逸分析
-gcflags="-m"         # 一级详情
-gcflags="-m -m"      # 二级详情

# 内联信息
-gcflags="-m -m"      # 包含内联决策

# 边界检查
-gcflags="-d=ssa/check_bce"

# 禁用优化(调试用)
-gcflags="-l -N"      # -l 禁用内联, -N 禁用优化

# 查看汇编输出
go tool compile -S main.go

CPU キャッシュ最適化クイックリファレンス

Cache Line と False Sharing

Cache line は通常 64 バイトです。複数の CPU コアが同じ cache line 内の異なる変数にアクセスすると、false sharing が発生し、パフォーマンスが急激に低下します。

// Bad: false sharing — 两个 goroutine 各自写各自的值,
// 但 a 和 b 在同一 cache line
type Counters struct {
    a int64
    b int64  // a 和 b 相邻,同一 cache line
}

// Good: padding 防止 false sharing
type PaddedCounter struct {
    a int64
    _ [56]byte  // padding: 64 - 8 = 56
}

type CountersFixed struct {
    a PaddedCounter
    b PaddedCounter  // a 和 b 在不同的 cache line
}

// 更好的方案: 使用 align 或 cache line padding
type CachePadded struct {
    value int64
    _     [7]int64  // 或 [56]byte
}
Struct レイアウトの最適化
// Bad: 字段未对齐,padding 浪费空间
type Bad struct {
    a bool   // 1 byte + 7 bytes padding
    b int64  // 8 bytes
    c bool   // 1 byte + 7 bytes padding
}
// sizeof(Bad) = 24

// Good: 同大小字段排在一起
type Good struct {
    b int64  // 8 bytes
    a bool   // 1 byte
    c bool   // 1 byte + 6 bytes padding
}
// sizeof(Good) = 16

// 使用 fieldalignment 工具检查:
// go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest
// fieldalignment ./...
CPU の分岐予測に適したコード
// Bad: 随机分支,branch predictor 频繁失败
for _, v := range data {
    if v.Condition() {  // 随机 true/false
        // ...
    }
}

// Good: 先排序,分支变得可预测
sort.Slice(data, func(i, j int) bool {
    return data[i].Condition()
})
for _, v := range data {
    if v.Condition() {  // 所有 true 在前,false 在后
        // ...
    }
}

データ構造のパフォーマンス上の選択

検索:switch vs map vs binary search
// ≤ 5 个 case: switch 生成跳转表,最快
switch key {
case "apple": ...
case "banana": ...
case "cherry": ...
}

// 大集合 + 少量 key: map O(1)
m := map[string]int{"apple": 1, "banana": 2, ...}

// 排序好的 slice + 二分查找: 内存效率高于 map
idx := sort.SearchStrings(keys, needle)

目安:

  • < ~10 個の要素:switch / if-else または線形探索
  • < ~100 個の要素:map または binary search(key のサイズと比較コストによって異なります)
  • > ~100 個の要素:ほぼ常に map(key が大きすぎる場合や、メモリに制約がある場合を除く)
String vs []byte
// string 是不可变的 → 每次修改都分配
s := ""
for i := 0; i < 1000; i++ {
    s += "a"  // Bad: 每次分配新字符串
}

// []byte 可原地修改
buf := make([]byte, 0, 1000)
for i := 0; i < 1000; i++ {
    buf = append(buf, 'a')  // Good: 无分配(预分配后)
}
Channel のパフォーマンス特性
- 无缓冲 channel: 同步,每次操作都涉及 goroutine 切换
- 有缓冲 channel: 异步,goroutine 可直接写入不阻塞(未满时)
- 用 struct{} channel 做信号(不传输数据)
- channel 内部使用锁 + 队列,并非 lock-free

並行処理パフォーマンスのクイックリファレンス

パフォーマンスのスケーラビリティ診断
- 吞吐量不随 CPU 增加而提升 → 可能 serialization bottleneck(锁竞争大)
- 用 pprof mutex profile:go tool pprof -http=:8080 mutex.out
锁选择决策
场景推荐理由
读写均衡sync.Mutex简单高效
读多写少 (≥ 90% 读)sync.RWMutex读可并发
简单计数器sync/atomic无锁,比 Mutex 快 10x+
仅读一次写一次的缓存sync.Map专为此优化
高并发写分片锁减少单个锁竞争
分片锁模式
const numShards = 64

type ShardedMap struct {
    shards [numShards]struct {
        mu sync.RWMutex
        m  map[string]int
    }
}

func (sm *ShardedMap) getShard(key string) *shard {
    h := fnv.New32a()
    h.Write([]byte(key))
    return &sm.shards[h.Sum32()%numShards]
}
Channel vs Mutex
对比维度ChannelMutex
数据所有权传递适合不适合
共享状态保护不适合适合
性能有内存拷贝 + 调度开销较低开销
语义"传递所有权""保护临界区"
死锁风险channel 阻塞等待锁顺序问题

优化工作流实操

完整流程
1. Benchmark → 找到慢的函数
2. pprof CPU → 找到最热的代码行
3. pprof Memory → 找到分配最多的位置
4. 分析原因 → 逃逸?未内联?不必要分配?算法复杂度?
5. 形成假设 → "如果预分配 slice,allocs 应该减少 X%"
6. 实施优化 → 改动最小的一处代码
7. Benchmark 验证 → benchstat 对比统计
8. 如果有效 → 继续下一个热点
9. 如果无效 → 回退,重新分析
ppof 速查
# CPU profile
go test -bench=. -cpuprofile=cpu.out
go tool pprof -http=:8080 cpu.out
go tool pprof -top cpu.out       # 终端 top 20

# Memory profile
go test -bench=. -memprofile=mem.out
go tool pprof -alloc_space mem.out    # 累计分配
go tool pprof -inuse_space mem.out    # 当前使用

# Mutex profile
go test -bench=. -mutexprofile=mutex.out
go tool pprof -http=:8080 mutex.out

# Block profile
go test -bench=. -blockprofile=block.out

# Trace
go test -bench=. -trace=trace.out
go tool trace trace.out

# 在线 profile (运行时)
import _ "net/http/pprof"
go func() { http.ListenAndServe(":6060", nil) }()
# 访问 http://localhost:6060/debug/pprof/
火焰图
go tool pprof -http=:8080 cpu.out
# 在浏览器中: View → Flame Graph

火焰图阅读:横向宽度 = CPU 占用,纵向 = 调用栈深度。找到最宽的函数就是最大热点。


Go 版本关键性能变更

版本性能相关变更
Go 1.12mid-stack inlining,运行时 timer 优化
Go 1.13sync.Pool GC 仅清空 victim,defer 性能优化
Go 1.14defer 零开销(常用路径),goroutine 异步抢占
Go 1.15链接器性能大幅提升,小对象分配优化
Go 1.16//go:embed 编译时嵌入文件
Go 1.17函数参数传递方式变更(寄存器传递),性能提升 5-10%
Go 1.18泛型(无运行时开销),sync.Pool 不再每次 GC 清空
Go 1.19GOMEMLIMIT,排序算法优化 (pdqsort),sync.Map 优化
Go 1.20PGO (Profile-Guided Optimization) 预览,arena 实验性
Go 1.21PGO GA,clear 内置函数,sync.Once variadic,maps 包实验性
Go 1.22range over int,PGO 改进,运行时优化
Go 1.23sync.Map.Clear, atomic.And/Or,结构化日志,unique 包
Go 1.24sync.Map 重写为 hash-trie map
Go 1.25synctest 包,sync.WaitGroup.Go
Go 1.27synctest.Sleep
PGO (Profile-Guided Optimization)
# 1. 收集 profile(生产环境/benchmark)
curl -o default.pgo http://localhost:6060/debug/pprof/profile?seconds=30

# 2. 将 default.pgo 放到 main 包目录

# 3. 构建(自动启用 PGO)
go build -o app

# PGO 对热路径可提升 2-7%,主要改善 inlining 决策和代码布局

性能反模式清单

审查 Go 代码时,逐项检查:

  • slice 在 append 前是否预分配了容量?
  • map 是否预分配了初始大小?
  • 循环中是否使用 strings.Builder 而非 + 拼接?
  • 是否频繁进行 []byte ↔ string 转换(在热路径中)?
  • 小对象是否不需要返回指针(避免逃逸)?
  • 不必要的 interface{} 是否用具体类型替代?
  • struct 字段是否按大小排序以减少 padding?
  • 并发热点是否使用了错误的锁类型(Mutex vs RWMutex)?
  • 是否有 false sharing(多 goroutine 写相邻字段)?
  • sync.Pool 是否用于高频临时对象?
  • 热路径中的 defer 是否可以移除(Go 1.14+ 通常不需要)?
  • 大的 goroutine 泄漏是否存在?
  • 是否用 -race 检测过数据竞争?
  • 是否确认了编译器完成了预期的 BCE 和内联?
  • GC 配置(GOGC/GOMEMLIMIT)是否适合当前负载?
  • 是否启用了 PGO?

快速诊断命令

# 1. 看哪些分配最多
go test -bench=. -benchmem -memprofile=mem.out
go tool pprof -top -alloc_space mem.out

# 2. 看哪些函数 CPU 最热
go test -bench=. -cpuprofile=cpu.out
go tool pprof -top cpu.out

# 3. 看逃逸分析
go build -gcflags="-m" ./... 2>&1 | grep "escapes to heap"

# 4. 看内联决策
go build -gcflags="-m -m" ./... 2>&1 | grep "inlining"

# 5. 看边界检查
go build -gcflags="-d=ssa/check_bce" ./...

# 6. 看 struct 对齐浪费
fieldalignment ./...

# 7. 看数据竞争
go test -race ./...

# 8. 看 GC 行为
GODEBUG=gctrace=1 ./app

# 9. benchstat 统计验证
go test -bench=. -count=10 > /tmp/bench.txt
benchstat /tmp/bench.txt

参考文件

需要更详细信息时加载:

  • references/benchmarking.md — 基准测试编写、benchstat 详解、常见反模式
  • references/memory-optimization.md — 逃逸分析详解、分配减少策略、Pool 最佳实践
  • references/cpu-optimization.md — BCE、内联、编译器 flag、汇编分析
  • references/cache-optimization.md — CPU 缓存、false sharing、struct 布局
  • references/concurrency-perf.md — 锁选择、分片、channel vs mutex、竞争分析
  • references/tooling.md — pprof、trace、fieldalignment、benchstat 完整用法
  • references/version-changes.md — Go 版本间关键性能变更详情
  • references/pgo.md — Profile-Guided Optimization 完整工作流

コメント