Go 1.24 新方法:编写性能测试用例方法 testing.B.Loop 介绍

Go 开发者在使用 testing包编写基准测试用例时,如果不注意,可能会遇到各种陷阱。这些陷阱,导致基准测试结果不准确。Go1.24 版本引入了一种新的基准测试编写方式,它同样易用,并且可以帮助规避编写基准测试时的一些坑。

Go 1.24 版本推荐使用 testing.B.Loop代替 testing.B.N来编写基准测试用例。

Go1.24 版本前,我们使用 b.N 来编写基准测试用例,例如:

go 复制代码
func Benchmark(b *testing.B) {
  for range b.N {
    ... 要测量的代码 ...
  }
}

改用b.Loop仅需要微不足道的改动:

go 复制代码
func Benchmark(b *testing.B) {
  for b.Loop() {
    ... 要测量的代码 ...
  }
}

testing.B.Loop有很多优点:

  • 可以防止基准测试循环内的不当编译优化;
  • 可以自动将设置和清理部分代码耗时从基准测试时间统计中剔除;
  • 代码不应意外地依赖于总迭代次数或当前迭代。

上述这些优点都是在使用 b.N编写基准测试代码时易犯的错误,这些错误会导致基准测试不准确。除了上述优点之外,b.Loop风格的基准测试,还能再更短的时间执行完。

接下来,我们来看下testing.B.Loop的优势以及如何有效地使用它。

旧基准测试循环问题

在 Go 1.24 之前,大部分的基准测试用例结构简单。但是复杂的测试用例,却需要很小心的编写:

go 复制代码
func Benchmark(b *testing.B) {
  ... setup ...
  b.ResetTimer() // 如果设置可能很昂贵
  for range b.N {
    ... 代码测量 ...
    ... 使用汇点或累积防止未用代码消除 ...
  }
  b.StopTimer() // 如果清理或报告可能很昂贵
  ... 清理 ...
  ... 报告 ...
}

如果设置 (setup)或清理 (cleanup)逻辑复杂,耗时较久,为了避免这些准备性的逻辑参与到核心代码的耗时统计中,需要使用ResetTimerStopTimer 方法,将这些时间剔除掉。但是真正开发的过程中, 可能有一些开发者会遗漏这些逻辑。即使开发者没有遗漏,也很难判断设置或清理过程是否"足够耗时"到需要使用它们。

还有一个更微妙的陷阱,需要更深入的理解(示例源代码):

go 复制代码
func isCond(b byte) bool {
  if b%3 == 1 && b%7 == 2 && b%17 == 11 && b%31 == 9 {
    return true
  }
  return false
}

func BenchmarkIsCondWrong(b *testing.B) {
  for range b.N {
    isCond(201)
  }
}

在这个例子中,用户可能会观察到 isCond 在亚纳秒级别的时间内执行。CPU 的速度很快,但并没有快到这个程度!这个看似异常的结果源于 isCond 被内联处理,并且由于其结果从未被使用,编译器将其视为无效代码而进行消除。

因此,这个基准测试根本没有测量 isCond。它测量的是进行无操作所需的时间。在这种情况下,亚纳秒的结果是一个明显的警示,但在更复杂的基准测试中,部分无效代码消除可能导致看起来合理但实际上并未测量预期内容的结果。

testing.B.Loop可以带来哪些好处?

b.N 风格的基准测试不同,testing.B.Loop 能够跟踪其首次调用时间以及基准测试的最终迭代结束时刻。循环开始时的 b.ResetTimer 和结束时的 b.StopTimer 被整合进 testing.B.Loop,消除了手动管理基准测试计时器以进行初始化和清理代码的开发步骤。

另外,Go 编译器现在可以探测到只调用 testing.B.Loop 时的循环,并阻止 testing.B.Loop 内的代码死循环。

testing.B.Loop 的另一个优点是其一次性快速提升的方法。对于 b.N 风格的基准测试,测试包必须多次调用基准测试函数,并使用不同的 b.N 值逐步增加,直到测量时间达到一个阈值。相比之下,b.Loop 可以简单地运行基准测试循环,直到达到时间阈值,只需调用基准测试函数一次。

b.N 风格循环的某些限制仍适用于 b.Loop 风格的循环。用户仍需在必要时负责在基准测试循环中管理计时器。(示例源

go 复制代码
func BenchmarkSortInts(b *testing.B) {
  ints := make([]int, N)
  for b.Loop() {
    b.StopTimer()
    fillRandomInts(ints)
    b.StartTimer()
    slices.Sort(ints)
  }
}

在这个例子中,为了测试 slices.Sort 方法的就地排序性能,每次迭代都需要一个随机初始化的数组。开发菏泽仍需在这些情况下手动管理计时器。

此外,基准测试函数体中仍需要只有一个这样的循环(b.N风格循环不能与b.Loop风格循环共存),并且循环的每次迭代应该做相同的事情。

何时使用

testing.B.Loop 方法,是从 Go 1.24 版本起,编写基准测试的首选方式。使用示例如下:

go 复制代码
func Benchmark(b *testing.B) {
  ... 设置 ...
  for b.Loop() {
    // 可选的循环内部设置/清理计时器控制
    ... 要测量的代码 ...
  }
  ... 清理 ...
}
  • 知识星球:令飞编程。10+ 高质量体系课( Go、云原生、AI Infra)、15+ 高质量实战项目,P8 技术专家助你提高技术天花板,冲击百万年薪!
  • 我公众号:令飞编程,分享 Go、云原生、AI Infra 相关技术。回复「资料」免费下载 Go、云原生、AI 等学习资料;
  • 哔哩哔哩:令飞编程 ,以视频、直播的形式,分享技术、职场、课程、面经等;
相关推荐
霸道流氓气质11 分钟前
Spring AI异常处理与重试机制
java·人工智能·spring
在所不辞兄19 分钟前
【零基础学智能仿真-07】随机森林——用多棵树获得更稳定的力学预测
人工智能·深度学习·神经网络·机器学习·工程技术
Tom·Ge1 小时前
【AI前沿】2026.08.17 SpaceX 600亿收购Cursor正式完成·DeepSeek V4 Pro万亿开源·人形机器人生态全面爆发
人工智能·大模型·ai前沿
郑州光合科技余经理2 小时前
本地生活服务系统:成品模块和定制接口怎么划界
java·前端·人工智能·后端·系统架构·php·ai编程
2603_965148112 小时前
家居百货蓝海:API挖掘高复购率生活小商品
大数据·服务器·人工智能·python·生活
2501_930472442 小时前
踩坑|CodeBuddy权限配置:AI误删文件、乱跑命令、.env泄露怎么防
人工智能·ai编程
小淮AI5 小时前
从“刷题”到“追问”:AI课堂正在重塑哪些学习旧习惯?
人工智能·学习
cui_ruicheng6 小时前
LangChain 应用开发(十四):Agent 上下文与记忆机制
服务器·人工智能·python·langchain
飞哥数智坊6 小时前
直播半小时,我聊了聊 Agent 最常见的 3 个疑问
人工智能
ggb喔7 小时前
AI 原生攻防时代:2026 年渗透测试与逆向开发的范式重构
人工智能·重构