当前位置:首页 > 文章列表 > Golang > Go教程 > testing.B.Loop 配合并行基准的结果解读

testing.B.Loop 配合并行基准的结果解读

来源:17golang原创 2026-10-10 17:32:07 0浏览 收藏

把 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与RunParallel职责边界的浅色工程蓝图静态框图
图1:B.Loop 与 RunParallel 的职责边界说明图;串行计时边界和并行工作分配是两组不同语义,这不是运行截图。

串行基准先用最小的 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 数字
并行基准结果字段与并行度旋钮关系的浅色工程蓝图静态框图
图2:并行基准的结果字段与并行度设置关系图;它帮助区分总迭代、墙钟口径和 worker 规模,不代表任何实测性能。

用 -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 配合并行基准就不会变成一组难以解释的数字。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
MCP 工具结果分页与长列表截断的设计MCP 工具结果分页与长列表截断的设计
上一篇
MCP 工具结果分页与长列表截断的设计
Google EnvHarness 开源后 AI 评测沙箱的设计方向
下一篇
Google EnvHarness 开源后 AI 评测沙箱的设计方向
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    406次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    483次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    493次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    437次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    262次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码