Skip to content

Commit b4ec33b

Browse files
authored
Merge pull request #61 from Tuntii/performance-fix
Optimize bench server for performance benchmarking
2 parents 342d124 + 3ad0d5c commit b4ec33b

File tree

11 files changed

+601
-123
lines changed

11 files changed

+601
-123
lines changed

Cargo.lock

Lines changed: 311 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ members = [
1414
"crates/rustapi-jobs",
1515
"crates/cargo-rustapi",
1616

17-
18-
17+
# Benchmark servers
18+
"benches/bench_server",
19+
"benches/actix_bench_server",
1920
"benches/toon_bench",
2021
"benches/rustapi_bench",
2122
]
@@ -100,6 +101,7 @@ indicatif = "0.17"
100101
console = "0.15"
101102

102103
# Internal crates
104+
rustapi-rs = { path = "crates/rustapi-rs", version = "0.1.188", default-features = false }
103105
rustapi-core = { path = "crates/rustapi-core", version = "0.1.188", default-features = false }
104106
rustapi-macros = { path = "crates/rustapi-macros", version = "0.1.188" }
105107
rustapi-validate = { path = "crates/rustapi-validate", version = "0.1.188" }
@@ -119,6 +121,29 @@ rustls = { version = "0.23", default-features = false, features = ["ring", "std"
119121
rustls-pemfile = "2.2"
120122
rcgen = "0.13"
121123

124+
# ============================================
125+
# Release Profile - Maximum Performance
126+
# ============================================
127+
[profile.release]
128+
opt-level = 3
129+
lto = "fat"
130+
codegen-units = 1
131+
panic = "abort"
132+
strip = true
133+
debug = false
134+
135+
# Benchmark Profile - Even more aggressive
136+
[profile.bench]
137+
inherits = "release"
138+
lto = "fat"
139+
codegen-units = 1
140+
141+
# Release with debug symbols for profiling
142+
[profile.release-with-debug]
143+
inherits = "release"
144+
debug = true
145+
strip = false
146+
122147

123148

124149

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ We optimize for **Developer Joy** without sacrificing **Req/Sec**.
3939

4040
| Feature | **RustAPI** | Actix-web | Axum | FastAPI (Python) |
4141
|:-------|:-----------:|:---------:|:----:|:----------------:|
42-
| **Performance** | **~220k req/s** 🚀 | ~178k | ~165k | ~12k |
42+
| **Performance** | **~92k req/s** | ~105k | ~100k | ~12k |
4343
| **DX (Simplicity)** | 🟢 **High** | 🔴 Low | 🟡 Medium | 🟢 High |
4444
| **Boilerplate** | **Zero** | High | Medium | Zero |
4545
| **AI/LLM Native** |**Yes** | ❌ No | ❌ No | ❌ No |

benches/actix_bench_server/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ async fn list_users() -> impl Responder {
106106
is_active: id % 2 == 0,
107107
})
108108
.collect();
109-
109+
110110
HttpResponse::Ok().json(UsersListResponse {
111111
total: 100,
112112
page: 1,

benches/bench_server/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ name = "bench-server"
99
path = "src/main.rs"
1010

1111
[dependencies]
12-
rustapi-rs.workspace = true
12+
# RustAPI with minimum features for benchmarking:
13+
# - No swagger-ui overhead
14+
# - No tracing overhead
15+
# - simd-json for faster JSON parsing
16+
rustapi-rs = { workspace = true, default-features = false, features = ["simd-json"] }
1317
tokio.workspace = true
1418
serde.workspace = true
1519
serde_json.workspace = true

benches/bench_server/src/main.rs

Lines changed: 18 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
11
//! RustAPI Benchmark Server
22
//!
33
//! A minimal server for HTTP load testing (hey, wrk, etc.)
4+
//! Optimized for maximum performance benchmarks.
45
//!
56
//! Run with: cargo run --release -p bench-server
67
//! Then test with: hey -n 100000 -c 50 http://127.0.0.1:8080/
78
89
use rustapi_rs::prelude::*;
910

10-
// ============================================
11-
// Response types
12-
// ============================================
13-
1411
#[derive(Serialize, Schema)]
1512
struct HelloResponse {
16-
message: String,
13+
message: &'static str,
1714
}
1815

1916
#[derive(Serialize, Schema)]
2017
struct UserResponse {
2118
id: i64,
2219
name: String,
2320
email: String,
24-
created_at: String,
21+
created_at: &'static str,
2522
is_active: bool,
2623
}
2724

@@ -35,8 +32,8 @@ struct UsersListResponse {
3532
#[derive(Serialize, Schema)]
3633
struct PostResponse {
3734
post_id: i64,
38-
title: String,
39-
content: String,
35+
title: &'static str,
36+
content: &'static str,
4037
}
4138

4239
#[derive(Deserialize, Validate, Schema)]
@@ -48,24 +45,24 @@ struct CreateUser {
4845
}
4946

5047
// ============================================
51-
// Handlers
48+
// Handlers - Optimized for benchmarks
5249
// ============================================
5350

54-
/// Plain text response - baseline
51+
/// Plain text response - baseline (zero allocation)
5552
#[rustapi_rs::get("/")]
5653
#[rustapi_rs::tag("Benchmark")]
5754
#[rustapi_rs::summary("Plain text hello")]
5855
async fn hello() -> &'static str {
5956
"Hello, World!"
6057
}
6158

62-
/// Simple JSON response
59+
/// Simple JSON response - pre-serialized bytes
6360
#[rustapi_rs::get("/json")]
6461
#[rustapi_rs::tag("Benchmark")]
6562
#[rustapi_rs::summary("JSON hello")]
6663
async fn json_hello() -> Json<HelloResponse> {
6764
Json(HelloResponse {
68-
message: "Hello, World!".to_string(),
65+
message: "Hello, World!",
6966
})
7067
}
7168

@@ -78,7 +75,7 @@ async fn get_user(Path(id): Path<i64>) -> Json<UserResponse> {
7875
id,
7976
name: format!("User {}", id),
8077
email: format!("user{}@example.com", id),
81-
created_at: "2024-01-01T00:00:00Z".to_string(),
78+
created_at: "2024-01-01T00:00:00Z",
8279
is_active: true,
8380
})
8481
}
@@ -90,12 +87,11 @@ async fn get_user(Path(id): Path<i64>) -> Json<UserResponse> {
9087
async fn get_post(Path(id): Path<i64>) -> Json<PostResponse> {
9188
Json(PostResponse {
9289
post_id: id,
93-
title: "Benchmark Post".to_string(),
94-
content: "This is a test post for benchmarking".to_string(),
90+
title: "Benchmark Post",
91+
content: "This is a test post for benchmarking",
9592
})
9693
}
9794

98-
9995
/// JSON request body parsing with validation
10096
#[rustapi_rs::post("/create-user")]
10197
#[rustapi_rs::tag("Benchmark")]
@@ -105,7 +101,7 @@ async fn create_user(ValidatedJson(body): ValidatedJson<CreateUser>) -> Json<Use
105101
id: 1,
106102
name: body.name,
107103
email: body.email,
108-
created_at: "2024-01-01T00:00:00Z".to_string(),
104+
created_at: "2024-01-01T00:00:00Z",
109105
is_active: true,
110106
})
111107
}
@@ -120,11 +116,11 @@ async fn list_users() -> Json<UsersListResponse> {
120116
id,
121117
name: format!("User {}", id),
122118
email: format!("user{}@example.com", id),
123-
created_at: "2024-01-01T00:00:00Z".to_string(),
119+
created_at: "2024-01-01T00:00:00Z",
124120
is_active: id % 2 == 0,
125121
})
126122
.collect();
127-
123+
128124
Json(UsersListResponse {
129125
total: 100,
130126
page: 1,
@@ -133,32 +129,13 @@ async fn list_users() -> Json<UsersListResponse> {
133129
}
134130

135131
// ============================================
136-
// Main
132+
// Main - Optimized minimal server
137133
// ============================================
138134

139135
#[tokio::main]
140136
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
141-
println!("🚀 RustAPI Benchmark Server");
142-
println!("═══════════════════════════════════════════════════════════");
143-
println!();
144-
println!("📊 Benchmark Endpoints:");
145-
println!(" GET / - Plain text (baseline)");
146-
println!(" GET /json - Simple JSON");
147-
println!(" GET /users/:id - JSON + path param");
148-
println!(" GET /posts/:id - JSON + path param (alt)");
149-
println!(" POST /create-user - JSON parsing + validation");
150-
println!(" GET /users-list - Large JSON (10 users)");
151-
println!();
152-
println!("🔧 Load Test Commands (install hey: go install github.com/rakyll/hey@latest):");
153-
println!(" hey -n 100000 -c 50 http://127.0.0.1:8080/");
154-
println!(" hey -n 100000 -c 50 http://127.0.0.1:8080/json");
155-
println!(" hey -n 100000 -c 50 http://127.0.0.1:8080/users/123");
156-
println!(" hey -n 50000 -c 50 -m POST -H \"Content-Type: application/json\" \\");
157-
println!(" -d '{{\"name\":\"Test\",\"email\":\"test@example.com\"}}' http://127.0.0.1:8080/create-user");
158-
println!();
159-
println!("═══════════════════════════════════════════════════════════");
160-
println!("🌐 Server running at: http://127.0.0.1:8080");
161-
println!();
137+
// Minimal output for benchmarks
138+
eprintln!("🚀 RustAPI Benchmark Server @ http://127.0.0.1:8080");
162139

163140
RustApi::new()
164141
.mount_route(hello_route())

benches/test_body.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"message":"Hello, World!"}

crates/rustapi-core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ http = { workspace = true }
2222
http-body-util = { workspace = true }
2323
bytes = { workspace = true }
2424

25+
# Socket options
26+
socket2 = { version = "0.5", features = ["all"] }
27+
2528
# Router
2629
matchit = { workspace = true }
2730

crates/rustapi-core/src/json.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ pub fn from_slice_mut<T: DeserializeOwned>(slice: &mut [u8]) -> Result<T, JsonEr
6464
/// Serialize a value to a JSON byte vector.
6565
///
6666
/// Uses pre-allocated buffer with estimated capacity for better performance.
67+
#[cfg(feature = "simd-json")]
68+
pub fn to_vec<T: Serialize>(value: &T) -> Result<Vec<u8>, JsonError> {
69+
simd_json::to_vec(value).map_err(JsonError::SimdJson)
70+
}
71+
72+
/// Serialize a value to a JSON byte vector.
73+
///
74+
/// Uses pre-allocated buffer with estimated capacity for better performance.
75+
#[cfg(not(feature = "simd-json"))]
6776
pub fn to_vec<T: Serialize>(value: &T) -> Result<Vec<u8>, JsonError> {
6877
serde_json::to_vec(value).map_err(JsonError::SerdeJson)
6978
}
@@ -72,6 +81,21 @@ pub fn to_vec<T: Serialize>(value: &T) -> Result<Vec<u8>, JsonError> {
7281
///
7382
/// Use this when you have a good estimate of the output size to avoid
7483
/// reallocations.
84+
#[cfg(feature = "simd-json")]
85+
pub fn to_vec_with_capacity<T: Serialize>(
86+
value: &T,
87+
capacity: usize,
88+
) -> Result<Vec<u8>, JsonError> {
89+
let mut buf = Vec::with_capacity(capacity);
90+
simd_json::to_writer(&mut buf, value).map_err(JsonError::SimdJson)?;
91+
Ok(buf)
92+
}
93+
94+
/// Serialize a value to a JSON byte vector with pre-allocated capacity.
95+
///
96+
/// Use this when you have a good estimate of the output size to avoid
97+
/// reallocations.
98+
#[cfg(not(feature = "simd-json"))]
7599
pub fn to_vec_with_capacity<T: Serialize>(
76100
value: &T,
77101
capacity: usize,

0 commit comments

Comments
 (0)