⚡ 基准测试指南
基准测试是 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)
}
})
}📚 相关资源
项目测试文档
- TDD 测试驱动开发 - TDD 实践方法
- 单元测试指南 - 单元测试最佳实践
- 集成测试 - 组件集成测试
外部参考
💡 基准测试建议: 基准测试应该关注关键路径和性能瓶颈。定期运行基准测试,建立性能基线,并在CI/CD中集成性能回归检测。