当前位置: 首页 > article >正文

40分钟学 Go 语言高并发:基准测试编写

基准测试编写

一、基准测试基础知识

测试类型主要用途关注指标重要程度
基准测试性能测量、代码优化执行时间、内存分配⭐⭐⭐⭐⭐
比较测试不同实现方案对比相对性能差异⭐⭐⭐⭐
并发基准并发性能测试QPS、吞吐量⭐⭐⭐⭐⭐
内存基准内存使用分析内存分配、GC⭐⭐⭐⭐

让我们通过代码示例来学习基准测试的编写:

package benchmark

import (
    "bytes"
    "strings"
    "sync"
    "testing"
)

// 被测试的函数
func concat(strs []string) string {
    var result string
    for _, s := range strs {
        result += s
    }
    return result
}

func concatBuilder(strs []string) string {
    var builder strings.Builder
    for _, s := range strs {
        builder.WriteString(s)
    }
    return builder.String()
}

// 基本的基准测试
func BenchmarkConcat(b *testing.B) {
    testData := []string{"hello", "world", "benchmark", "test"}
    b.ResetTimer() // 重置计时器,忽略前面的准备时间
    
    for i := 0; i < b.N; i++ {
        concat(testData)
    }
}

func BenchmarkConcatBuilder(b *testing.B) {
    testData := []string{"hello", "world", "benchmark", "test"}
    b.ResetTimer()
    
    for i := 0; i < b.N; i++ {
        concatBuilder(testData)
    }
}

// 带内存统计的基准测试
func BenchmarkConcatWithAlloc(b *testing.B) {
    testData := []string{"hello", "world", "benchmark", "test"}
    b.ResetTimer()
    
    b.ReportAllocs() // 报告内存分配统计
    for i := 0; i < b.N; i++ {
        concat(testData)
    }
}

// 并发基准测试
func BenchmarkConcatParallel(b *testing.B) {
    testData := []string{"hello", "world", "benchmark", "test"}
    
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            concatBuilder(testData)
        }
    })
}

// 字节缓冲池
var bufferPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

// 使用对象池的基准测试
func BenchmarkConcatWithPool(b *testing.B) {
    testData := []string{"hello", "world", "benchmark", "test"}
    b.ResetTimer()
    
    for i := 0; i < b.N; i++ {
        buf := bufferPool.Get().(*bytes.Buffer)
        buf.Reset()
        
        for _, s := range testData {
            buf.WriteString(s)
        }
        
        _ = buf.String()
        bufferPool.Put(buf)
    }
}

// 子测试基准测试
func BenchmarkStringOperations(b *testing.B) {
    testData := []string{"hello", "world", "benchmark", "test"}
    
    b.Run("Concat", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            concat(testData)
        }
    })
    
    b.Run("Builder", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            concatBuilder(testData)
        }
    })
    
    b.Run("Pool", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            buf := bufferPool.Get().(*bytes.Buffer)
            buf.Reset()
            for _, s := range testData {
                buf.WriteString(s)
            }
            _ = buf.String()
            bufferPool.Put(buf)
        }
    })
}

// 不同数据大小的基准测试
func BenchmarkConcatSize(b *testing.B) {
    sizes := []int{10, 100, 1000}
    for _, size := range sizes {
        b.Run(fmt.Sprintf("Size-%d", size), func(b *testing.B) {
            testData := make([]string, size)
            for i := range testData {
                testData[i] = "a"
            }
            
            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                concatBuilder(testData)
            }
        })
    }
}

让我们创建一个基准测试工作流程图:
在这里插入图片描述
现在,让我们创建一个性能分析工具:

package benchmark

import (
    "fmt"
    "math"
    "sort"
    "time"
)

// 基准测试结果
type BenchmarkResult struct {
    Name         string
    Operations   int
    NsPerOp      float64
    AllocsPerOp  int64
    BytesPerOp   int64
    Duration     time.Duration
}

// 性能比较结果
type ComparisonResult struct {
    Baseline    *BenchmarkResult
    Comparison  *BenchmarkResult
    SpeedupFactor float64
    MemoryImprovement float64
}

// 基准测试分析器
type BenchmarkAnalyzer struct {
    results []BenchmarkResult
}

// 添加测试结果
func (ba *BenchmarkAnalyzer) AddResult(name string, ops int, nsPerOp float64, 
    allocsPerOp, bytesPerOp int64) {
    ba.results = append(ba.results, BenchmarkResult{
        Name:        name,
        Operations:  ops,
        NsPerOp:     nsPerOp,
        AllocsPerOp: allocsPerOp,
        BytesPerOp:  bytesPerOp,
        Duration:    time.Duration(nsPerOp) * time.Nanosecond,
    })
}

// 计算统计信息
func (ba *BenchmarkAnalyzer) CalculateStats() map[string]float64 {
    if len(ba.results) == 0 {
        return nil
    }

    stats := make(map[string]float64)
    var nsPerOps []float64
    
    for _, result := range ba.results {
        nsPerOps = append(nsPerOps, result.NsPerOp)
    }
    
    // 计算平均值
    sum := 0.0
    for _, ns := range nsPerOps {
        sum += ns
    }
    stats["mean"] = sum / float64(len(nsPerOps))
    
    // 计算标准差
    sumSquares := 0.0
    for _, ns := range nsPerOps {
        diff := ns - stats["mean"]
        sumSquares += diff * diff
    }
    stats["stddev"] = math.Sqrt(sumSquares / float64(len(nsPerOps)))
    
    // 计算中位数
    sort.Float64s(nsPerOps)
    stats["median"] = nsPerOps[len(nsPerOps)/2]
    
    return stats
}

// 比较两个测试结果
func (ba *BenchmarkAnalyzer) Compare(baseline, comparison string) *ComparisonResult {
    var baseResult, compResult *BenchmarkResult
    
    for i := range ba.results {
        if ba.results[i].Name == baseline {
            baseResult = &ba.results[i]
        }
        if ba.results[i].Name == comparison {
            compResult = &ba.results[i]
        }
    }
    
    if baseResult == nil || compResult == nil {
        return nil
    }
    
    speedup := baseResult.NsPerOp / compResult.NsPerOp
    memoryImprovement := float64(baseResult.BytesPerOp) / float64(compResult.BytesPerOp)
    
    return &ComparisonResult{
        Baseline:          baseResult,
        Comparison:        compResult,
        SpeedupFactor:     speedup,
        MemoryImprovement: memoryImprovement,
    }
}

// 生成报告
func (ba *BenchmarkAnalyzer) GenerateReport() string {
    var report string
    report += "Benchmark Results:\n\n"
    
    // 基本结果
    for _, result := range ba.results {
        report += fmt.Sprintf("Test: %s\n", result.Name)
        report += fmt.Sprintf("Operations: %d\n", result.Operations)
        report += fmt.Sprintf("Time per operation: %.2f ns\n", result.NsPerOp)
        report += fmt.Sprintf("Allocations per operation: %d\n", result.AllocsPerOp)
        report += fmt.Sprintf("Bytes per operation: %d\n", result.BytesPerOp)
        report += fmt.Sprintf("Duration: %v\n\n", result.Duration)
    }
    
    // 统计信息
    stats := ba.CalculateStats()
    if stats != nil {
        report += "Statistics:\n"
        report += fmt.Sprintf("Mean: %.2f ns\n", stats["mean"])
        report += fmt.Sprintf("Median: %.2f ns\n", stats["median"])
        report += fmt.Sprintf("Standard Deviation: %.2f ns\n\n", stats["stddev"])
    }
    
    return report
}

// 示例使用
func ExampleBenchmarkAnalysis() {
    analyzer := &BenchmarkAnalyzer{}
    
    // 添加测试结果
    analyzer.AddResult("Concat", 1000000, 200.0, 2, 64)
    analyzer.AddResult("Builder", 2000000, 100.0, 1, 32)
    analyzer.AddResult("Pool", 3000000, 50.0, 0, 16)
    
    // 生成报告
    report := analyzer.GenerateReport()
    fmt.Println(report)
    
    // 比较结果
    comparison := analyzer.Compare("Concat", "Pool")
    if comparison != nil {
        fmt.Printf("Speedup: %.2fx\n", comparison.SpeedupFactor)
        fmt.Printf("Memory improvement: %.2fx\n", comparison.MemoryImprovement)
    }
}

二、基准测试方法详解

1. 基本测试方法

  1. 重置计时器
b.ResetTimer() // 重置计时器,忽略准备时间
  1. 报告内存分配
b.ReportAllocs() // 启用内存分配统计
  1. 并行测试
b.RunParallel(func(pb *testing.PB) {
    for pb.Next() {
        // 测试代码
    }
})

2. 运行基准测试

# 运行所有基准测试
go test -bench=.

# 运行特定测试
go test -bench=BenchmarkConcat

# 详细内存统计
go test -bench=. -benchmem

# 指定运行时间
go test -bench=. -benchtime=10s

3. 测试结果解读

基准测试输出格式:

BenchmarkConcat-8    1000000    1234 ns/op    128 B/op    2 allocs/op
字段含义
BenchmarkConcat-8测试名称和CPU核心数
1000000执行次数
1234 ns/op每次操作耗时
128 B/op每次操作分配内存
2 allocs/op每次操作内存分配次数

三、性能分析工具

1. pprof工具

import "runtime/pprof"

// 内存profile
f, _ := os.Create("mem.prof")
defer f.Close()
pprof.WriteHeapProfile(f)

使用pprof分析结果:

# 分析CPU profile
go tool pprof cpu.prof

# 分析内存profile
go tool pprof mem.prof

# 生成web可视化报告
go tool pprof -http=:8080 cpu.prof

2. trace工具

package main

import (
    "fmt"
    "os"
    "runtime/trace"
    "sync"
)

func main() {
    // 创建trace文件
    f, err := os.Create("trace.out")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    // 启动trace
    err = trace.Start(f)
    if err != nil {
        panic(err)
    }
    defer trace.Stop()

    // 执行要分析的代码
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            // 创建一个trace区域
            region := trace.StartRegion(context.Background(), fmt.Sprintf("worker-%d", id))
            defer region.End()
            
            // 执行一些工作
            sum := 0
            for i := 0; i < 1000000; i++ {
                sum += i
            }
        }(i)
    }
    wg.Wait()
}

使用trace工具分析:

go tool trace trace.out

四、基准测试最佳实践

1. 测试设计原则

  1. 隔离测试环境

    • 关闭其他程序
    • 固定CPU频率
    • 避免系统干扰
  2. 合理的测试规模

    • 足够的迭代次数
    • 合适的数据量
    • 多次运行取平均值
  3. 完整的测试覆盖

    • 边界条件测试
    • 不同规模测试
    • 并发场景测试

2. 测试结果分析

3. 性能优化决策

指标优化建议注意事项
CPU时间优化算法、减少计算保持代码可读性
内存分配使用对象池、减少临时对象权衡内存使用和性能
GC影响控制对象生命周期、减少压力避免内存泄漏
并发性能合理使用goroutine、控制并发度避免竞态条件

五、总结与建议

1. 基准测试开发流程

  1. 准备阶段

    • 定义测试目标
    • 准备测试数据
    • 设置测试环境
  2. 执行阶段

    • 运行基准测试
    • 收集性能数据
    • 记录测试结果
  3. 分析阶段

    • 分析测试数据
    • 识别性能瓶颈
    • 提出优化建议
  4. 优化阶段

    • 实施优化措施
    • 验证优化效果
    • 记录优化经验

2. 注意事项

  1. 测试环境

    • 保持环境一致
    • 避免外部干扰
    • 多次运行测试
  2. 测试代码

    • 遵循最佳实践
    • 注意测试覆盖
    • 保持代码整洁
  3. 结果分析

    • 客观分析数据
    • 考虑多个因素
    • 合理解释结果

怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!


http://www.kler.cn/a/422195.html

相关文章:

  • docker启动redis,使用docker容器的redis-cli
  • burp2
  • go并发设计模式runner模式
  • 44 基于32单片机的博物馆安全监控系统设计
  • OD C卷 - 实现 【手机App防沉迷系统】
  • xiaolin coding 图解 MySQL笔记——事务篇
  • 项目开发规范
  • 论文:IoU Loss for 2D/3D Object Detection
  • 明明的随机数
  • FPGA实战篇(触摸按键控制LED灯)
  • Mock.js的学习使用
  • 5G学习笔记之随机接入
  • 基于Java Springboot校园导航微信小程序
  • 658.找到K个最接近的元素(双指针)
  • 【深度学习】—CNN卷积神经网络 从原理到实现
  • 社区团购中 2+1 链动模式商城小程序的创新融合与发展策略研究
  • Linux 网卡收包流程如下
  • 手机ip地址取决于什么?可以随便改吗
  • 20240921解决使用PotPlayer在WIN10电脑播放4K分辨率10bit的视频出现偏色的问题
  • stable diffusion实践操作-大模型介绍:SD的发展历史,SD1.5和SDXL之间的差别
  • DVWA靶场——XSS(Stored)
  • 数据库python连接测试
  • 学习笔记050——SpringBoot学习1
  • docker使用(镜像、容器)
  • sheng的学习笔记-【中】【吴恩达课后测验】Course 5 - 序列模型 - 第三周测验 - 序列模型与注意力机制
  • 用于LiDAR测量的1.58um单芯片MOPA(一)