Skip to content

⚡ 基准测试指南

基准测试是 lzt 项目性能优化的重要工具,本文档提供编写和执行基准测试的完整指南。

🎯 基准测试概览

基准测试目标

  • 性能基线 - 建立性能基准和回归检测
  • 优化验证 - 验证优化效果和性能改进
  • 容量规划 - 评估系统容量和扩展需求
  • 瓶颈识别 - 发现性能瓶颈和热点代码

📊 Go 基准测试

基础基准测试

go
// pkg/bubble/benchmark_test.go
func BenchmarkProcessStrings(b *testing.B) {
    items := []string{"task1", "task2", "task3", "task4", "task5"}
    processFunc := func(s string) error {
        // 模拟处理时间
        time.Sleep(time.Microsecond)
        return nil
    }
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _ = bubble.ProcessStrings(items, processFunc)
    }
}

内存分配基准测试

go
func BenchmarkProgressBarMemory(b *testing.B) {
    tasks := make([]bubble.Task, 1000)
    for i := range tasks {
        tasks[i] = bubble.NewSimpleTask(fmt.Sprintf("Task %d", i), "data")
    }
    
    b.ResetTimer()
    b.ReportAllocs()
    
    for i := 0; i < b.N; i++ {
        model := bubble.NewProgressModel(tasks)
        _ = model.View() // 触发渲染
    }
}

并发基准测试

go
func BenchmarkConcurrentProcessing(b *testing.B) {
    tasks := generateTestTasks(100)
    
    benchmarks := []struct {
        name    string
        workers int
    }{
        {"SingleWorker", 1},
        {"TwoWorkers", 2},
        {"FourWorkers", 4},
        {"EightWorkers", 8},
    }
    
    for _, bm := range benchmarks {
        b.Run(bm.name, func(b *testing.B) {
            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                bubble.ProcessConcurrent(tasks, processTask, bubble.Options{
                    Workers: bm.workers,
                })
            }
        })
    }
}

🗄️ 数据库性能基准测试

CRUD 操作基准测试

go
// internal/app/ledger/benchmark_test.go
func BenchmarkTransactionCRUD(b *testing.B) {
    db := setupBenchmarkDB(b)
    repo := NewGormTransactionRepository(db)
    
    b.Run("Create", func(b *testing.B) {
        transactions := generateTestTransactions(b.N)
        
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            _ = repo.Create(context.Background(), transactions[i])
        }
    })
    
    b.Run("Read", func(b *testing.B) {
        // 预先创建一些数据
        transactions := generateTestTransactions(1000)
        for _, tx := range transactions {
            _ = repo.Create(context.Background(), tx)
        }
        
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            txID := transactions[i%len(transactions)].ID
            _, _ = repo.GetByID(context.Background(), txID)
        }
    })
    
    b.Run("List", func(b *testing.B) {
        filter := ListTransactionsFilter{
            Limit:  20,
            Offset: 0,
        }
        
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            _, _ = repo.List(context.Background(), filter)
        }
    })
}

批量操作基准测试

go
func BenchmarkBatchOperations(b *testing.B) {
    db := setupBenchmarkDB(b)
    repo := NewGormTransactionRepository(db)
    
    batchSizes := []int{10, 50, 100, 500, 1000}
    
    for _, size := range batchSizes {
        b.Run(fmt.Sprintf("BatchSize_%d", size), func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                transactions := generateTestTransactions(size)
                
                b.StartTimer()
                err := repo.BatchCreate(context.Background(), transactions)
                b.StopTimer()
                
                if err != nil {
                    b.Fatalf("Batch create failed: %v", err)
                }
            }
        })
    }
}

🌐 HTTP API 基准测试

HTTP 端点基准测试

go
func BenchmarkHTTPEndpoints(b *testing.B) {
    server := setupBenchmarkServer(b)
    defer server.Close()
    
    client := &http.Client{
        Timeout: 30 * time.Second,
    }
    
    b.Run("CreateTransaction", func(b *testing.B) {
        reqData := map[string]interface{}{
            "ledger_id":   "bench-ledger",
            "type":        "expense",
            "amount":      1000,
            "currency":    "CNY",
            "description": "Benchmark transaction",
        }
        
        reqBody, _ := json.Marshal(reqData)
        
        b.ResetTimer()
        b.RunParallel(func(pb *testing.PB) {
            for pb.Next() {
                resp, err := client.Post(
                    server.URL+"/api/v1/transactions",
                    "application/json",
                    bytes.NewBuffer(reqBody),
                )
                if err != nil {
                    b.Fatalf("Request failed: %v", err)
                }
                resp.Body.Close()
            }
        })
    })
    
    b.Run("ListTransactions", func(b *testing.B) {
        b.ResetTimer()
        b.RunParallel(func(pb *testing.PB) {
            for pb.Next() {
                resp, err := client.Get(server.URL + "/api/v1/transactions?limit=20")
                if err != nil {
                    b.Fatalf("Request failed: %v", err)
                }
                resp.Body.Close()
            }
        })
    })
}

gRPC 基准测试

go
func BenchmarkGRPCService(b *testing.B) {
    conn, client := setupBenchmarkGRPCServer(b)
    defer conn.Close()
    
    b.Run("CreateTransaction", func(b *testing.B) {
        req := &pb.CreateTransactionRequest{
            LedgerId:    "bench-ledger",
            Type:        pb.TransactionType_TRANSACTION_TYPE_EXPENSE,
            Amount:      &pb.Money{Amount: 1000, Currency: pb.Currency_CURRENCY_CNY},
            Description: "Benchmark transaction",
        }
        
        b.ResetTimer()
        b.RunParallel(func(pb *testing.PB) {
            for pb.Next() {
                _, err := client.CreateTransaction(context.Background(), req)
                if err != nil {
                    b.Fatalf("gRPC call failed: %v", err)
                }
            }
        })
    })
}

📈 性能分析和优化

CPU 性能分析

go
func BenchmarkWithProfiling(b *testing.B) {
    // 启用 CPU 分析
    f, err := os.Create("cpu.prof")
    if err != nil {
        b.Fatal(err)
    }
    defer f.Close()
    
    if err := pprof.StartCPUProfile(f); err != nil {
        b.Fatal(err)
    }
    defer pprof.StopCPUProfile()
    
    // 运行基准测试
    tasks := generateTestTasks(10000)
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        bubble.ProcessConcurrent(tasks, processTask, bubble.Options{
            Workers: 8,
        })
    }
}

内存性能分析

go
func BenchmarkMemoryProfile(b *testing.B) {
    var memStats1, memStats2 runtime.MemStats
    
    // 记录开始内存状态
    runtime.GC()
    runtime.ReadMemStats(&memStats1)
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        // 执行测试代码
        model := bubble.NewProgressModel(generateTestTasks(1000))
        _ = model.View()
    }
    
    // 记录结束内存状态
    runtime.GC()
    runtime.ReadMemStats(&memStats2)
    
    // 报告内存使用
    b.ReportMetric(float64(memStats2.Alloc-memStats1.Alloc), "bytes/op")
    b.ReportMetric(float64(memStats2.Mallocs-memStats1.Mallocs), "allocs/op")
}

🔧 基准测试工具

运行基准测试

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

# 运行特定的基准测试
go test -bench=BenchmarkProcessStrings ./pkg/bubble/

# 运行多次以获得更准确的结果
go test -bench=. -count=5 ./...

# 生成内存分配报告
go test -bench=. -benchmem ./...

# 运行长时间基准测试
go test -bench=. -benchtime=10s ./...

# 输出详细信息
go test -bench=. -v ./...

基准测试比较

bash
# 安装 benchcmp 工具
go install golang.org/x/tools/cmd/benchcmp@latest

# 保存基准测试结果
go test -bench=. ./... > old.txt

# 进行优化后再次运行
go test -bench=. ./... > new.txt

# 比较结果
benchcmp old.txt new.txt

使用 benchstat

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

# 运行基准测试并保存结果
go test -bench=. -count=10 ./... > benchmark.txt

# 分析结果
benchstat benchmark.txt

📊 基准测试最佳实践

1. 基准测试设计原则

go
func BenchmarkGoodExample(b *testing.B) {
    // 在计时开始前进行初始化
    data := setupTestData()
    processFunc := func(item string) error {
        // 实际的处理逻辑
        return processItem(item)
    }
    
    // 重置计时器,排除初始化时间
    b.ResetTimer()
    
    for i := 0; i < b.N; i++ {
        // 只测试核心逻辑
        _ = bubble.ProcessStrings(data, processFunc)
    }
}

func BenchmarkBadExample(b *testing.B) {
    for i := 0; i < b.N; i++ {
        // ❌ 每次迭代都重新创建数据
        data := setupTestData()
        
        // ❌ 在循环内部进行不必要的操作
        processFunc := func(item string) error {
            return processItem(item)
        }
        
        _ = bubble.ProcessStrings(data, processFunc)
    }
}

2. 避免编译器优化

go
func BenchmarkAvoidOptimization(b *testing.B) {
    var result string // 用于存储结果,避免被优化掉
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        result = expensiveStringOperation("test input")
    }
    
    // 使用结果,防止编译器优化
    _ = result
}

// 或者使用全局变量
var globalResult string

func BenchmarkWithGlobalSink(b *testing.B) {
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        globalResult = expensiveStringOperation("test input")
    }
}

3. 子基准测试

go
func BenchmarkDifferentSizes(b *testing.B) {
    sizes := []int{10, 100, 1000, 10000}
    
    for _, size := range sizes {
        b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) {
            data := generateTestData(size)
            
            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                _ = processData(data)
            }
        })
    }
}

🎯 性能目标和监控

性能基准线

go
// 定义性能目标
const (
    MaxProcessingTimeNs    = 1000000  // 1ms
    MaxMemoryAllocBytes    = 1024     // 1KB
    MaxAllocsPerOperation  = 10       // 10次分配
)

func BenchmarkWithTargets(b *testing.B) {
    b.ResetTimer()
    b.ReportAllocs()
    
    for i := 0; i < b.N; i++ {
        result := expensiveOperation()
        _ = result
    }
    
    // 可以在CI中检查这些指标
}

性能回归检测

bash
#!/bin/bash
# scripts/performance_check.sh

# 运行基准测试
go test -bench=. -count=5 ./... > new_results.txt

# 与基准线比较
if [ -f baseline_results.txt ]; then
    benchstat baseline_results.txt new_results.txt > comparison.txt
    
    # 检查是否有显著的性能回归
    if grep -q "slower" comparison.txt; then
        echo "Performance regression detected!"
        cat comparison.txt
        exit 1
    fi
fi

echo "Performance check passed!"

🔍 实际项目基准测试案例

Bubble 组件性能测试

go
// pkg/bubble/composite_benchmark_test.go
func BenchmarkCompositeProgress(b *testing.B) {
    // 测试不同规模的复合任务
    testCases := []struct {
        name         string
        mainTasks    int
        subTasksEach int
    }{
        {"Small", 3, 10},
        {"Medium", 10, 50},
        {"Large", 20, 100},
    }
    
    for _, tc := range testCases {
        b.Run(tc.name, func(b *testing.B) {
            tasks := generateCompositeTestTasks(tc.mainTasks, tc.subTasksEach)
            
            b.ResetTimer()
            b.ReportAllocs()
            
            for i := 0; i < b.N; i++ {
                bubble.RunCompositeProgress(tasks, processCompositeTask)
            }
        })
    }
}

数据库查询优化基准测试

go
func BenchmarkQueryOptimization(b *testing.B) {
    db := setupBenchmarkDB(b)
    
    // 比较不同查询策略
    b.Run("WithoutIndex", func(b *testing.B) {
        // 删除索引
        db.Exec("DROP INDEX idx_ledger_id ON transactions")
        
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            var count int64
            db.Model(&Transaction{}).Where("ledger_id = ?", "test-ledger").Count(&count)
        }
    })
    
    b.Run("WithIndex", func(b *testing.B) {
        // 添加索引
        db.Exec("CREATE INDEX idx_ledger_id ON transactions(ledger_id)")
        
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            var count int64
            db.Model(&Transaction{}).Where("ledger_id = ?", "test-ledger").Count(&count)
        }
    })
}

📚 相关资源

项目测试文档

外部参考


💡 基准测试建议: 基准测试应该关注关键路径和性能瓶颈。定期运行基准测试,建立性能基线,并在CI/CD中集成性能回归检测。

基于 MIT 许可证发布