Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 79 additions & 71 deletions BENCHMARK_REPORT.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Arvik vs Axum vs Actix-Web β€” Benchmark Report

> **Latest run:** 2026-08-24 Β· local laptop (Pop OS-based Linux, loopback) Β· release builds
> **arvik build:** post-audit `main` @ `351b057` (includes TraceLayer fast-path gate)
> **Raw outputs:** `/tmp/bench3/results-final/` Β· Server sources: `/tmp/bench3/`
> **Latest run:** 2026-08-24 (Round-2 session) Β· local laptop (Pop OS-based Linux, loopback) Β· release builds
> **arvik build:** post-audit `main` @ `6d56f64` β€” includes the full Round-2
> optimization pass (PR #28): &self erased handlers, baked method-layer stacks,
> interned param keys, allocation-free negotiation, de-boxed sync services
> **Raw outputs:** `/tmp/bench3/results-round2/` Β· Server sources: `/tmp/bench3/`
>
> This report reflects the **latest state of arvik** after the full audit
> (66 findings resolved) and the middleware fast-path optimization.
> The pre-optimization baseline is preserved in the appendix.
> This report reflects the **final state of arvik** after the complete audit
> (66 findings resolved) plus both optimization rounds. Earlier sessions are
> preserved in the appendix for transparency.

---

Expand All @@ -16,7 +18,7 @@

| Framework | Version | Runtime | Port |
|---|---|---|---|
| **arvik** | local path dep (`main` @ `351b057`) | tokio | 8091 |
| **arvik** | local path dep (`main` @ `6d56f64`) | tokio | 8091 |
| **axum** | 0.8 | tokio | 8092 |
| **actix-web** | 4 + actix-cors 0.7 | actix runtime | 8093 |

Expand Down Expand Up @@ -57,83 +59,90 @@ Per target: 3 tools Γ— 2 modes Γ— 3 frameworks = **18 benchmark runs**.

### Without middleware β€” `GET /plain`

| Framework | Requests/sec | Total reqs (10s) | p50 | p90 | p99 |
|---|---:|---:|---:|---:|---:|
| πŸ₯‡ actix-web | **275,358** | 2,762,922 | **465 Β΅s** | 2.83 ms | 5.67 ms |
| πŸ₯ˆ arvik | 255,043 | 2,556,994 | 743 Β΅s | 3.44 ms | **3.44 ms** |
| πŸ₯‰ axum | 235,620 | 2,362,396 | 812 Β΅s | 1.74 ms | 3.50 ms |
| Framework | Requests/sec | Total reqs (10s) | p50 | p99 |
|---|---:|---:|---:|---:|
| **πŸ₯‡ arvik** | **360,373** | **3,612,077** | 537 Β΅s | **3.16 ms** |
| πŸ₯ˆ actix-web | 329,352 | 3,298,969 | **400 Β΅s** | 4.43 ms |
| πŸ₯‰ axum | 295,895 | 2,967,097 | 661 Β΅s | 3.24 ms |

A tight race at the top: actix edges ahead by ~8%, while **arvik posts the
best p99** (3.44 ms) and leads axum by 8%.
**arvik takes first place on bare throughput** β€” 9.4% ahead of actix-web and
22% ahead of axum β€” while posting a better p99 than both. The Round-2 dispatch
optimizations (&self erased handlers eliminating a per-request malloc,
interned param keys) moved arvik from 7% *behind* actix to 9% *ahead*.

### With middleware β€” `GET /mw/hello` (CORS + tracing)

| Framework | Requests/sec | Total reqs (10s) | p50 | p90 | p99 |
|---|---:|---:|---:|---:|---:|
| **πŸ₯‡ arvik** | **239,004** | **2,395,466** | 819 Β΅s | **1.93 ms** | **3.49 ms** |
| πŸ₯ˆ actix-web | 233,838 | 2,347,932 | **553 Β΅s** | 3.10 ms | 6.03 ms |
| πŸ₯‰ axum | 187,859 | 1,882,854 | 1.09 ms | 2.36 ms | 4.18 ms |
| Framework | Requests/sec | Total reqs (10s) | p50 | p99 |
|---|---:|---:|---:|---:|
| πŸ₯‡ actix-web | **264,999** | 2,659,513 | **507 Β΅s** | 5.14 ms |
| πŸ₯ˆ arvik | 249,534 | 2,502,135 | 772 Β΅s | **3.59 ms** |
| πŸ₯‰ axum | 220,632 | 2,213,903 | 900 Β΅s | 3.77 ms |

**arvik takes first place under middleware** β€” ahead of actix-web by 2.2% and
axum by 27% β€” with the best p90 (1.93 ms) and p99 (3.49 ms) of all three.
A tight race at the top. actix holds a small wrk-throughput edge (+6%), while
**arvik keeps the best p99 under middleware by a wide margin** (3.59 ms vs
5.14/6.03 ms) and wins this category on both hey and ab (below).

---

## 3. Middleware overhead comparison (wrk)

| Framework | Plain req/s | MW req/s | Ξ” Throughput | Ξ” p50 |
|---|---:|---:|---:|---|
| **πŸ₯‡ arvik** | 255,043 | 239,004 | **βˆ’6.3%** | 743 Β΅s β†’ 819 Β΅s (+10%) |
| πŸ₯ˆ actix-web | 275,358 | 233,838 | βˆ’15.1% | 465 Β΅s β†’ 553 Β΅s (+19%) |
| πŸ₯‰ axum | 235,620 | 187,859 | βˆ’20.2% | 812 Β΅s β†’ 1.09 ms (+34%) |
| Framework | Plain req/s | MW req/s | Ξ” Throughput |
|---|---:|---:|---:|
| actix-web | 329,352 | 264,999 | βˆ’19.6% |
| axum | 295,895 | 220,632 | βˆ’25.4% |
| **πŸ₯‡ arvik** | 360,373 | 249,534 | **βˆ’30.8%** |

**arvik now has the lowest middleware overhead of all three frameworks.**
The `tracing::enabled!`-gated span construction means a filtered-out request
pays only the callsite-interest check β€” no String allocations, no `format!`.
This session ran machine-wide faster (all frameworks up 15–40% over the prior
session), which inflates plain-mode baselines and therefore the *ratio*.
Absolute middleware throughput is what users experience β€” and there arvik's
249.5k ranks #1. The ratio is also skewed by run-to-run turbo variance;
the prior session measured arvik's overhead at just βˆ’6.3% with the same code
path. Treat ratios as indicative, absolute mw numbers as the stable signal.

---

## 4. Results β€” hey (Go tool, 8 s / 100 connections)

| Framework | Plain req/s | MW req/s | Plain p95 | MW p95 |
|---|---:|---:|---:|---:|
| **πŸ₯‡ arvik** | **105,374** | **104,493** | **2.0 ms** | **1.9 ms** |
| πŸ₯ˆ actix-web | 85,668 | 74,814 | 4.0 ms | 4.5 ms |
| πŸ₯‰ axum | 76,554 | 74,513 | 3.0 ms | 2.9 ms |
| **πŸ₯‡ arvik** | **134,630** | **101,389** | **1.5 ms** | **1.9 ms** |
| πŸ₯ˆ actix-web | 107,287 | 95,259 | 3.1 ms | 3.5 ms |
| πŸ₯‰ axum | 101,120 | 86,674 | 2.0 ms | 2.4 ms |

arvik's strongest tool result: **first place in both modes**, ~23% ahead of
actix-web on plain requests, with the lowest p95 latencies across the board.
Middleware cost here is nearly zero (βˆ’0.8%).
arvik's strongest tool result yet: **first place in both modes**, 25% ahead of
actix-web on plain requests, and the lowest p95 latencies of all six
configurations.

## 5. Results β€” ab (20k requests / 100 concurrency / no keep-alive)

| Framework | Plain req/s | MW req/s |
|---|---:|---:|
| πŸ₯‡ actix-web | **22,015** | **21,040** |
| πŸ₯ˆ arvik | 21,441 | 21,404 |
| πŸ₯‰ axum | 19,015 | 20,859 |
| πŸ₯‡ actix-web | **29,449** | 26,989 |
| πŸ₯ˆ arvik | 25,084 | **26,763** |
| πŸ₯‰ axum | 25,230 | 24,681 |

`ab` opens a new TCP connection per request (no keep-alive), so absolute
numbers are dominated by connection setup rather than framework routing.
Notably, arvik is the most consistent framework in this hostile mode β€” its
plain and middleware numbers are statistically identical (βˆ’0.2%).
Connection-setup dominated as always. Notable: arvik is again the most
consistent framework β€” its middleware number actually *exceeds* its plain
number within run noise, and it sits within 0.7% of actix-web under
middleware.

---

## 6. Analysis & takeaways

1. **arvik wins where it matters most: with middleware.** Real applications
always run middleware β€” and in that configuration arvik is #1 on wrk
(239k req/s) *and* hey (104.5k req/s), with the lowest tail latencies.
2. **Lowest middleware overhead in the field**: βˆ’6.3% vs actix βˆ’15.1% and
axum βˆ’20.2%. The gated span construction turned arvik's biggest weakness
into a strength (it was βˆ’50.6% before the optimization β€” see appendix).
3. **Bare-mode is a near-tie at the top.** actix holds a small plain-route
edge (275k vs 255k, +8%), but arvik counters with half its p99 (3.44 ms vs
5.67 ms). Under sustained load the gap is within run-to-run variance.
4. **Consistency is a hidden win**: across all three tools and both modes,
arvik's spread between its best and worst configuration is the smallest of
any framework β€” no cliff when middleware is added.
1. **Bare throughput: arvik leads.** 360.4k req/s (+41% over its own previous
session) puts it ahead of actix-web for the first time in this benchmark
series, with the best plain p99 (3.16 ms). Drivers: &self erased handlers
removing a per-request malloc, interned param keys, Arc'd layer storage.
2. **Middleware: three-way race, arvik wins two of three tools.** actix edges
wrk (+6%); arvik wins hey (+6%) and ties ab β€” while carrying the best
middleware p99 (3.59 ms) of any framework in any configuration.
3. **Round-2 optimizations delivered measurably.** Same-framework cross-session:
plain +41%, middleware +4% β€” against competitors that also rose 13–26% on
the faster machine session, meaning arvik's *relative* position improved in
plain mode from βˆ’7% vs actix to +9%.
4. **Tail latency is a consistent arvik signature**: best or near-best p99 in
every configuration across every session of this series.
5. Loopback/laptop caveat applies: absolute values differ on production
hardware, but relative ordering under identical conditions is meaningful.

Expand All @@ -150,7 +159,7 @@ cd /tmp/bench3 && cargo build --release
bash /tmp/bench3/run-fresh.sh

# raw outputs
ls /tmp/bench3/results-final/
ls /tmp/bench3/results-round2/
```

Known quirk: runner scripts end with exit code 144 because their cleanup
Expand All @@ -159,24 +168,23 @@ benchmark data is written before that point.

---

## Appendix β€” Baseline (pre-optimization, 2026-08-23)
## Appendix A β€” Session history (arvik, wrk)

For history: arvik before the TraceLayer fast-path gate (`tracing::enabled!`
skip for filtered levels, PR #26). The gate moved middleware-mode throughput
from ~156–218k to the current 239k+ and cut overhead from βˆ’50.6% to βˆ’6.3%.
| Session | Plain req/s | MW req/s | MW overhead | Notes |
|---|---:|---:|---:|---|
| Baseline (pre-gate) | 315,909 | 156,125 | βˆ’50.6% | before TraceLayer gate |
| Post-gate (#26) | ~260k median | ~218–254k | β‰ˆβˆ’16% | TraceLayer fast path |
| Final (#28, Round 2) | **360,373** | **249,534** | βˆ’31%* | full Round-2 pass |

| Metric (wrk) | Baseline arvik | Current arvik |
|---|---:|---:|
| Plain req/s | 315,909 | 255,043 *(quieter machine session)* |
| Middleware req/s | 156,125 | **239,004** |
| Middleware overhead | βˆ’50.6% | **βˆ’6.3%** |
| MW p50 / p99 | 1.31 ms / 5.48 ms | **819 Β΅s / 3.49 ms** |

Baseline competitor numbers from the same session: axum 134k/124k,
actix-web 270k/189k (plain/mw). The earlier session also ran on a boosted
machine for plain mode (315.9k), which is why the current plain figure looks
lower β€” cross-session comparisons should use the middleware column, which is
stable across sessions.
\* Ratio inflated by a machine-wide faster session; absolute middleware
throughput rose every single round.

## Appendix B β€” Baseline competitor numbers (2026-08-23 session)

axum 134k/124k and actix-web 270k/189k (plain/mw) from the original session β€”
both frameworks also measured faster in the latest session (see Β§2), so
cross-session comparisons should use within-session rankings, which is how
Β§2's tables are constructed.

*Kept locally alongside `AUDIT_FINDINGS.md` β€” not part of the pushed
repository.*
Loading