文章

阶段4 性能与工程化 - 代码实例集

阶段 4 性能与工程化 · 代码实例集

覆盖:pprof / trace / 基准测试 / 逃逸分析 / GC 调优 / 表驱动测试 / fuzz / CI。 配合 04-生产实践/*.md03-标准库/10-运行时与调试.md 食用。


1. pprof(CPU / 内存剖析)

1.1 通过 net/http/pprof 采集(最常用)

import (
	"net/http"
	_ "net/http/pprof" // 匿名导入,注册 /debug/pprof/*
)

func main() {
	go func() { http.ListenAndServe(":6060", nil) }() // pprof 端口
	// ... 你的业务
}

采集命令:

# CPU 30 秒
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# 堆内存
go tool pprof http://localhost:6060/debug/pprof/heap
# 进入交互后:top / web / list 函数名

1.2 在测试里采集

func BenchmarkHandler(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = handleRequest()
	}
}
# go test -bench=. -cpuprofile=cpu.out -memprofile=mem.out
# go tool pprof cpu.out

2. trace(调度 / 阻塞可视化)

import (
	"os"
	"runtime/trace"
)

f, _ := os.Create("trace.out")
trace.Start(f)
defer trace.Stop()
// ... 跑业务
// 查看:go tool trace trace.out

用于看 goroutine 阻塞、GC 停顿、syscall 耗时。


3. 基准测试 + benchstat(对比优化效果)

func BenchmarkConcat(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = fmt.Sprintf("%d-%d", i, i)
	}
}
// 跑两次取均值对比
# go test -bench=. -benchmem
# benchstat old.txt new.txt   # 显示优化前后差异

-benchmem 显示每次操作分配的内存(B/op)和分配次数(allocs/op)。


4. 逃逸分析(确定变量分配在栈还是堆)

go build -gcflags="-m" main.go
# 输出 "moved to heap" 表示该变量逃逸到堆(增加 GC 压力)

优化方向:避免不必要的指针传递、减少闭包捕获大对象、复用对象(sync.Pool)。


5. GC 调优

GOGC=50 ./app   # 堆增长 50% 就触发 GC(默认 100,越小越频繁但内存占用低)
# 或 GOMEMLIMIT=512MiB 设置软内存上限(Go 1.19+)

原则:不要盲目调小 GOGC,先 profiling 确认是 GC 瓶颈。


6. 表驱动测试(工程化标配)

func TestParse(t *testing.T) {
	cases := []struct {
		name string
		in   string
		want int
	}{
		{"normal", "123", 123},
		{"zero", "0", 0},
		{"neg", "-5", -5},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			if got := parse(c.in); got != c.want {
				t.Errorf("parse(%q)=%d, want %d", c.in, got, c.want)
			}
		})
	}
}

7. fuzz 测试(模糊测试,Go 1.18+)

func FuzzParse(f *testing.F) {
	f.Add("123")          // 种子语料
	f.Add("-5")
	f.Fuzz(func(t *testing.T, s string) {
		parse(s)           // 只要求不 panic / 不崩溃
		// 可加不变量断言:如 parse 不会 panic
	})
}
// go test -fuzz=FuzzParse -fuzztime=30s

8. CI 集成(GitLab CI 示例)

# .gitlab-ci.yml
test:
  image: golang:1.22
  script:
    - go vet ./...
    - go test -race -coverprofile=coverage.out ./...
    - go test -bench=. -benchmem
  coverage: '/coverage: \d+\.\d+%/'

关键检查:

  • go vet 静态检查
  • go test -race 数据竞争
  • go test -cover 覆盖率
  • 基准测试防止性能回归

9. 性能优化 checklist

  • 用 pprof 找到真正瓶颈(别猜)
  • 高频路径减少堆分配(逃逸分析 + sync.Pool)
  • 锁粒度细化(RWMutex / atomic 替代 Mutex)
  • 字符串拼接用 strings.Builder
  • 预分配 slice/map 容量(make([]T, 0, n)
  • 避免不必要的接口装箱

自测题

  1. 怎么用 pprof 采集 30 秒 CPU profile?
  2. go test -race 发现 DATA RACE,根因通常是什么?怎么修?
  3. 逃逸分析命令是什么?-m 输出里的 “moved to heap” 意味着什么?
  4. fuzz 测试和单元测试的目标区别是什么?
  5. 为什么生产 CI 要跑 -race

实战建议:拿 care-mate 的某个 handler 做一次真实 pprof + 表驱动测试,最有收获。卡住就问。