Apples to Apples: Why Koru Is Hard to Benchmark
Yesterday we posted that Orisha beats nginx by 51% for static file serving. The response was predictable: “That’s not a fair comparison. nginx does X, Y, Z that you’re not doing.”
They’re right. And they’re missing the point.
The Benchmark Trap
Traditional benchmarks compare implementations of the same architecture:
- Web server A vs web server B (both read files at runtime)
- JSON parser A vs JSON parser B (both parse at runtime)
- ORM A vs ORM B (both generate SQL at runtime)
This makes sense when the architecture is fixed. If you’re choosing between Express and Fastify, benchmark them. They’re solving the same problem the same way.
But what if you’re not?
The Koru Approach
Koru doesn’t optimize the same operations faster. It deletes operations entirely.
Static file serving:
- nginx: Request → stat() → open() → read()/sendfile() → format headers → write()
- Orisha: Request → lookup blob → write()
The nginx path is highly optimized. Kernel page cache, sendfile zero-copy, worker pools. But it’s still doing work that Orisha simply doesn’t do.
Configuration:
- Traditional: Read file/env → parse → validate → store → lookup at runtime
- Koru: Configuration is a typed event. Values flow through at compile-time OR runtime. Framework doesn’t care.
Dependency injection:
- Traditional: Container registration → factory resolution → interface dispatch → runtime instantiation
- Koru:
~abstract eventdeclares shape.~implprovides it. Compile-time resolution, zero dispatch.
The Configuration Pattern
Here’s a concrete example. Orisha needs configuration: port, worker count, static directory, etc.
Traditional approach:
// config.json
{ "port": 3000, "workers": 4 }
// app.js
const config = JSON.parse(fs.readFileSync('config.json'));
const port = config.port || 3000; Runtime file I/O. Runtime parsing. Runtime fallback logic. Every request pays for this (even if cached in memory, the lookup exists).
Koru approach:
// In orisha framework
~pub abstract event config {}
| configuration { port: u16, workers: u8, static_dir: []const u8 }
// Default implementation
~config = configuration { port: 3000, workers: 4, static_dir: "public" } The framework declares what it needs. Now the user decides how to provide it:
// Option 1: Compile-time constant (baked into binary)
~impl orisha:config = configuration { port: 8080, workers: 8 }
// Option 2: Environment variables (runtime)
~impl orisha:config =
env:get_all()
| vars v |> configuration {
port: v.PORT orelse 3000,
workers: v.WORKERS orelse 4,
static_dir: v.STATIC_DIR orelse "public"
}
// Option 3: Remote config server (runtime with fallback)
~impl orisha:config =
http:get(url: "https://config.internal/orisha")
| response r |> parse_config(body: r.body)
| ok c |> configuration { port: c.port, workers: c.workers }
| error |> configuration { port: 3000, workers: 4 } // fallback
| error |> configuration { port: 3000, workers: 4 } // network failed The framework code stays exactly the same. It calls config() and handles | configuration c |>. Whether that configuration was baked in at compile time or fetched from a server in Tokyo—Orisha doesn’t know and doesn’t care.
This is dependency inversion without:
- Interface definitions
- Factory registrations
- Container configurations
- Runtime reflection
Just events and branches. The same primitives that handle HTTP requests handle configuration. The same primitives that handle database queries handle dependency injection.
Why This Is Hard to Benchmark
How do you benchmark “deleted work”?
If we benchmark Orisha against nginx, nginx advocates say “but nginx can reload config without restart!” True. But:
- How often do you actually reload config?
- What’s the cost of that capability when you’re NOT using it?
- Is “config reload” even the right abstraction for your use case?
If we benchmark the configuration pattern, what do we compare against?
- Against
JSON.parse()? But Koru can also parse JSON at runtime. - Against Spring DI? But that’s solving a different shape of problem.
- Against compile-time constants? But Koru can also fetch from config servers.
The honest answer: traditional benchmarks measure the wrong thing.
What We Actually Measure
Instead of “operations per second,” we think about:
1. Work per request
Not “how fast can you do X” but “how much X do you need to do?”
Orisha serves 148K req/s not because it has faster I/O (it uses the same syscalls as nginx) but because it does less work per request:
- No stat() to check file existence
- No header formatting
- No content-length calculation
- No ETag computation
All of that happened once, at compile time.
2. Abstraction cost
In most languages, abstractions have runtime cost. Interfaces require dispatch. Generics require monomorphization overhead. Dependency injection requires container lookups.
In Koru, ~abstract event compiles to direct calls. The abstraction exists in source code, not in the binary. We verified this: Koru’s ~capture matches hand-written Zig within measurement noise (17.0ms vs 17.3ms for 100M iterations).
3. Decision points
Every runtime decision is potential latency. “If config.gzip then compress()” is a branch. A thousand requests means a thousand branches.
Koru moves decisions to compile time when possible:
// This becomes a compile-time constant
const should_gzip = config.gzip;
// At runtime, this is just unconditional code (or not present at all) The branch predictor doesn’t need to predict what the compiler already knew.
The Real Comparison
When someone asks “is Koru faster than X?”, the honest answer is:
For the same architecture: Probably similar. Koru compiles to Zig, which compiles to LLVM. The generated code is competitive with any systems language.
For a different architecture: Potentially much faster, but you’re not comparing the same thing. You’re comparing “runtime work” against “compile-time work that already happened.”
Orisha isn’t a faster nginx. It’s a different kind of web server—one where the response bytes exist before the first request arrives.
The configuration pattern isn’t a faster DI container. It’s a different kind of abstraction—one where the framework doesn’t know or care when its dependencies are resolved.
The Philosophy
Traditional performance optimization: Do the same work faster.
Koru performance philosophy: Do less work. Ideally, none.
This isn’t always possible. Database queries happen at runtime. User input arrives at runtime. Network calls happen at runtime.
But when you control the compiler, you get to ask: Does this NEED to happen at runtime?
- Static files? Compile-time.
- Route tables? Compile-time.
- Configuration with known values? Compile-time.
- Configuration from environment? Runtime, but framework doesn’t change.
- Configuration from remote server? Runtime, same code path.
The abstraction (~abstract event) is the same whether the implementation is compile-time or runtime. The benchmark would measure different things depending on which implementation you choose.
That’s why Koru is hard to benchmark. The interesting optimization isn’t in the code—it’s in the absence of code.
What’s Next
We’re working on:
- Dynamic content: Koru flows handling requests at runtime, with database queries
- Compile-time gzip: Pre-compress static files, serve even smaller blobs
- Docker integration: Declare container images in Koru, generate FROM scratch images
Each of these follows the same pattern: push work to compile time when possible, make runtime work pay for itself when not.
The goal isn’t “fastest web server.” The goal is a language where the obvious code is the fast code—where good abstractions don’t cost performance, and where the compiler is a partner in optimization, not an obstacle to it.
Koru is an open-source language focused on compile-time optimization and zero-cost abstractions. Learn more at korulang.org.