Go 的 go test -bench 默认仅输出总耗时(ns/op),但可通过手动调用 testing.Benchmark 获取 BenchmarkResult,进而计算并打印自定义指标(如单位元素耗时 ns/op/S)。
go 的 `go test -bench` 默认仅输出总耗时(ns/op),但可通过手动调用 `testing.benchmark` 获取 `benchmarkresult`,进而计算并打印自定义指标(如单位元素耗时 ns/op/s)。
在标准 go test -bench 流程中,测试框架负责驱动循环、计时和格式化输出,但不提供对原始基准结果的直接访问——因此无法在默认报告中添加 (ns/op)/S 这类衍生指标。不过,Go 标准库明确暴露了 testing.Benchmark 函数,它接受一个 func(*testing.B) 并返回完整的 testing.BenchmarkResult 结构体,其中包含总耗时 r.T(纳秒)与执行次数 r.N,从而可精确计算任意派生指标。
要实现每元素耗时(即 ns/op / S),推荐将基准逻辑封装为独立函数,并在 main 包中调用 testing.Benchmark:
package main
import (
"fmt"
"testing"
)
// 假设的辅助函数(需按实际替换)
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) {
r := testing.Benchmark(func(b *testing.B) {
benchmarkMy(size, b)
})
totalNanos := int64(r.T)
ops := int64(r.N)
nsPerOp := totalNanos / ops
nsPerItem := nsPerOp / int64(size)
fmt.Printf("BenchmarkMy%d:\t%8d ns/op\t%8d ns/op/item\n", size, nsPerOp, nsPerItem)
}
func main() {
sizes := []int{10, 20, 30, 40, 50, 1000, 10000, 100000}
for _, s := range sizes {
runCustomBenchmark(s)
}
}运行方式为 go run main.go(不能使用 go test),输出示例:
BenchmarkMy10: 15 ns/op 1 ns/op/item BenchmarkMy20: 33 ns/op 1 ns/op/item BenchmarkMy30: 43 ns/op 1 ns/op/item ...
⚠️ 注意事项:
- 此方法绕过了 go test 的自动发现与并发控制,需自行管理基准参数;
- testing.Benchmark 内部会执行至少一次预热运行,确保结果稳定,但建议仍开启 -benchmem 或手动验证内存分配是否影响性能;
- 若需与 CI/CD 集成或保留 go test 生态(如 -benchmem, -cpuprofile),可编写简单 shell 脚本解析 go test -bench 原始输出并计算 ns/op/S,但纯 Go 方案更可控、可复用;
- 所有 benchmarkMy 逻辑必须保持无副作用且可重复执行,b.ResetTimer() 的位置需严格置于初始化之后、主循环之前。
通过该方案,你不仅能获得 ns/op/S,还可扩展计算吞吐量(items/sec)、线性拟合斜率等高级分析指标,真正实现基准测试结果的灵活定制。

















