Router Performance

Comprehensive benchmark comparison between rivaas/router and other popular Go web frameworks, with methodology and reproduction instructions.

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:

Test Scenarios

All frameworks are tested with the same three route patterns:

  1. Static route: GET /
  2. One parameter: GET /users/:id
  3. 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/http adaptor (fiberadaptor.FiberApp) for compatibility with httptest.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 implement http.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.conf is 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.

Frameworkns/opB/opallocs/opNotes
Rivaas65.300Zero alloc
Gin66.100Zero alloc
StdMux76.200Zero alloc
Echo76.681
Chi282.33682
Beego602.83604
Hertz1689.0344824via ut.PerformRequest
Fiber2110.0197220via http adaptor
FiberV36985.03309615via 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.

Frameworkns/opB/opallocs/opNotes
Gin120.500Zero alloc
Rivaas126.400Zero alloc
Echo133.0162
StdMux223.8161
Chi365.03682
Beego931.64006
Hertz2011.0354427via ut.PerformRequest
Fiber2145.0205920via http adaptor
FiberV37190.03311216via 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.

Frameworkns/opB/opallocs/opNotes
Gin199.700Zero alloc
Rivaas222.500Zero alloc
Echo223.7324
StdMux435.3482
Chi475.73682
Beego1236.04488
Hertz2163.0366429via ut.PerformRequest
Fiber2339.0216420via http adaptor
FiberV37636.03357318via 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