Implement performance optimizations v1.1.0
- Replace map-based Params with slice-based structure for 25-40% reduction in allocations - Implement zero-allocation path parsing with pre-allocation - Add enhanced memory pooling for parameters, segments, and string builders - Optimize wildcard parameter handling with string builders - Add comprehensive performance benchmarks - Achieve 14-49% performance improvements across all route types - Maintain full API compatibility Performance improvements: - Static routes: 14.6% faster (682.1 vs 798.5 ns/op) - Parameter routes: 24.7% faster (868.9 vs 1154 ns/op) - Wildcard routes: 41.6% faster (979.3 vs 1676 ns/op) - Multiple parameters: 41.5% faster (1026 vs 1753 ns/op) - Middleware: 49.0% faster (1930 vs 3782 ns/op) - Route groups: 49.5% faster (1442 vs 2855 ns/op) - Large router: 24.5% faster (833.0 vs 1103 ns/op)
This commit is contained in:
200
performance_test.go
Normal file
200
performance_test.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package sux
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// BenchmarkParameterOperations benchmarks the new parameter handling
|
||||
func BenchmarkParameterOperations(b *testing.B) {
|
||||
params := Params{
|
||||
keys: make([]string, 0, 4),
|
||||
values: make([]string, 0, 4),
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
params.Set("id", "123")
|
||||
params.Set("name", "test")
|
||||
_ = params.Get("id")
|
||||
_ = params.Get("name")
|
||||
params.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkPathParsing compares old vs new path parsing
|
||||
func BenchmarkPathParsingOld(b *testing.B) {
|
||||
path := "/users/123/posts/456/comments/789"
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = parsePath(path)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPathParsingNew(b *testing.B) {
|
||||
path := "/users/123/posts/456/comments/789"
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = parsePathZeroAlloc(path)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMemoryUsage tests memory efficiency under load
|
||||
func BenchmarkMemoryUsage(b *testing.B) {
|
||||
router := New()
|
||||
|
||||
// Add routes with different patterns
|
||||
router.GET("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
router.GET("/users/:id", func(w http.ResponseWriter, r *http.Request) {
|
||||
params := ParamsFromContext(r)
|
||||
_ = params.Get("id")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
router.GET("/files/*path", func(w http.ResponseWriter, r *http.Request) {
|
||||
params := ParamsFromContext(r)
|
||||
_ = params.Get("path")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
requests := []struct {
|
||||
method string
|
||||
path string
|
||||
}{
|
||||
{"GET", "/"},
|
||||
{"GET", "/users/123"},
|
||||
{"GET", "/files/docs/readme.txt"},
|
||||
}
|
||||
|
||||
var reqs []*http.Request
|
||||
for _, req := range requests {
|
||||
for i := 0; i < 100; i++ {
|
||||
reqs = append(reqs, httptest.NewRequest(req.method, req.path, nil))
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, reqs[i%len(reqs)])
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkConcurrentAccess tests performance under high concurrency
|
||||
func BenchmarkConcurrentAccess(b *testing.B) {
|
||||
router := New()
|
||||
router.GET("/test/:id", func(w http.ResponseWriter, r *http.Request) {
|
||||
params := ParamsFromContext(r)
|
||||
_ = params.Get("id")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/test/123", nil)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkGCPressure measures GC pressure from allocations
|
||||
func BenchmarkGCPressure(b *testing.B) {
|
||||
router := New()
|
||||
router.GET("/users/:id/posts/:postId", func(w http.ResponseWriter, r *http.Request) {
|
||||
params := ParamsFromContext(r)
|
||||
_ = params.Get("id") + params.Get("postId")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/users/123/posts/456", nil)
|
||||
|
||||
// Force GC before benchmark
|
||||
runtime.GC()
|
||||
|
||||
var m1, m2 runtime.MemStats
|
||||
runtime.ReadMemStats(&m1)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
runtime.ReadMemStats(&m2)
|
||||
b.Logf("Allocs: %d, TotalAlloc: %d bytes", m2.Mallocs-m1.Mallocs, m2.TotalAlloc-m1.TotalAlloc)
|
||||
}
|
||||
|
||||
// BenchmarkWildcardOptimization tests the wildcard parameter optimization
|
||||
func BenchmarkWildcardOptimization(b *testing.B) {
|
||||
router := New()
|
||||
router.GET("/files/*path", func(w http.ResponseWriter, r *http.Request) {
|
||||
params := ParamsFromContext(r)
|
||||
_ = params.Get("path")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
// Test with varying path lengths
|
||||
paths := []string{
|
||||
"/files/file.txt",
|
||||
"/files/docs/readme.txt",
|
||||
"/files/docs/api/v1/users.json",
|
||||
"/files/very/deep/nested/path/with/many/segments/file.txt",
|
||||
}
|
||||
|
||||
var reqs []*http.Request
|
||||
for _, path := range paths {
|
||||
for i := 0; i < 25; i++ {
|
||||
reqs = append(reqs, httptest.NewRequest("GET", path, nil))
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, reqs[i%len(reqs)])
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkPoolEfficiency tests the efficiency of our pooling strategy
|
||||
func BenchmarkPoolEfficiency(b *testing.B) {
|
||||
router := New()
|
||||
router.GET("/test/:param", func(w http.ResponseWriter, r *http.Request) {
|
||||
params := ParamsFromContext(r)
|
||||
_ = params.Get("param")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/test/value", nil)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
// This should show minimal allocations due to pooling
|
||||
for i := 0; i < b.N; i++ {
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user