Skip to content

Commit 4454a76

Browse files
[rust/axum] Axum performance improvements (#9484)
* perf: switch plaintext and json to one runtime per thread, improving performance. * perf: remove need for additional vec * perf: reduce length of query parameter * perf: increase strength of inlining hint * perf: reduce query length * perf: shorten path and use references * bug: increased length of route in line with requirements * build: upgraded dependencies * bug: increased mongodb pool size * perf: replacement of moka with quick_cache * perf: simplification of mass update query * perf: improvements to random number generation * build: upgrade rust version * fix: re-added ordering to remove deadlocks * perf: use of mimalloc
1 parent 30f4876 commit 4454a76

File tree

13 files changed

+828
-738
lines changed

13 files changed

+828
-738
lines changed

frameworks/Rust/axum/Cargo.lock

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

frameworks/Rust/axum/Cargo.toml

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,48 +39,49 @@ simd-json = [
3939
]
4040

4141
[dependencies]
42-
axum = { version = "0.7.6", default-features = false, features = [
42+
axum = { version = "0.7.9", default-features = false, features = [
4343
"json",
4444
"query",
4545
"http1",
4646
"tokio",
4747
] }
4848
deadpool = { version = "0.12.1", features = ["rt_tokio_1", "serde", "managed"] }
49-
deadpool-postgres = { version = "0.14.0", features = ["rt_tokio_1", "serde"] }
49+
deadpool-postgres = { version = "0.14.1", features = ["rt_tokio_1", "serde"] }
5050
dotenv = "0.15.0"
51-
futures = "0.3.30"
52-
futures-util = "0.3.30"
53-
mongodb = { version = "2.8.0", features = [
51+
futures = "0.3.31"
52+
futures-util = "0.3.31"
53+
mongodb = { version = "3.1.1", features = [
5454
"zstd-compression",
5555
"snappy-compression",
5656
"zlib-compression",
5757
] }
5858
num_cpus = "1.16.0"
5959
rand = { version = "0.8.5", features = ["small_rng"] }
60-
serde = { version = "1.0.196", features = ["derive"] }
61-
serde_json = "1.0.127"
62-
sqlx = { version = "0.7.3", features = [
60+
serde = { version = "1.0.216", features = ["derive"] }
61+
serde_json = "1.0.134"
62+
sqlx = { version = "0.8.2", features = [
6363
"postgres",
6464
"macros",
6565
"runtime-tokio",
6666
"tls-rustls",
6767
] }
68-
tokio = { version = "1.39.3", features = ["full"] }
68+
tokio = { version = "1.42.0", features = ["full"] }
6969
tokio-pg-mapper = { version = "0.2.0" }
7070
tokio-pg-mapper-derive = { version = "0.2.0" }
71-
tokio-postgres = { version = "0.7.11" }
72-
tower = { version = "0.5.0", features = ["util"] }
73-
tower-http = { version = "0.5.2", features = ["set-header"] }
71+
tokio-postgres = { version = "0.7.12" }
72+
tower = { version = "0.5.2", features = ["util"] }
73+
tower-http = { version = "0.6.2", features = ["set-header"] }
7474
yarte = "0.15.7"
75-
simd-json = { version = "0.13.8", optional = true }
76-
axum-core = { version = "0.4.3", optional = true }
75+
simd-json = { version = "0.14.3", optional = true }
76+
axum-core = { version = "0.4.5", optional = true }
7777
mime = { version = "0.3.17", optional = true }
78-
bytes = { version = "1.5.0", optional = true }
79-
serde_path_to_error = { version = "0.1.15", optional = true }
80-
moka = { version = "0.12.8", features = ["future"] }
81-
socket2 = "0.5.7"
82-
hyper = { version = "1.4", features = ["server", "http1"] }
78+
bytes = { version = "1.9.0", optional = true }
79+
serde_path_to_error = { version = "0.1.16", optional = true }
80+
socket2 = "0.5.8"
81+
hyper = { version = "1.5", features = ["server", "http1"] }
8382
hyper-util = { version = "0.1", features = ["tokio", "server-auto", "http1"] }
83+
quick_cache = "0.6.9"
84+
mimalloc = "0.1.43"
8485

8586

8687
[profile.release]

frameworks/Rust/axum/README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ built with Tokio, Tower, and Hyper.
2727
## Notable Points (both performance and build)
2828

2929
- Use of `async`.
30-
- Use of most recent versions of Rust, `axum` and dependencies.
30+
- Use of the most recent versions of Rust, `axum` and dependencies.
3131
- (Disabled by default) Compile-time swap-in of `simd-json` instead of `serde_json` for faster JSON serialization.
3232
- Release binaries are stripped and compiled with CPU native.
33-
- Sockets configured with TCP_NODELAY and to support an increased number of pending connections.
33+
- Sockets configured with `TCP_NODELAY` and to support an increased number of pending connections.
34+
- For very simple benchmarks, use of a separate, single-threaded Tokio runtime for each thread.
3435
- Server configured to serve HTTP/1 only, with no need for websockets.
3536
- Separation of build and deployment containers using multi-stage builds.
3637
- Deployment into Google's minimal `distroless-cc` container.
@@ -39,8 +40,5 @@ built with Tokio, Tower, and Hyper.
3940
- Use of PostgreSQL prepared statements cache (where supported).
4041
- Use of PostgreSQL arrays to execute multi-row database updates with a single `UPDATE` query.
4142
- This is permitted by the [test requirements](https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview#database-updates), step (ix).
42-
- In version 0.7.6 (as yet unreleased), a native API to set TCP_NODELAY will be included.
43-
- https://github.com/tokio-rs/axum/pull/2653/
44-
- https://github.com/tokio-rs/axum/issues/2521
4543
- More performance improvements are to be expected in version 0.8:
4644
- https://github.com/tokio-rs/axum/issues/1827

frameworks/Rust/axum/axum.dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM docker.io/rust:1.80-slim-bookworm AS builder
1+
FROM docker.io/rust:1.83-slim-bookworm AS builder
22

33
RUN apt-get update && apt-get install -y --no-install-recommends \
44
pkg-config libssl-dev \
@@ -18,7 +18,7 @@ ENV POSTGRES_MIN_POOL_SIZE=56
1818
ENV POSTGRES_MAX_POOL_SIZE=56
1919
ENV MONGODB_URL=mongodb://tfb-database:27017
2020
ENV MONGODB_MIN_POOL_SIZE=28
21-
ENV MONGODB_MAX_POOL_SIZE=14
21+
ENV MONGODB_MAX_POOL_SIZE=28
2222
COPY --from=builder /build/target/release/axum* /app/
2323
EXPOSE 8000
2424
CMD ["/app/axum"]

frameworks/Rust/axum/src/common/mod.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ pub const SELECT_WORLD_BY_ID: &str =
1717
pub const SELECT_ALL_CACHED_WORLDS: &str =
1818
"SELECT id, randomnumber FROM world ORDER BY id";
1919
#[allow(dead_code)]
20-
pub const UPDATE_WORLDS: &str = "WITH vals AS (SELECT * FROM UNNEST($1::int[], $2::int[]) AS v(id, rnum))
21-
UPDATE world SET randomnumber = new.rnum FROM
22-
(SELECT w.id, v.rnum FROM world w INNER JOIN vals v ON v.id = w.id ORDER BY w.id FOR UPDATE) AS new
23-
WHERE world.id = new.id";
20+
pub const UPDATE_WORLDS: &str = r#"UPDATE world SET randomnumber = new.rnum FROM
21+
(SELECT * FROM UNNEST($1::int[], $2::int[]) AS v(id, rnum) ORDER BY 1) AS new
22+
WHERE world.id = new.id"#;
2423

2524
/// Return the value of an environment variable.
2625
#[allow(dead_code)]
@@ -41,11 +40,10 @@ pub fn random_id(rng: &mut SmallRng) -> i32 {
4140
rng.gen_range(1..10_001)
4241
}
4342

44-
/// Generate vector of integers in the range 1 to 10,000 (inclusive)
43+
/// Generate an iterator of integers in the range 1 to 10,000 (inclusive)
4544
#[allow(dead_code)]
4645
#[inline(always)]
47-
pub fn random_ids(rng: &mut SmallRng, count: usize) -> Vec<i32> {
46+
pub fn random_ids(rng: &mut SmallRng, count: usize) -> impl Iterator<Item = i32> + use<'_> {
4847
rng.sample_iter(Uniform::new(1, 10_001))
4948
.take(count)
50-
.collect()
51-
}
49+
}

frameworks/Rust/axum/src/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ mod server;
44
use axum::{http::StatusCode, response::IntoResponse, routing::get, Router};
55
use common::models::Message;
66
use dotenv::dotenv;
7+
use mimalloc::MiMalloc;
8+
9+
#[global_allocator]
10+
static GLOBAL: MiMalloc = MiMalloc;
711

812
#[cfg(not(feature = "simd-json"))]
913
use axum::Json;

frameworks/Rust/axum/src/main_mongo.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ use axum::Json;
1414
#[cfg(feature = "simd-json")]
1515
use common::simd_json::Json;
1616
use common::{
17-
models::{FortuneInfo, World},
18-
random_ids,
17+
models::{FortuneInfo, World}, random_id
1918
};
2019
use dotenv::dotenv;
2120
use mongodb::{
@@ -24,6 +23,10 @@ use mongodb::{
2423
};
2524
use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng};
2625
use yarte::Template;
26+
use mimalloc::MiMalloc;
27+
28+
#[global_allocator]
29+
static GLOBAL: MiMalloc = MiMalloc;
2730

2831
use common::{
2932
get_env,
@@ -58,9 +61,7 @@ async fn queries(
5861
let q = parse_params(params);
5962

6063
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
61-
let ids = random_ids(&mut rng, q);
62-
63-
let worlds = find_worlds(db, ids).await;
64+
let worlds = find_worlds(db, &mut rng, q).await;
6465
let results = worlds.expect("worlds could not be retrieved");
6566

6667
(StatusCode::OK, Json(results))
@@ -73,17 +74,14 @@ async fn updates(
7374
let q = parse_params(params);
7475

7576
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
76-
let ids = random_ids(&mut rng, q);
7777

78-
let worlds = find_worlds(db.clone(), ids)
78+
let worlds = find_worlds(db.clone(), &mut rng, q)
7979
.await
8080
.expect("worlds could not be retrieved");
8181
let mut updated_worlds: Vec<World> = Vec::with_capacity(q);
8282

8383
for mut world in worlds {
84-
let random_number = (rng.gen::<u32>() % 10_000 + 1) as i32;
85-
86-
world.random_number = random_number;
84+
world.random_number = random_id(&mut rng);
8785
updated_worlds.push(world);
8886
}
8987

frameworks/Rust/axum/src/main_mongo_raw.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ mod common;
22
mod mongo_raw;
33
mod server;
44

5-
use common::{models::World, random_id, random_ids};
5+
use common::{models::World, random_id};
66
use mongo_raw::database::{
77
find_world_by_id, find_worlds, update_worlds, DatabaseConnection,
88
};
@@ -17,6 +17,11 @@ use axum::{
1717
extract::Query, http::StatusCode, response::IntoResponse, routing::get, Router,
1818
};
1919

20+
use mimalloc::MiMalloc;
21+
22+
#[global_allocator]
23+
static GLOBAL: MiMalloc = MiMalloc;
24+
2025
#[cfg(not(feature = "simd-json"))]
2126
use axum::Json;
2227
#[cfg(feature = "simd-json")]
@@ -27,7 +32,7 @@ use mongodb::{
2732
options::{ClientOptions, Compressor},
2833
Client,
2934
};
30-
use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng};
35+
use rand::{rngs::SmallRng, thread_rng, SeedableRng};
3136

3237
async fn db(DatabaseConnection(db): DatabaseConnection) -> impl IntoResponse {
3338
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
@@ -48,9 +53,7 @@ async fn queries(
4853
let q = parse_params(params);
4954

5055
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
51-
let ids = random_ids(&mut rng, q);
52-
53-
let worlds = find_worlds(db, ids).await;
56+
let worlds = find_worlds(db, &mut rng, q).await;
5457
let results = worlds.expect("worlds could not be retrieved");
5558

5659
(StatusCode::OK, Json(results))
@@ -64,16 +67,13 @@ async fn updates(
6467

6568
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
6669

67-
let ids = random_ids(&mut rng, q);
68-
let worlds = find_worlds(db.clone(), ids)
70+
let worlds = find_worlds(db.clone(), &mut rng, q)
6971
.await
7072
.expect("worlds could not be retrieved");
7173
let mut updated_worlds: Vec<World> = Vec::with_capacity(q);
7274

7375
for mut world in worlds {
74-
let random_number = (rng.gen::<u32>() % 10_000 + 1) as i32;
75-
76-
world.random_number = random_number;
76+
world.random_number = random_id(&mut rng);
7777
updated_worlds.push(world);
7878
}
7979

frameworks/Rust/axum/src/main_pg.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ use axum::{
77
use dotenv::dotenv;
88
use rand::{rngs::SmallRng, thread_rng, SeedableRng};
99
use yarte::Template;
10+
use mimalloc::MiMalloc;
11+
12+
#[global_allocator]
13+
static GLOBAL: MiMalloc = MiMalloc;
1014

1115
#[cfg(not(feature = "simd-json"))]
1216
use axum::Json;

frameworks/Rust/axum/src/main_pg_pool.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ use dotenv::dotenv;
1515
use futures_util::{stream::FuturesUnordered, TryStreamExt};
1616
use rand::{rngs::SmallRng, thread_rng, SeedableRng};
1717
use yarte::Template;
18+
use mimalloc::MiMalloc;
19+
20+
#[global_allocator]
21+
static GLOBAL: MiMalloc = MiMalloc;
1822

1923
mod server;
2024

0 commit comments

Comments
 (0)