
Go 内置 go test -bench 不支持直接输出自定义计算指标(如 ns/op/S),但可通过手动调用 testing.Benchmark 获取 BenchmarkResult,再结合输入参数进行二次计算,实现灵活、可编程的基准结果输出。
go 内置 `go test -bench` 不支持直接输出自定义计算指标(如 `ns/op/s`),但可通过手动调用 `testing.benchmark` 获取 `benchmarkresult`,再结合输入参数进行二次计算,实现灵活、可编程的基准结果输出。
在 Go 的标准测试框架中,go test -bench=. 提供的默认输出(如 15.7 ns/op)仅反映单次操作的平均耗时,无法直接体现算法的单位规模开销(例如每项元素的处理时间)。当你的基准测试按不同数据规模(如 My10、My1000)组织时,真正关心的往往是 ns/op ÷ S —— 即每个列表元素的平均纳秒开销,这对分析算法时间复杂度(如是否线性、是否存在常数级额外开销)至关重要。
幸运的是,Go 的 testing 包提供了底层接口 testing.Benchmark,它接受一个 func(*B) 并返回完整的 testing.BenchmarkResult 结构体,其中包含精确的总耗时 r.T(time.Duration)和运行次数 r.N。这使你完全掌控结果计算与格式化逻辑。
以下是一个可直接运行的自定义基准示例(需保存为独立 main.go,用 go run 执行):
package main
import (
"fmt"
"testing"
)
// 模拟你的实际被测函数:对长度为 size 的列表执行操作
func MakeList(size int) []int {
list := make([]int, size)
for i := range list {
list[i] = i
}
return list
}
func My(list []int) int {
sum := 0
for _, v := range list {
sum += v
}
return sum
}
func benchmarkMy(size int, b *testing.B) {
mylist := MakeList(size)
b.ResetTimer()
for n := 0; n < b.N; n++ {
_ = My(mylist)
}
}
func runCustomBenchmark(size int) {
result := testing.Benchmark(func(b *testing.B) {
benchmarkMy(size, b)
})
nsPerOp := int(result.T) / result.N
nsPerItem := nsPerOp / size
fmt.Printf("BenchmarkMy%d:\t%d ns/op\t%d ns/op/i\n", size, nsPerOp, nsPerItem)
}
func main() {
for _, s := range []int{10, 20, 30, 40, 50, 1000, 10000, 100000} {
runCustomBenchmark(s)
}
}运行 go run main.go 将输出类似:
BenchmarkMy10: 15 ns/op 1 ns/op/i BenchmarkMy20: 33 ns/op 1 ns/op/i BenchmarkMy30: 43 ns/op 1 ns/op/i ...
⚠️ 注意事项:
- 此方式不能与 go test 混用:testing.Benchmark 必须在非测试包中调用(如 main 包),且需通过 go run 启动,因为 go test 会接管并重写 *testing.B 生命周期;
- b.ResetTimer() 仍需保留在被测函数内,确保仅测量核心逻辑;
- 若需统计多个迭代或误差分析,可多次调用 testing.Benchmark 并聚合 result.T 和 result.N;
- 对于生产级基准报告,建议进一步封装为 CLI 工具,支持 CSV 输出或图表生成。
通过这种显式调用 + 自定义格式的方式,你不再依赖外部工具(如 Excel)做后处理,而是将性能洞察直接嵌入构建流程,让规模归一化指标成为可复现、可脚本化的第一等公民。

















