基准测试 #

基准测试(Benchmark)用于测量代码的性能。Go语言的 testing 包内置了基准测试支持,可以方便地进行性能分析。

基本基准测试 #

编写基准测试 #

基准测试函数以 Benchmark 开头,参数为 *testing.B

go
package myapp

import "testing"

func Fibonacci(n int) int {
    if n <= 1 {
        return n
    }
    return Fibonacci(n-1) + Fibonacci(n-2)
}

func BenchmarkFibonacci(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Fibonacci(20)
    }
}

运行基准测试 #

bash
go test -bench=.
go test -bench=Fibonacci
go test -bench=. -benchmem

输出示例:

text
BenchmarkFibonacci-8          30000             45123 ns/op
  • 30000 - 运行次数
  • 45123 ns/op - 每次操作耗时(纳秒)

理解 b.N #

b.N 是基准测试框架自动调整的迭代次数,确保测试运行足够长的时间:

go
package myapp

import "testing"

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

框架会自动增加 b.N 直到获得稳定的测量结果。

基准测试选项 #

控制运行时间 #

bash
go test -bench=. -benchtime=5s
go test -bench=. -benchtime=100x

内存分析 #

bash
go test -bench=. -benchmem

输出示例:

text
BenchmarkMakeSlice-8    1000000    1234 ns/op    1024 B/op    1 allocs/op
  • 1024 B/op - 每次操作分配的内存
  • 1 allocs/op - 每次操作的内存分配次数

子基准测试 #

go
package myapp

import (
    "sort"
    "testing"
)

func BenchmarkSort(b *testing.B) {
    b.Run("Small", func(b *testing.B) {
        data := make([]int, 10)
        for i := 0; i < b.N; i++ {
            sort.Ints(data)
        }
    })
    
    b.Run("Medium", func(b *testing.B) {
        data := make([]int, 1000)
        for i := 0; i < b.N; i++ {
            sort.Ints(data)
        }
    })
    
    b.Run("Large", func(b *testing.B) {
        data := make([]int, 100000)
        for i := 0; i < b.N; i++ {
            sort.Ints(data)
        }
    })
}

重置计时器 #

当基准测试需要准备数据时,使用 ResetTimer 排除准备时间:

go
package myapp

import "testing"

func BenchmarkWithSetup(b *testing.B) {
    data := make([]int, 1000000)
    for i := range data {
        data[i] = i
    }
    
    b.ResetTimer()
    
    for i := 0; i < b.N; i++ {
        process(data)
    }
}

停止和恢复计时器 #

go
package myapp

import "testing"

func BenchmarkStopTimer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        b.StopTimer()
        data := prepareData()
        b.StartTimer()
        
        process(data)
    }
}

并行基准测试 #

使用 RunParallel 测试并发性能:

go
package myapp

import (
    "sync"
    "testing"
)

var pool = sync.Pool{
    New: func() interface{} {
        return make([]byte, 1024)
    },
}

func BenchmarkPool(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            buf := pool.Get().([]byte)
            processBuffer(buf)
            pool.Put(buf)
        }
    })
}

控制并发数 #

bash
go test -bench=. -cpu=1,2,4,8

内存分配优化 #

比较不同实现 #

go
package myapp

import (
    "strings"
    "testing"
)

func concatPlus(n int) string {
    var s string
    for i := 0; i < n; i++ {
        s += "x"
    }
    return s
}

func concatBuilder(n int) string {
    var builder strings.Builder
    for i := 0; i < n; i++ {
        builder.WriteString("x")
    }
    return builder.String()
}

func BenchmarkConcatPlus(b *testing.B) {
    for i := 0; i < b.N; i++ {
        concatPlus(1000)
    }
}

func BenchmarkConcatBuilder(b *testing.B) {
    for i := 0; i < b.N; i++ {
        concatBuilder(1000)
    }
}

运行结果:

text
BenchmarkConcatPlus-8           5000    280000 ns/op    528384 B/op    999 allocs/op
BenchmarkConcatBuilder-8      500000       3200 ns/op       512 B/op      1 allocs/op

预分配优化 #

go
package myapp

import "testing"

func makeSliceWithoutCap(n int) []int {
    var result []int
    for i := 0; i < n; i++ {
        result = append(result, i)
    }
    return result
}

func makeSliceWithCap(n int) []int {
    result := make([]int, 0, n)
    for i := 0; i < n; i++ {
        result = append(result, i)
    }
    return result
}

func BenchmarkSliceWithoutCap(b *testing.B) {
    for i := 0; i < b.N; i++ {
        makeSliceWithoutCap(10000)
    }
}

func BenchmarkSliceWithCap(b *testing.B) {
    for i := 0; i < b.N; i++ {
        makeSliceWithCap(10000)
    }
}

比较基准测试结果 #

使用 benchstat 工具比较不同版本的性能:

bash
go install golang.org/x/perf/cmd/benchstat@latest

go test -bench=. -count=5 > old.txt
go test -bench=. -count=5 > new.txt

benchstat old.txt new.txt

实用示例:JSON处理性能 #

go
package myapp

import (
    "encoding/json"
    "testing"
)

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

var userJSON = []byte(`{"id":1,"name":"张三","email":"zhangsan@example.com"}`)

func BenchmarkJSONUnmarshal(b *testing.B) {
    var user User
    for i := 0; i < b.N; i++ {
        json.Unmarshal(userJSON, &user)
    }
}

func BenchmarkJSONMarshal(b *testing.B) {
    user := User{ID: 1, Name: "张三", Email: "zhangsan@example.com"}
    for i := 0; i < b.N; i++ {
        json.Marshal(user)
    }
}

实用示例:Map性能对比 #

go
package myapp

import (
    "sync"
    "testing"
)

func BenchmarkSyncMap(b *testing.B) {
    var m sync.Map
    
    b.RunParallel(func(pb *testing.PB) {
        i := 0
        for pb.Next() {
            key := string(rune(i % 100))
            m.Store(key, i)
            m.Load(key)
            i++
        }
    })
}

func BenchmarkMutexMap(b *testing.B) {
    m := make(map[string]int)
    var mu sync.Mutex
    
    b.RunParallel(func(pb *testing.PB) {
        i := 0
        for pb.Next() {
            key := string(rune(i % 100))
            mu.Lock()
            m[key] = i
            _ = m[key]
            mu.Unlock()
            i++
        }
    })
}

小结 #

命令 说明
go test -bench=. 运行所有基准测试
go test -benchmem 显示内存分配信息
go test -benchtime=5s 设置运行时间
go test -cpu=1,2,4 测试不同CPU核心数
b.ResetTimer() 重置计时器
b.RunParallel() 并行基准测试

基准测试是性能优化的基础,通过测量可以找到真正的性能瓶颈,避免过早优化。