Router Performance
5 minute read
This page contains detailed performance benchmarks comparing rivaas/router against other popular Go web frameworks. The benchmarks measure pure routing dispatch overhead by using direct writes (via io.WriteString) in all handlers to eliminate string concatenation allocations.
Benchmark Methodology
Test Environment
- Go Version: 1.25
- CPU: AMD EPYC 9V74 80-Core Processor
- OS: linux/amd64
- Last Updated: 2026-02-15
Frameworks Compared
The following frameworks are included in the comparison:
- Rivaas (rivaas.dev/router) - This router
- StdMux (net/http) - Go 1.22+ standard library with dynamic routing (
{param}) - Gin (github.com/gin-gonic/gin) - High-performance web framework
- Echo (github.com/labstack/echo) - Minimalist web framework
- Chi (github.com/go-chi/chi) - Lightweight router
- Fiber v2 (github.com/gofiber/fiber/v2) - Express-inspired framework
- Fiber v3 (github.com/gofiber/fiber/v3) - Latest version of Fiber
- Hertz (github.com/cloudwego/hertz) - CloudWeGo HTTP framework
- Beego (github.com/beego/beego) - Full-stack framework
Test Scenarios
All frameworks are tested with the same three route patterns:
- Static route:
GET / - One parameter:
GET /users/:id - Two parameters:
GET /users/:id/posts/:post_id
Handler Implementation
To ensure fair comparison and isolate routing overhead, all handlers use direct writes rather than string concatenation:
// Instead of this (causes one string allocation):
w.Write([]byte("User: " + id))
// Handlers do this (zero allocations for supported frameworks):
io.WriteString(w, "User: ")
io.WriteString(w, id)
This eliminates the handler allocation cost, so the measured time represents:
- Route tree traversal and matching
- Parameter extraction
- Context setup
- Response writer overhead (framework-specific)
Measurement Notes
- Fiber v2/v3: Measured via
net/httpadaptor (fiberadaptor.FiberApp) for compatibility withhttptest.ResponseRecorder. The adaptor adds overhead but is necessary for the standard test harness. - Hertz: Measured using
ut.PerformRequest(h.Engine, ...)(Hertz’s native test API) because Hertz does not implementhttp.Handler. Numbers are not directly comparable to httptest-based frameworks due to different measurement approach. - Beego: May log “init global config instance failed” when
conf/app.confis missing; this is safe to ignore in benchmarks.
Benchmark Results
Static Route (/)
This scenario measures the overhead of dispatching a request to a static route with no parameters.
| Framework | ns/op | B/op | allocs/op | Notes |
|---|---|---|---|---|
| Rivaas | 65.3 | 0 | 0 | Zero alloc |
| Gin | 66.1 | 0 | 0 | Zero alloc |
| StdMux | 76.2 | 0 | 0 | Zero alloc |
| Echo | 76.6 | 8 | 1 | |
| Chi | 282.3 | 368 | 2 | |
| Beego | 602.8 | 360 | 4 | |
| Hertz | 1689.0 | 3448 | 24 | via ut.PerformRequest |
| Fiber | 2110.0 | 1972 | 20 | via http adaptor |
| FiberV3 | 6985.0 | 33096 | 15 | via http adaptor |
Scenario: / —
Lower is better. All handlers use direct writes (io.WriteString) to minimize overhead and isolate routing cost.
Key Observations:
- Rivaas, Gin, and StdMux achieve zero allocations with direct writes
- Echo has 1 allocation from its internal context
- Chi, Fiber, Hertz, and Beego have framework-specific overhead
One Parameter (/users/:id)
This scenario measures routing + parameter extraction for a single dynamic segment.
| Framework | ns/op | B/op | allocs/op | Notes |
|---|---|---|---|---|
| Gin | 120.5 | 0 | 0 | Zero alloc |
| Rivaas | 126.4 | 0 | 0 | Zero alloc |
| Echo | 133.0 | 16 | 2 | |
| StdMux | 223.8 | 16 | 1 | |
| Chi | 365.0 | 368 | 2 | |
| Beego | 931.6 | 400 | 6 | |
| Hertz | 2011.0 | 3544 | 27 | via ut.PerformRequest |
| Fiber | 2145.0 | 2059 | 20 | via http adaptor |
| FiberV3 | 7190.0 | 33112 | 16 | via http adaptor |
Scenario: /users/:id —
Lower is better. All handlers use direct writes (io.WriteString) to minimize overhead and isolate routing cost.
Key Observations:
- Rivaas and Gin maintain zero allocations even with parameter extraction
- StdMux has 1 allocation from
r.PathValue() - Echo has 2 allocations (context + param storage)
Two Parameters (/users/:id/posts/:post_id)
This scenario tests routing with multiple dynamic segments.
| Framework | ns/op | B/op | allocs/op | Notes |
|---|---|---|---|---|
| Gin | 199.7 | 0 | 0 | Zero alloc |
| Rivaas | 222.5 | 0 | 0 | Zero alloc |
| Echo | 223.7 | 32 | 4 | |
| StdMux | 435.3 | 48 | 2 | |
| Chi | 475.7 | 368 | 2 | |
| Beego | 1236.0 | 448 | 8 | |
| Hertz | 2163.0 | 3664 | 29 | via ut.PerformRequest |
| Fiber | 2339.0 | 2164 | 20 | via http adaptor |
| FiberV3 | 7636.0 | 33573 | 18 | via http adaptor |
Scenario: /users/:id/posts/:post_id —
Lower is better. All handlers use direct writes (io.WriteString) to minimize overhead and isolate routing cost.
Key Observations:
- Rivaas and Gin continue to show zero allocations
- StdMux scales linearly (2 allocs for 2 params)
- Echo scales with each additional parameter
How to Reproduce
The benchmarks are located in the router/benchmarks directory of the rivaas repository.
Running All Benchmarks
cd router/benchmarks
go test -bench=. -benchmem
Running a Specific Scenario
# Static route only
go test -bench=BenchmarkStatic -benchmem
# One parameter only
go test -bench=BenchmarkOneParam -benchmem
# Two parameters only
go test -bench=BenchmarkTwoParams -benchmem
Running a Specific Framework
# Rivaas only
go test -bench='/(Rivaas)$' -benchmem
# Gin only
go test -bench='/(Gin)$' -benchmem
Multiple Runs for Statistical Analysis
Use -count to run benchmarks multiple times and benchstat to compare:
go test -bench=. -benchmem -count=5 > results.txt
go install golang.org/x/perf/cmd/benchstat@latest
benchstat results.txt
Understanding the Results
Metrics Explained
- ns/op: Nanoseconds per operation (lower is better)
- B/op: Bytes allocated per operation (lower is better)
- allocs/op: Number of allocations per operation (lower is better)
Why Zero Allocations Matter
Each allocation has a cost:
- Time: Allocating memory takes time (~30-50ns for small allocations)
- GC pressure: More allocations mean more garbage collection work
- Scalability: At high request rates (millions/sec), eliminating allocations significantly reduces CPU and memory usage
Rivaas achieves zero allocations for routing and parameter extraction by:
- Pre-allocating context pools
- Using array-based parameter storage for ≤8 params
- Avoiding string concatenation in hot paths
- Efficient radix tree implementation with minimal allocations
Continuous Benchmarking
The rivaas repository uses continuous benchmarking to detect performance regressions:
- Pull Requests: Every PR runs Rivaas-only benchmarks and compares against a baseline. If performance regresses beyond a threshold, the PR check fails.
- Releases: Full framework comparison runs on every release tag and updates this page automatically.
See the benchmarks.yml workflow for implementation details.
See Also
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.