基准测试必须模拟真实服务路径,端到端测量链路瓶颈而非单函数;需启用生产级中间件、真实数据库、合理并发与持续时间,并结合-benchmem和pprof分析内存与CPU,用b.Run隔离变量并独立初始化资源。

基准测试必须跑在真实服务路径上
直接测单个函数没用,微服务的性能瓶颈往往藏在链路里:gRPC序列化、中间件耗时、数据库连接池争抢。比如你写了个 BenchmarkJSONMarshal,结果很好,但线上慢的其实是 grpc.Server.ServeHTTP 里的反射调用或 TLS 握手开销。
实操建议:
- 用
benchmark/client/main.go和benchmark/server/main.go模拟端到端调用,而不是只测 handler 内部逻辑 - 确保测试环境启用了和生产一致的中间件(如 auth、logging、tracing),否则会漏掉可观测性组件的开销
- 数据库连接必须走真实实例(Docker Compose 启的
mongodb或postgres),不能 mock 或内存 SQLite —— 连接池行为、网络延迟、事务锁都会影响结果
别忽略 -benchmem 和 -cpuprofile 的组合使用
只看 ns/op 容易误判。比如某个优化让耗时下降 20%,但 allocs/op 从 3 升到 15,说明 GC 压力正在悄悄上涨,高并发下反而更慢。
实操建议:
- 每次基准测试都加
-benchmem,重点关注B/op和allocs/op是否同步改善 - 发现内存异常时,立刻补上
-cpuprofile=cpu.pprof和-memprofile=mem.pprof,用go tool pprof查具体哪行在频繁 new 对象 - 注意
b.ResetTimer()的位置:初始化 DB 连接、编解码器等必须放在这之前,否则把 setup 时间也算进 benchmark 了
并发度(-c)和测试时长(-d)要匹配业务特征
./run_bench.sh -c 100 跑出来的数据,对 QPS 500 的服务有意义;但对长连接、低频高负载的 IoT 网关就失真了——它可能根本压不到连接池上限,也测不出 keepalive 配置的真实效果。
实操建议:
- 先查线上监控(如 Prometheus 的
grpc_server_handled_total),确定典型并发请求数,再设-c值 - 对短连接服务(如 HTTP API),
-d至少 60 秒,避免冷启动抖动干扰;对长连接服务(如 gRPC streaming),要测连接复用率,得观察 5 分钟以上内存增长趋势 - GoFr 或 rpcx 用户特别注意:
DB_MAX_OPEN_CONNECTION和grpc.KeepaliveParams.MaxConnectionAge这类配置,必须在 benchmark 中显式设置并验证是否生效,光改 config 文件不等于 runtime 生效
子基准测试(b.Run)是定位差异的关键
当你对比两种序列化方案(Protobuf vs JSON)、两个数据库驱动(pq vs pgx),或者不同连接池大小的效果时,用一个 Benchmark 函数硬塞所有逻辑,结果根本没法归因。
实操建议:
- 用
b.Run("pgx_pool_16", func(b *testing.B){...})显式命名每个变体,输出结果一目了然 - 每个子测试里独立初始化资源(如新 dial 一个 DB 连接池),避免互相污染
- 如果某个子测试 panic 或超时,其他子测试仍会继续执行 —— 这比拆成多个文件更高效,也方便做 CI 自动对比
runtime.mallocgc,但根源可能是某处没复用 bytes.Buffer;trace 里看到大量 goroutine 阻塞在 net.(*pollDesc).wait,实际是 DNS 解析没配 timeout。基准测试只是起点,不是终点。



















