simoom / Compare / hyper and axum vs simoom
Same droplet. Measured.
hyper and axum vs simoom
hyper is Rust's default HTTP library, and axum is the framework most teams build on top of it, both on tokio. simoom takes a different road: no async runtime, no third-party crates, HTTP/3 and TLS in the box. Benchmark campaign 4 (2026-11-12) measured hyper, axum, axum with rustls, actix-web and simoom on the same droplets in the same rounds. Here is what came out, including where they are ahead.
hyper, axum and simoom at a glance
Wins and losses in one table. The measured rows are from campaign 4: hyper 1.12.0, axum 0.8.9 and axum with rustls, each on the same DigitalOcean c-4 droplet as simoom in the same rounds, with simoom's figure from those same runs.
| hyper / axum | simoom | |
|---|---|---|
| What it is | hyper: HTTP/1 and HTTP/2 library; axum: framework on tokio, tower and hyper | HTTP/1.1, HTTP/2 and HTTP/3 server engine with its own TLS 1.3, WebSocket and a client |
| Runtime | tokio (async/await) | none: plain functions on one event loop per core |
| Third-party crates | tokio, tower and their dependency trees | 0, not even for tests |
| HTTP/3 | not in hyper; the separate h3 crate describes itself as experimental | in-tree, a cargo feature; 54 of 58 QUIC Interop Runner pairs |
| TLS | rustls or native-tls, chosen by you | its own TLS 1.3; not independently audited yet |
| Middleware and ecosystem | tower layers, axum extractors, many integrations, years of production use | a router and optional helpers; version 0.1.0 |
| Status endpoint, 256 clients (req/s, 99% within 1 ms) | hyper 110,931; axum 101,477 | 137,889 (24% more than hyper, 36% more than axum) |
| CPU per request at a fixed 50,000 req/s | hyper 32.6 µs; axum 35.6 µs | 23.6 µs |
| HTTPS API, 256 clients (req/s, 99% within 1 ms) | axum + rustls 88,990 | 134,004 (30% more) |
| New TLS 1.3 clients: full handshakes per second | axum + rustls 11,633 | 9,530 (axum + rustls 24% more) |
| Returning TLS 1.3 clients: resumed handshakes per second | axum + rustls 11,462 | 10,797 (axum + rustls 9% more) |
| HTTP/2, 64 connections x 100 streams (req/s, 99% within 5 ms) | hyper 143,530; axum 124,765 | 153,232 |
| A new connection per request (req/s, 99% within 5 ms) | hyper 16,755 | 16,359 (level) |
| Memory per connection, 10,000 idle connections | hyper 17,790 B; axum 21,888 B | 573 B |
| Memory at rest (PSS) | hyper 1,374 KB; axum 1,518 KB | 1,769 KB (hyper 26% less) |
| Start-up to first answer | hyper 1.48 ms; axum 1.56 ms | 0.94 ms default build; 1.46 ms full server (level with hyper) |
| Program size, stripped | hyper 2,069 KB; axum 2,246 KB; axum + rustls 5,400 KB | 735 KB default build; 1,550 KB full server |
| Traffic spike at 195,000 req/s offered, answers within 1 ms | hyper 2,179/s; axum 956/s | 2,743/s (all three far below actix-web's 59,015/s) |
Campaign 4, 2026-11-12: every figure is the median of rounds, and simoom's figure comes from the same droplet and rounds as the server it is compared with, so it differs a little from row to row. Details in the benchmarks.
Every measure, head to head
| measure (campaign 4) | hyper | axum | axum + rustls | actix-web |
|---|---|---|---|---|
| Status endpoint, 64 clients: requests/s with 99% within 1 ms | simoom 53% more84,364 req/s vs simoom 128,914 req/s | simoom 34% more96,227 req/s vs simoom 128,914 req/s | – | simoom 29% more99,838 req/s vs simoom 128,914 req/s |
| Status endpoint, 256 clients: requests/s with 99% within 1 ms | simoom 24% more110,931 req/s vs simoom 137,889 req/s | simoom 36% more101,477 req/s vs simoom 137,890 req/s | – | simoom 29% more106,566 req/s vs simoom 137,890 req/s |
| Status endpoint, 1,024 clients: requests/s with 99% within 1 ms | simoom 50% more102,154 req/s vs simoom 153,049 req/s | simoom 2.0x more76,953 req/s vs simoom 153,049 req/s | – | simoom 84% more83,023 req/s vs simoom 153,049 req/s |
| Latency at a fixed 50,000 requests/s (256 clients): p99 | simoom 7% lower418 µs vs simoom 387 µs | simoom 14% lower404 µs vs simoom 346 µs | – | simoom 6% lower410 µs vs simoom 387 µs |
| CPU per request at a fixed 50,000 requests/s | simoom 26% lower32.6 µs vs simoom 24.1 µs | simoom 34% lower35.6 µs vs simoom 23.6 µs | – | simoom 23% lower31.2 µs vs simoom 24.1 µs |
| JSON API, 1 KB in and out, 256 clients: requests/s with 99% within 1 ms | simoom 19% more71,171 req/s vs simoom 84,755 req/s | simoom 27% more127,598 req/s vs simoom 161,438 req/s | – | simoom 56% more54,349 req/s vs simoom 84,755 req/s |
| HTTPS API, 256 clients: requests/s with 99% within 1 ms | – | – | simoom 30% more103,343 req/s vs simoom 134,004 req/s | – |
| New TLS 1.3 clients: full handshakes per second | – | – | 24% more than simoom11,633/s vs simoom 9,396/s | – |
| Returning TLS 1.3 clients: resumed handshakes per second | – | – | 9% more than simoom13,537/s vs simoom 12,388/s | – |
| A new connection per request, 192 clients: requests/s with 99% within 5 ms | level16,378 req/s vs simoom 16,359 req/s | simoom 9% more15,009 req/s vs simoom 16,359 req/s | – | 5% more than simoom17,137 req/s vs simoom 16,359 req/s |
| Service-to-service HTTP/2, 64 connections x 100 streams: requests/s with 99% within 5 ms | simoom 7% more143,530 req/s vs simoom 153,232 req/s | simoom 23% more124,765 req/s vs simoom 153,232 req/s | – | simoom 2.1x more72,914 req/s vs simoom 153,232 req/s |
| 64 KB downloads, 64 clients: requests/s with 99% within 5 ms | level3,776 req/s vs simoom 3,773 req/s | level3,779 req/s vs simoom 3,773 req/s | – | level3,780 req/s vs simoom 3,773 req/s |
| 10,000 idle connections: memory per connection | simoom 31x less17,790 B vs simoom 573 B | simoom 38x less21,888 B vs simoom 573 B | – | simoom 21x less11,892 B vs simoom 573 B |
| 100,000 idle connections: memory per connection | simoom 30x less16,293 B vs simoom 535 B | simoom 38x less20,356 B vs simoom 535 B | – | simoom 20x less10,649 B vs simoom 535 B |
| Traffic spike at 195,000 requests/s offered: answers within 1 ms per second | simoom 26% more2,179 req/s vs simoom 2,743 req/s | simoom 2.9x more956 req/s vs simoom 2,743 req/s | – | 22x more than simoom59,015 req/s vs simoom 2,743 req/s |
| Traffic spike: all answers per second | level157,030 req/s vs simoom 155,270 req/s | 3% more than simoom160,356 req/s vs simoom 155,270 req/s | – | level157,873 req/s vs simoom 155,270 req/s |
| Under 11,000 slow attackers: p99 of real requests | level1,026 µs vs simoom 1,002 µs | simoom 6% lower1,065 µs vs simoom 1,002 µs | – | simoom 4.4x lower4,428 µs vs simoom 1,002 µs |
| Start-up, process start to first answer: simoom's default build (HTTP/1.1, HTTP/2, WebSocket) | simoom 57% faster1.48 ms vs simoom 0.94 ms | simoom 66% faster1.56 ms vs simoom 0.94 ms | simoom 2.0x faster1.91 ms vs simoom 0.94 ms | simoom 93% faster1.81 ms vs simoom 0.94 ms |
| Start-up, process start to first answer: simoom's full server (also TLS 1.3 and HTTP/3) | level1.48 ms vs simoom 1.46 ms | simoom 7% faster1.56 ms vs simoom 1.46 ms | simoom 31% faster1.91 ms vs simoom 1.46 ms | simoom 24% faster1.81 ms vs simoom 1.46 ms |
| Program size, stripped: simoom's default build (HTTP/1.1, HTTP/2, WebSocket) | simoom 2.8x smaller2,069 KB vs simoom 735 KB | simoom 3.1x smaller2,246 KB vs simoom 735 KB | simoom 7.3x smaller5,400 KB vs simoom 735 KB | simoom 3.3x smaller2,450 KB vs simoom 735 KB |
| Program size, stripped: simoom's full server (also TLS 1.3 and HTTP/3) | simoom 25% smaller2,069 KB vs simoom 1,550 KB | simoom 31% smaller2,246 KB vs simoom 1,550 KB | simoom 3.5x smaller5,400 KB vs simoom 1,550 KB | simoom 37% smaller2,450 KB vs simoom 1,550 KB |
| Memory at rest (PSS) | 26% less than simoom1,315 KB vs simoom 1,769 KB | 17% less than simoom1,466 KB vs simoom 1,769 KB | simoom 43% less3,110 KB vs simoom 1,769 KB | simoom 7% less1,885 KB vs simoom 1,761 KB |
| Peak memory while serving the status endpoint (PSS) | simoom 5.8x less16.1 MB vs simoom 2,848 KB | simoom 5.3x less14.9 MB vs simoom 2,848 KB | – | simoom 2.4x less6,912 KB vs simoom 2,880 KB |
| Peak memory while serving the JSON API (PSS) | simoom 6.0x less17.4 MB vs simoom 2,988 KB | simoom 5.5x less16.0 MB vs simoom 2,988 KB | – | simoom 3.7x less10.9 MB vs simoom 2,988 KB |
| Peak memory while serving HTTPS (PSS) | – | – | simoom 2.3x less16.4 MB vs simoom 7,385 KB | – |
Each cell: simoom against that server on the same droplet in the same round, median of rounds, then the server's value against simoom's in the same runs. Every measure and every other server: the benchmarks.
When to pick hyper or axum instead
For many Rust services, axum on tokio is the right answer, and that is fine.
Pick hyper or axum when
- Your program is already async and runs on tokio, or uses libraries that need it (database drivers, gRPC, cloud SDKs).
- You want tower middleware, axum extractors, OpenAPI generators and the rest of the ecosystem.
- Your team already knows axum, and hiring and onboarding matter more than footprint.
- You need a library with years of production use and many contributors. simoom is at 0.1.0.
- You want independently audited TLS: pair hyper with rustls. In campaign 4, axum with rustls also completed 24% more full TLS 1.3 handshakes per second than simoom (11,633 against 9,530).
- Memory at rest matters more than memory per connection: hyper's idle process used 1,374 KB against simoom's 1,769 KB.
Pick simoom when
- You don't want an async runtime in a program that never needed one: a CLI, a desktop app, a daemon, a device.
- Every third-party crate is a review: simoom has none, from the TLS handshake to QUIC.
- Binary size and start-up count: about 215 KB added for HTTP/1.1; in campaign 4 the default build was 735 KB stripped against hyper's 2,069 KB, and answered its first request 0.94 ms after process start against hyper's 1.48 ms.
- You want HTTP/3 without a separate experimental stack.
- Throughput per core matters and you can live with a smaller API.
What the code looks like
No async, no runtime to start. A route is a function that takes the request and a reply.
use simoom::{Outcome, Reply, Request, Router, Server};
let router = Router::new()
.get("/health", |_: &mut Request<'_>, r: Reply<'_>| r.body_static(b"ok"))
.get("/users/:id", |req: &mut Request<'_>, r: Reply<'_>| -> Outcome {
let id = req.param_str("id").unwrap_or("").to_string();
r.header("content-type", "application/json").body(format!("{{\"id\":\"{id}\"}}"))
});
let server = Server::builder().listen("0.0.0.0:8080").service(router).start().expect("start");
Slow work (a database query, a call to another service) goes to the bounded offload pool with simoom::blocking, or to your own threads with Reply::defer. See routing and slow work.
How it was measured
Setup
- DigitalOcean
c-4droplets (4 dedicated vCPUs, Xeon Platinum 8280, Bangalore), Ubuntu 24.04. - One server droplet, two separate load droplets, open-loop load: requests arrive at a fixed rate whether or not the server keeps up.
- hyper 1.12.0, axum 0.8.9, axum-server 0.7.3 with rustls 0.23.46 and actix-web 4.18.2, each from its pinned
Cargo.lock; simoom from the measured commit.
Rules
- Headline: the highest steady rate whose p99 stays at or under 1 ms (5 ms for HTTP/2 with 100 streams and for a new connection per request).
- Campaign 4, 2026-11-12: simoom ran on the same droplet in the same round as each competitor; every figure is the median of rounds (three or one per measure; the benchmarks list the runs).
- Only servers on the same droplet are compared; raw HDR logs and scripts are in
bench/results/.
Method and every number: benchmark method, the losses.
Common questions
Is simoom faster than hyper?
In benchmark campaign 4 (2026-11-12), with both servers on the same 4-vCPU droplet in the same rounds, simoom answered 137,889 status requests per second with 99% within 1 ms at 256 clients against hyper's 110,931 (24% more), and used 23.6 µs of CPU per request at a fixed 50,000 req/s against hyper's 32.6 µs (26% less). Of 21 measures simoom was ahead of hyper on 15, level on 5 and behind on 1: hyper's process used less memory at rest (1,374 KB against 1,769 KB). Start-up of simoom's full server (with TLS 1.3 and HTTP/3) was level with hyper, 1.46 ms against 1.48 ms; simoom's default build started in 0.94 ms.
Can I use hyper without tokio?
hyper is built for async Rust and in practice runs on tokio. If the goal is no async runtime at all, simoom is built that way: a service is a plain function on an event loop thread, and slow work goes to a bounded offload pool or to your own threads.
hyper vs axum vs simoom: which should I use?
For a typical web API on tokio with middleware, extractors and a team that knows the ecosystem, axum is the safer choice. For a low-level HTTP library inside the tokio world, use hyper. For an embedded server with no async runtime, no third-party crates, HTTP/3 and a small binary, use simoom.
Does simoom work with tower middleware?
No. simoom has its own Service type, a Router and optional helpers for compression, CORS, cookies, multipart, conditional requests and authentication. It does not implement tower's traits, so tower layers and axum extractors do not plug into it.
Does hyper support HTTP/3?
Not by itself. HTTP/3 for the hyper ecosystem lives in the separate h3 crate, which describes itself as experimental. simoom ships HTTP/3, QUIC v1 and TLS 1.3 in-tree as cargo features.
Try it next to your hyper service
Add the crate, serve the same route on another port, and measure both on your own machine.
cargo add simoom