If you've spent any time looking at Go HTTP performance, you've probably heard of fasthttp. It's been around for years, and its pitch is simple: it's way faster than net/http. But how does it actually stack up against everything else — not just Go frameworks, but Rust, C, Zig, and the whole zoo?
I ran go-fasthttp through HttpArena, an open benchmark suite that tests frameworks across a bunch of realistic scenarios. Here's what I found.
What is fasthttp?
fasthttp is a high-performance HTTP library for Go built by Aliaksandr Valialkin. Unlike Go's standard net/http, it avoids allocations wherever possible. Instead of creating a new request/response object per request, it pools and reuses them. It's basically Go's answer to "what if we cared about garbage collection pressure?"
The HttpArena implementation uses reuseport to spin up one listener per CPU core, which is a neat trick — each goroutine gets its own socket listener, reducing lock contention.
The Numbers
Let's get into it.
Baseline (Plain Text Responses)
| Connections | RPS | Avg Latency | Rank |
|---|---|---|---|
| 512 | 1,337,651 | 382us | #14/30 |
| 4,096 | 1,478,446 | 2.76ms | #13/30 |
| 16,384 | 1,310,081 | 10.63ms | #13/30 |
Middle of the pack. Honestly, for baseline text responses, 1.3-1.5M RPS is solid — but ringzero (C) is hitting 3.4M at the top. The Rust and C frameworks dominate here. For plain "return a string" work, fasthttp lands in the top half but doesn't lead.
Compared to other Go frameworks:
- go-fasthttp: 1,478,446 rps
- caddy: 582,248 rps
- echo: 456,383 rps
- gin: 446,160 rps
It's about 3x faster than standard Go HTTP frameworks. That's the fasthttp promise delivering.
Pipelined Requests — Where fasthttp Shines
This is where things get interesting.
| Connections | RPS | Rank |
|---|---|---|
| 512 | 16,786,953 | #4/30 |
| 4,096 | 17,808,031 | #4/30 |
| 16,384 | 16,403,972 | #4/30 |
17.8 million requests per second. That's not a typo. fasthttp handles HTTP pipelining exceptionally well. It sits right behind ringzero (C, 46.8M), blitz (Zig, 39.5M), and actix (Rust, 23M).
Top 5 at 4,096 connections:
- ringzero (C) — 46,803,504 rps
- blitz (Zig) — 39,534,054 rps
- actix (Rust) — 23,001,200 rps
- go-fasthttp (Go) — 17,808,031 rps
- hyper (Rust) — 16,273,142 rps
Being #4 overall and beating hyper (Rust!) at pipelining is impressive. The zero-allocation design really pays off when you're hammering the same connection with sequential requests. Meanwhile, gin and echo are down at ~1M rps — a 17x gap.
Mixed Workload — The Crown Jewel
Here's the headline result:
| Connections | RPS | Rank |
|---|---|---|
| 4,096 | 87,964 | #1/27 |
| 16,384 | 164,178 | #1/27 |
go-fasthttp wins the mixed workload test. Both connection levels. Beating actix, beating nginx, beating everything.
Top 5 at 16,384 connections:
- go-fasthttp (Go) — 164,178 rps
- actix (Rust) — 157,549 rps
- salvo (Rust) — 67,520 rps
- bun (TS) — 64,614 rps
- node (JS) — 55,988 rps
The mixed workload combines baseline requests, JSON processing, compression, uploads, and database queries into a single test. It's the closest thing to a "real-world" scenario in the suite. And fasthttp tops it — barely edging out actix at 16K connections, and more convincingly at 4K (87,964 vs 75,948 rps).
This tells us something important: fasthttp's architecture handles diverse workloads better than almost anything else. It's not just fast at one thing.
JSON Processing
| Connections | RPS | Rank |
|---|---|---|
| 4,096 | 311,030 | #18/29 |
| 16,384 | 265,535 | #22/29 |
| 32,768 | 149,159 | #10/14 |
This is the weak spot. Bottom half in JSON serialization. Go's encoding/json is famously not great, and it shows here. nginx leads at 1.18M rps (it's serving pre-computed static JSON), and actix with serde is at ~1M.
For the Go comparison: fasthttp still beats caddy (308K), gin (169K), and echo (158K) — but only by a modest margin on JSON. The serialization bottleneck is the equalizer.
Compression
| Connections | RPS | Rank |
|---|---|---|
| 4,096 | 14,771 | #5/28 |
| 16,384 | 13,736 | #5/28 |
Solid #5 finish. The implementation uses compress/flate with BestSpeed level — trading compression ratio for throughput. blitz (Zig) leads at 89K rps, then actix, salvo, and h2o. But fasthttp holds its own at nearly 15K rps, which is almost 2x the caddy result (8,147 rps).
Upload Handling
| Connections | RPS | Rank |
|---|---|---|
| 256 | 910 | #6/29 |
| 512 | 842 | mid-table |
Mid-table for uploads. spring-jvm actually wins this category at 1,294 rps — the JVM's buffered I/O handling pays off for large body ingestion.
Limited Connections — The Achilles' Heel
| Connections | RPS | Rank |
|---|---|---|
| 512 | 147,847 | #26/30 |
| 4,096 | 636,185 | #13/30 |
Oof. At 512 connections, fasthttp drops to #26/30. h2o leads at 1.6M rps, and most frameworks do significantly better. This is surprising — you'd expect a fast framework to shine with fewer connections too.
The likely culprit? fasthttp's architecture is optimized for high concurrency. The reuseport multi-listener design and goroutine-per-connection model have overhead that doesn't amortize well with limited connections. At 4,096 connections it recovers to #13, which matches baseline performance. This framework wants a lot of concurrent work to hit its stride.
Reading the Source Code
The HttpArena implementation reveals some interesting architectural choices:
SO_REUSEPORT with per-CPU listeners:
for i := 0; i < numCPU; i++ {
go func() {
ln, _ := reuseport.Listen("tcp4", ":8080")
s := &fasthttp.Server{Handler: handler}
s.Serve(ln)
}()
}
Each CPU core gets its own listener socket. The kernel distributes incoming connections across them. This avoids the thundering herd problem and reduces lock contention — a big deal at high concurrency.
Manual path-based routing: No router library. Just a switch on ctx.Path(). Zero overhead from regex matching or tree traversal. For a benchmark this makes sense, but it also shows the performance ceiling when you strip away routing abstractions.
Pre-computed large JSON responses: The compression endpoint pre-marshals the JSON during startup and serves the cached bytes. Smart — you avoid re-serializing on every request.
Memory usage: 188 MiB for baseline, 716 MiB for JSON, but 10.2 GiB for mixed workloads. The garbage collector is working hard under mixed load. That's the Go trade-off — the runtime manages memory for you, but at scale it adds up.
Who Should Use fasthttp?
Great fit if:
- You're building Go services that need raw throughput
- Your workload involves lots of concurrent connections
- You need pipelining support (API gateways, proxies)
- You want to stay in the Go ecosystem but need better performance than net/http
- Mixed workloads (the typical real-world pattern)
Maybe look elsewhere if:
- You have few connections and need maximum per-connection throughput
- JSON serialization is your bottleneck (consider Rust frameworks or use a faster JSON lib like sonic/jsoniter)
- You need HTTP/2 or HTTP/3 (fasthttp is HTTP/1.1 only)
- You want a batteries-included framework with routing, middleware, etc. (that's echo/gin territory)
The Verdict
go-fasthttp is a fascinating framework. It's not the fastest at any single micro-benchmark (C and Zig own that space), but it's the most versatile performer in the entire HttpArena suite. Winning the mixed workload test — beating actix, nginx, and h2o — is a big deal because that's the test that most resembles real production traffic.
The pipelining numbers (17.8M rps, #4 overall) show its architecture is fundamentally sound. The limited-connection weakness is real but only matters in specific scenarios. And being 3-17x faster than standard Go frameworks (gin, echo, caddy) makes it the clear choice for performance-sensitive Go work.
Just watch out for that encoding/json bottleneck. Pair fasthttp with a faster serializer and you might climb even higher.
All data from HttpArena (GitHub). Check the full results if you want to compare frameworks yourself.
What framework should I deep-dive next? Drop a comment!
Top comments (0)