testing.B.Loop 配合并行基准的结果解读
来源:17golang原创
时间:2026-10-10 17:32:07 122浏览 收藏
把 testing.B.Loop 加到并行基准里,并不等于把原来的 for range b.N 直接替换掉就结束了。B.Loop 负责一种串行基准循环语义,而 B.RunParallel 负责把总迭代分给多个 worker;两者的计时边界、状态归属和结果口径都不同。写完以后,ns/op 也不能简单理解成某个 goroutine 的单次耗时。
先记住结论:普通基准用 for b.Loop(),并行基准用 b.RunParallel 并在回调中使用 pb.Next()。并行结果里的 b.N 是所有 worker 合计完成的迭代数,ns/op 是整个并行基准的墙钟口径。需要调并行度时,再结合 -cpu 和 SetParallelism 做对照。
官方资料:https://go.dev/blog/testing-b-loop
先区分 B.Loop 和 RunParallel 的职责
B.Loop 的典型用途是把一次基准函数里的准备、被测循环和清理分开。第一次调用 Loop 时,基准计时器开始工作;循环结束后,清理代码不会继续累加到被测时间里。Go 官方文档还说明,写在 for b.Loop() {} 体内的函数调用和结果会被保活,从而降低结果被编译器完全消除的风险。
RunParallel 是另一层语义:它创建多个 goroutine,把 b.N 次迭代分配给这些 goroutine。回调接收的是 *testing.PB,每个 worker 都要通过 pb.Next() 判断自己是否还有工作。不要在这个回调里调用 StartTimer、StopTimer 或 ResetTimer,这些方法对整个基准有全局影响。

串行基准先用最小的 B.Loop 写法
假设被测对象是把一段固定文本解析为模板。输入可以在第一次进入循环前准备好,这部分不应混进每次被测操作;循环内部只保留真正想比较的调用。
package bench
import (
"io"
"testing"
"text/template"
)
func BenchmarkTemplateExecute(b *testing.B) {
// 固定输入只准备一次,避免把模板解析成本混入 Execute 的测量。
tpl := template.Must(template.New("hello").Parse("hello, {{.}}"))
for b.Loop() {
// B.Loop 会让循环体内的调用结果保持可见,便于测量真实调用。
// 使用 io.Discard 丢弃输出,避免 nil Writer 导致运行时错误。
if err := tpl.Execute(io.Discard, "Go"); err != nil {
b.Fatal(err)
}
}
}
示例的重点是结构而不是业务输出:准备工作在循环外,测量目标在 Loop 体内。实际代码中应把输出写入合适的缓冲区并处理错误;如果每轮确实需要准备输入,也要把那部分明确算入或排除,不能靠猜。
并行基准要把状态放到 worker 内
并行基准最容易出现的错误,是把一个会被多个 goroutine 同时修改的缓冲区、计数器或临时对象放在回调外。这样测到的可能是锁竞争、数据竞争或共享内存抖动,而不是目标函数本身。
package bench
import (
"bytes"
"testing"
"text/template"
)
func BenchmarkTemplateExecuteParallel(b *testing.B) {
// 模板只读共享即可;解析成本不应放进每次迭代。
tpl := template.Must(template.New("hello").Parse("hello, {{.}}"))
b.RunParallel(func(pb *testing.PB) {
// 每个 worker 独占自己的缓冲区,避免无关锁竞争和数据竞争。
var buf bytes.Buffer
for pb.Next() {
buf.Reset()
// 错误不能被静默吞掉;真实项目可在循环外汇总状态。
if err := tpl.Execute(&buf, "Go"); err != nil {
b.Fatal(err)
}
}
})
}
这里没有在 RunParallel 外再套一层 for b.Loop()。两种循环代表不同的基准驱动方式,官方文档要求二选一。pb.Next() 返回 false 后,当前 worker 结束;所有 worker 合起来才完成这次基准的 b.N 次迭代。
并行结果到底应该怎么看
基准输出通常包含名称、迭代次数和 ns/op。在并行场景里,迭代次数仍然是所有 worker 的总和,ns/op 表示整个并行基准完成每次总迭代的平均墙钟成本。它不是把每个 worker 的 CPU 时间相加,也不是某一条 goroutine 的独立延迟。
| 字段或旋钮 | 它表达什么 | 不要怎样解读 |
|---|---|---|
b.N | 本轮基准由所有 worker 合计执行的迭代数 | 不要当成单个 worker 的工作量 |
ns/op | 整个并行基准的墙钟平均值 | 不要与单 goroutine 延迟直接等同 |
GOMAXPROCS | 运行时可同时使用的逻辑处理资源上限 | 不要当成业务吞吐保证 |
SetParallelism(p) | 把 RunParallel 的 worker 规模设置为约 p * GOMAXPROCS | 不要把 p 当成最终 goroutine 数字 |

用 -cpu 和 SetParallelism 做可比对照
如果目标是观察并发度变化,不要只运行一次然后根据一个数字下结论。可以先固定代码,再改变 -cpu;只有当被测工作适合超出 CPU 数量的并发,才考虑 SetParallelism。
# 用多个 GOMAXPROCS 档位运行同一个并行基准,便于比较趋势。 go test -run '^$' -bench 'BenchmarkTemplateExecuteParallel$' -benchmem -cpu=1,2,4 # 对网络等待或其他非纯 CPU 工作,再显式扩大 RunParallel 的 worker 规模。 # p 是相对 GOMAXPROCS 的倍数,不是固定的 goroutine 总数。 go test -run '^$' -bench 'BenchmarkTemplateExecuteParallel$' -cpu=2
命令只负责产生对照数据,解释结果时还要记录机器、Go 版本、输入规模和是否存在共享资源。若 ns/op 随并行度下降,可能说明资源利用率提高;如果不再下降甚至升高,则要进一步看锁、分配、缓存、调度和外部依赖,而不能单凭 worker 数量判断“并发越高越快”。
四个常见误区
把 B.Loop 和 b.N 循环混在一起
一个基准函数应选择一种驱动方式。迁移到 B.Loop 后删除旧的显式 b.N 循环;如果要并行,则改成 RunParallel 和 PB.Next 的组合。
把准备成本偷偷移进循环
每轮重新解析模板、构造大对象或生成随机输入,会让结果回答另一个问题。准备成本有意属于被测任务时可以放进循环,但必须在标题、说明和比较组中保持一致。
让所有 worker 共享可变状态
共享状态会把同步成本带进结果。优先使用只读共享输入和 worker 本地的缓冲区;必须共享时,再明确锁或原子操作本身就是被测对象。
把一次输出当成性能结论
基准结果需要在相同环境下做多次对照,必要时使用专门的基准比较工具分析差异。本文的重点是读懂字段语义,不提供脱离环境的性能数字。
最后的速查结论
- 串行基准:准备输入后使用
for b.Loop(),让计时边界和循环体语义清晰。 - 并行基准:使用
b.RunParallel,在回调中用pb.Next()取得工作,不再嵌套b.Loop。 - 结果解读:
b.N是所有 worker 的合计迭代数,ns/op是整个并行基准的墙钟口径。 - 并行度对照:先用
-cpu控制GOMAXPROCS,再按工作类型考虑SetParallelism。
只要把“循环谁负责”“状态放在哪里”“结果按什么口径计时”这三个问题分开,testing.B.Loop 配合并行基准就不会变成一组难以解释的数字。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习