Go进阶之性能测试原理

0 阅读4分钟

1.数据结构:

源码位置:src/testing/benchmark.go:B

type B struct {
    common
    importPath       string // import path of the package containing the benchmark
    bstate           *benchState
    N                int
    previousN        int           // number of iterations in the previous run
    previousDuration time.Duration // total duration of the previous run
    benchFunc        func(b *B)
    benchTime        durationOrCountFlag
    bytes            int64
    missingBytes     bool // one of the subbenchmarks does not have bytes set.
    timerOn          bool
    showAllocResult  bool
    result           BenchmarkResult
    parallelism      int // RunParallel creates parallelism*GOMAXPROCS goroutines
    // The initial states of memStats.Mallocs and memStats.TotalAlloc.
    startAllocs uint64
    startBytes  uint64
    // The net total of this test after being run.
    netAllocs uint64
    netBytes  uint64
    // Extra metrics collected by ReportMetric.
    extra map[string]float64
    // For Loop() to be executed in benchFunc.
    // Loop() has its own control logic that skips the loop scaling.
    // See issue #61515.
    loopN int
}

1).基础标识与调度:

importPath:包导入路径.

bstate:状态测试管理器.

benchFunc:测试函数.

2).循环控制:

N:循环执行次数.

previousN:上一轮循环次数.

loopN:loop专用循环数.

3).耗时统计:

benchTime:测试目标耗时. 控制基准测试的最小总耗时(默认 1s,可通过 -

benchtime 调整,如 -benchtime=5s -benchtime=100x )

previousDuration:上一轮总耗时.

timerOn:计时器开关.

4).内存或字节统计:

bytes:每次迭代处理的字节数.

missingBytes:测试字节数缺失标记.

startAllocs:内存分布次数初始值.

startBytes:内存分配字节初始值.

netAllocs:总分配次数.

netBytes:总分配字节数.

5).并行控制:

parallelism:并行度.

6).结果与输出:

result:基准测试结果.

showAllocResult:内存结果显示标记.

extra:自定义指标.

7).通用能力:

common:通用测试能力.

2.启动计时.B.StartTimer():

源码位置:src/testing/benchmark.go

func (b *B) StartTimer() {
    if !b.timerOn {
       runtime.ReadMemStats(&memStats)
       b.startAllocs = memStats.Mallocs
       b.startBytes = memStats.TotalAlloc
       b.start = highPrecisionTimeNow()
       b.timerOn = true
    }
}

StartTimer()负责启动计时并初始化内存相关计数.测试执行时会自动调用.一般不需

要用户启动.

流程图:

3.停止计时.B.StopTimer():

源码位置:src/testing/benchmark.go

StopTimer()负责停止计时.并累加相应的值.

// StopTimer stops timing a test. This can be used to pause the timer
// while performing steps that you don't want to measure.
func (b *B) StopTimer() {
    if b.timerOn {
       b.duration += highPrecisionTimeSince(b.start)
       runtime.ReadMemStats(&memStats)
       b.netAllocs += memStats.Mallocs - b.startAllocs
       b.netBytes += memStats.TotalAlloc - b.startBytes
       b.timerOn = false
    }
}

需要注意的是.StopTimer()并不一定是测试结束.一个测试中有可能有多个统计阶

段.所以统计值是累加的.

流程图:

4.重置计时.B.ResetTimer():

ResetTimer()用于重置计时器.相应的也会把其他统计值也重置.

源码位置:src/testing/benchmark.go

// ResetTimer zeroes the elapsed benchmark time and memory allocation counters
// and deletes user-reported metrics.
// It does not affect whether the timer is running.
func (b *B) ResetTimer() {
    if b.extra == nil {
       // Allocate the extra map before reading memory stats.
       // Pre-size it to make more allocation unlikely.
       b.extra = make(map[string]float64, 16)
    } else {
       clear(b.extra)
    }
    if b.timerOn {
       runtime.ReadMemStats(&memStats)
       b.startAllocs = memStats.Mallocs
       b.startBytes = memStats.TotalAlloc
       b.start = highPrecisionTimeNow()
    }
    b.duration = 0
    b.netAllocs = 0
    b.netBytes = 0
}

流程图:

5.SetBytes():

源码位置:src/testing/benchmark.go

// SetBytes records the number of bytes processed in a single operation.
// If this is called, the benchmark will report ns/op and MB/s.
func (b *B) SetBytes(n int64) { b.bytes = n }

流程图:

核心公式:

// 总处理字节数 = 单次迭代字节数 × 总迭代次数

totalBytes = b.bytes × b.N

// 总耗时(秒)= 总有效耗时(纳秒) / 10^9

totalSeconds = b.duration / 1e9

// 吞吐量(MB/s)= 总处理字节数(MB) / 总耗时(秒)

// 注:1MB = 1024×1024 = 1048576 字节

throughputMBps = (totalBytes / 1048576) / totalSeconds

测试代码:

func BenchmarkSetBytes(b *testing.B) {
    b.SetBytes(1024 * 1024)
    for i := 0; i < b.N; i++ {
       time.Sleep(1 * time.Second)
    }
}

执行结果:

6.runN():

源码位置:src/testing/benchmark.go

// runN runs a single benchmark for the specified number of iterations.
func (b *B) runN(n int) {
    benchmarkLock.Lock()
    defer benchmarkLock.Unlock()
    ctx, cancelCtx := context.WithCancel(context.Background())
    defer func() {
       b.runCleanup(normalPanic)
       b.checkRaces()
    }()
    // Try to get a comparable environment for each run
    // by clearing garbage from previous runs.
    runtime.GC()
    b.resetRaces()
    b.N = n
    b.loopN = 0
    b.ctx = ctx
    b.cancelCtx = cancelCtx

    b.parallelism = 1
    b.ResetTimer()
    b.StartTimer()
    b.benchFunc(b)
    b.StopTimer()
    b.previousN = n
    b.previousDuration = b.duration
}

流程图:

7.内存统计:

源码位置:src/testing/benchmark.go

// BenchmarkResult contains the results of a benchmark run.
type BenchmarkResult struct {
    N         int           // The number of iterations.
    T         time.Duration // The total time taken.
    Bytes     int64         // Bytes processed in one iteration.
    MemAllocs uint64        // The total number of memory allocations.
    MemBytes  uint64        // The total number of bytes allocated.

    // Extra records additional metrics reported by ReportMetric.
    Extra map[string]float64
}

N:最终执行的迭代次数.

T:总耗时.

Bytes:单词迭代处理的字节数.

MemAllocs:总内存分配次数.

MemBytes:总内存分配字节数.

Extra:自定义性能指标.

生成流程(关联runN):


AllocsPerOp()函数:

// AllocsPerOp returns the "allocs/op" metric,
// which is calculated as r.MemAllocs / r.N.
func (r BenchmarkResult) AllocsPerOp() int64 {
    if v, ok := r.Extra["allocs/op"]; ok {
       return int64(v)
    }
    if r.N <= 0 {
       return 0
    }
    return int64(r.MemAllocs) / int64(r.N)
}

流程图:


AllocedBytesPerOp()函数:

// AllocedBytesPerOp returns the "B/op" metric,
// which is calculated as r.MemBytes / r.N.
func (r BenchmarkResult) AllocedBytesPerOp() int64 {
    if v, ok := r.Extra["B/op"]; ok {
       return int64(v)
    }
    if r.N <= 0 {
       return 0
    }
    return int64(r.MemBytes) / int64(r.N)
}

流程图:

******

语雀地址www.yuque.com/itbosunmian…?

《Go.》 密码:xbkk 欢迎大家访问.提意见.

岁月悠悠.



如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路