Quarkus Native vs Rust frameworks: Performance comparison for hello world case
This is a highly requested article.
Numerous people have requested to compare Quarkus running natively (image built using graal) vs popular frameworks on the Rust side (Actix, Axum, Rocket, & Warp). We’ve already seen a similar comparison with Spring Boot Webflux Native (the reactive arm of Spring Boot). We’ve seen that reactive Spring Boot gave a very tough competition to Rust frameworks. Quarkus is generally known to be very fast. The competition is expected to be tougher.
Here it is. This article compares Quarkus native with the four most popular frameworks on the Rust side.
Note: This isn’t really an apple to apple comparison. Neither of the Rust frameworks provide a fraction of the extensive features provided by Quarkus. We’ll still compare them because of the reader’s interest.
Test setup
All tests are executed on MacBook Pro M2 with 16G RAM & 8+4 CPU cores. The load tester is Bombardier (Written in Go). The software versions are:
- Java 21 Graal CE
- Quarkus 3.6.3
- Rust 1.75.0
The application code is as follows:
Java
HelloWorldApplication.java
package org.acme;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import io.smallrye.common.annotation.NonBlocking;
@Path("/")
public class HelloWorldApplication {
@GET
@NonBlocking
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello World!";
}
}
Rust
Actix
cargo.toml
[package]
name = "hello_world_actix"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-web = "4.4.1"
main.rs
use actix_web::{get, App, HttpServer, Responder};
#[get("/")]
async fn index() -> impl Responder {
"Hello World!"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 3000))?
.run()
.await
}
Axum
cargo.toml
[package]
name = "axum_hello_world"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = "0.7.3"
hyper = { version = "1.1.0", features = ["full"] }
tokio = { version = "1.35.1", features = ["full"] }
tower = "0.4.13"
main.rs
use axum::{
routing::get,
Router,
};
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(|| async { "Hello World!" }));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
Rocket
cargo.toml
[package]
name = "rocket_hello_world"
version = "0.1.0"
edition = "2021"
[dependencies]
rocket = "=0.5.0"
main.rs
#[macro_use] extern crate rocket;
#[get("/")]
fn index() -> &'static str {
"Hello world!"
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![index])
}
Warp
cargo.toml
[package]
name = "warp_hello_world"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1.35.1", features = ["full"] }
warp = "0.3"
main.rs
use warp::Filter;
#[tokio::main]
async fn main() {
let routes = warp::path::end().map(|| "Hello World!");
warp::serve(routes)
.run(([127, 0, 0, 1], 3000))
.await;
}
NOTE: All the rust code has been built in RELEASE mode
Results
Each test consists of running 5M for 100, and 300 concurrent connections.
The results in chart form are as follows:
Time taken & RPS
Latencies
Resource usage
Impressions
As expected, Quarkus gave a very tough competition to the mighty Rust. Quarkus’s performance is at part with all the Rust frameworks. The CPU usage is also comparable. The only major difference is the memory usage, which is very low for Rust. You can say that this is a battle of frameworks rather than languages.
Winner: None or Both