Gin 性能测试与基准测试
基准测试(Benchmark)用于评估代码性能,是优化的重要依据。
基准测试基础
基本格式
Go
// 基准测试函数命名:func BenchmarkXxx(b *testing.B)
func BenchmarkPingHandler(b *testing.B) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong"})
})
// b.N 是测试框架自动调整的迭代次数
b.ResetTimer() // 重置计时器,忽略初始化时间
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("GET", "/ping", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
}
运行基准测试
Bash
# 运行所有基准测试
go test -bench=. ./...
# 运行特定基准测试
go test -bench=BenchmarkPingHandler ./...
# 详细输出
go test -bench=. -benchmem ./...
# 指定运行时间
go test -bench=. -benchtime=5s ./...
# 输出示例
// BenchmarkPingHandler-8 1000000 1200 ns/op 256 B/op 2 allocs/op
// 含义:迭代100万次,每次1200纳秒,每次256字节内存,每次2次内存分配
Handler 基准测试
JSON 响应测试
Go
func BenchmarkJSONResponse(b *testing.B) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/data", func(c *gin.Context) {
c.JSON(200, gin.H{
"id": 123,
"name": "Test User",
"email": "test@example.com",
"active": true,
})
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("GET", "/data", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
}
POST 请求测试
Go
func BenchmarkCreateUser(b *testing.B) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.POST("/users", func(c *gin.Context) {
var user User
c.ShouldBindJSON(&user)
c.JSON(201, gin.H{"id": "123"})
})
body := `{"name":"Test","email":"test@example.com"}`
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("POST", "/users", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
}
中间件性能测试
中间件对比
Go
func BenchmarkNoMiddleware(b *testing.B) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/test", func(c *gin.Context) {
c.String(200, "ok")
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
}
func BenchmarkWithMiddleware(b *testing.B) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.Use(RequestIDMiddleware())
r.Use(LoggerMiddleware())
r.GET("/test", func(c *gin.Context) {
c.String(200, "ok")
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
}
func RequestIDMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("request_id", uuid.New().String())
c.Next()
}
}
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
}
}
内存分配测试
并行基准测试
Go
func BenchmarkParallelHandler(b *testing.B) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/test", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
})
}
// 运行:go test -bench=BenchmarkParallel -benchmem ./...
内存分配分析
Go
func BenchmarkMemoryAllocation(b *testing.B) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/large", func(c *gin.Context) {
// 大数据响应
data := make([]map[string]interface{}, 100)
for i := 0; i < 100; i++ {
data[i] = map[string]interface{}{
"id": i,
"name": "User" + strconv.Itoa(i),
"value": i * 100,
}
}
c.JSON(200, data)
})
b.ResetTimer()
b.ReportAllocs() // 详细内存报告
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("GET", "/large", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
}
性能对比测试
不同方法对比
Go
func BenchmarkStringResponse(b *testing.B) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/string", func(c *gin.Context) {
c.String(200, "Hello World")
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("GET", "/string", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
}
func BenchmarkJSONResponse(b *testing.B) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/json", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "Hello World"})
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("GET", "/json", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
}
func BenchmarkJSONStructResponse(b *testing.B) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/struct", func(c *gin.Context) {
c.JSON(200, struct{ Message string }{"Hello World"})
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("GET", "/struct", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
}
// 运行对比
// go test -bench=Benchmark.*Response -benchmem ./...
参数绑定测试
Go
func BenchmarkBindJSON(b *testing.B) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.POST("/bind", func(c *gin.Context) {
var data struct {
Name string `json:"name"`
Email string `json:"email"`
}
c.ShouldBindJSON(&data)
c.JSON(200, data)
})
body := `{"name":"Test","email":"test@example.com"}`
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("POST", "/bind", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
}
func BenchmarkBindQuery(b *testing.B) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/bind", func(c *gin.Context) {
var data struct {
Name string `form:"name"`
Email string `form:"email"`
}
c.ShouldBindQuery(&data)
c.JSON(200, data)
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("GET", "/bind?name=Test&email=test@example.com", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
}
子基准测试
Go
func BenchmarkHandlers(b *testing.B) {
gin.SetMode(gin.TestMode)
b.Run("SimpleGET", func(b *testing.B) {
r := gin.New()
r.GET("/simple", func(c *gin.Context) {
c.String(200, "ok")
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("GET", "/simple", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
})
b.Run("JSONPOST", func(b *testing.B) {
r := gin.New()
r.POST("/json", func(c *gin.Context) {
var data map[string]string
c.ShouldBindJSON(&data)
c.JSON(200, data)
})
body := `{"key":"value"}`
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("POST", "/json", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
})
b.Run("WithMiddleware", func(b *testing.B) {
r := gin.New()
r.Use(func(c *gin.Context) { c.Next() })
r.GET("/test", func(c *gin.Context) { c.String(200, "ok") })
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
})
}
基准测试输出分析
Go
BenchmarkPingHandler-8 5000000 280 ns/op 64 B/op 1 allocs/op
| 字段 | 含义 |
|---|---|
| 5000000 | 迭代次数 |
| 280 ns/op | 每次操作耗时 |
| 64 B/op | 每次内存分配 |
| 1 allocs/op | 每次分配次数 |
性能优化检查点
text
// 优化前:每次请求创建路由
func BenchmarkBadPattern(b *testing.B) {
for i := 0; i < b.N; i++ {
r := gin.New() // 错误:每次创建
r.GET("/test", func(c *gin.Context) { c.String(200, "ok") })
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
}
// 优化后:路由预先创建
func BenchmarkGoodPattern(b *testing.B) {
r := gin.New()
r.GET("/test", func(c *gin.Context) { c.String(200, "ok") })
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
}
}
注意:基准测试中初始化代码应在
b.ResetTimer()之前执行。
要点总结
- 函数命名:
func BenchmarkXxx(b *testing.B) - 迭代模式:使用
b.N自动调整迭代次数 - 计时控制:
b.ResetTimer()重置计时器 - 内存报告:
-benchmem显示内存分配情况 - 并行测试:
b.RunParallel测试并发性能 - 对比测试:使用子测试
b.Run()组织多个场景
📝 发现内容有误?点击此处直接编辑