如何在 Go 中程序化获取并保存基准测试结果

本文介绍如何使用 go 标准库的 `testing.benchmark` 函数主动调用基准测试,获取 `testing.benchmarkresult` 实例,并将其结构化输出到文件,从而实现对性能数据的自动化采集与持久化。

在 Go 中,默认的 go test -bench 命令仅将基准测试结果打印到标准输出,无法直接编程访问或导出。但 Go 的 testing 包提供了 testing.Benchmark(func(*testing.B)) 函数——它允许你在普通 main 程序中显式执行一个基准函数,并返回一个完整的 testing.BenchmarkResult 结构体,包含所有关键性能指标:N(执行次数)、T(总耗时,纳秒)、Bytes(每操作字节数)、MemAllocs(内存分配次数)和 MemBytes(总分配字节数)等。

以下是一个完整可运行的示例:

package main

import (
    "encoding/json"
    "fmt"
    "os"
    "testing"
    "time"
)

func Add(a, b int) int {
    time.Sleep(10 * time.Microsecond) // 模拟轻微开销,确保可测性
    return a + b
}

func BenchAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = Add(1, 2)
    }
}

func main() {
    // 主动执行基准测试,获取结构化结果
    result := testing.Benchmark(BenchAdd)

    // 打印人类可读摘要(与 go test -bench 输出格式一致)
    fmt.Printf("%-12s %8d %8.0f ns/op\n",
        "BenchAdd", result.N, float64(result.T)/float64(result.N))

    // 输出完整 BenchmarkResult(调试用)
    fmt.Printf("Raw result: %+v\n", result)

    // ✅ 序列化为 JSON 并写入文件(推荐用于后续分析)
    data, err := json.MarshalIndent(result, "", "  ")
    if err != nil {
        panic(err)
    }

    if err := os.WriteFile("benchmark_result.json", data, 0644); err != nil {
        panic(err)
    }
    fmt.Println("✅ Benchmark result saved to benchmark_result.json")
}

运行该程序(go run main.go)将输出类似:

BenchAdd       120000    10000 ns/op
Raw result: {N:120000 T:1200000000 Bytes:0 MemAllocs:0 MemBytes:0 Extra:map[]}
✅ Benchmark result saved to benchmark_result.json

生成的 benchmark_result.json 内容如下(结构清晰、易于 CI/CD 解析):

{
  "N": 120000,
  "T": 1200000000,
  "Bytes": 0,
  "MemAllocs": 0,
  "MemBytes": 0,
  "Extra": {}
}

⚠️ 注意事项:

  • testing.Benchmark 只能在非测试包中调用(即 package main),且不能与 go test 混用;它绕过了测试框架的自动发现机制,适用于需要精确控制执行环境或集成进构建流水线的场景。
  • time.Sleep 仅用于演示;真实基准应避免非必要阻塞,确保测量的是目标逻辑本身。
  • 若需多组对比(如不同参数、版本),可循环调用 testing.Benchmark 并聚合多个 BenchmarkResult。
  • Extra 字段支持通过 b.ReportMetric(value, unit) 注册自定义指标(如 QPS、错误率),增强可观测性。

通过这种方式,你不再依赖终端输出,而是获得可编程、可序列化、可版本化管理的性能数据,为持续性能监控(CPM)打下坚实基础。