Skip to content

Commit 8934141

Browse files
authored
perf: add concurrent keyspace mode (--concurrent flag) (#50)
* perf: add concurrent keyspace mode (--concurrent flag) adds an experimental mode that uses DashMap for lock-free concurrent access, bypassing the sharded channel architecture for GET/SET commands. benchmark results on 8-core VM: - SET: +13% throughput (1,010k → 1,142k ops/sec) - GET: +11% throughput (1,115k → 1,241k ops/sec) the improvement comes from eliminating the per-request channel overhead (mpsc send + oneshot reply) that exists in the sharded architecture. new files: - concurrent.rs: DashMap-backed ConcurrentKeyspace - concurrent_handler.rs: connection handler for concurrent mode usage: ember-server --concurrent * chore: fmt and clippy fixes
1 parent ce99e06 commit 8934141

9 files changed

Lines changed: 790 additions & 14 deletions

File tree

Cargo.lock

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

crates/ember-core/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ tokio = { workspace = true }
2121
tracing = { workspace = true }
2222
rand = { workspace = true }
2323
ordered-float = { workspace = true }
24+
dashmap = "6"
25+
parking_lot = "0.12"
2426

2527
[dev-dependencies]
2628
tempfile = "3"
@@ -34,3 +36,7 @@ harness = false
3436
[[bench]]
3537
name = "engine"
3638
harness = false
39+
40+
[[bench]]
41+
name = "concurrent"
42+
harness = false
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//! Benchmark comparing sharded engine vs concurrent keyspace.
2+
3+
use bytes::Bytes;
4+
use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput};
5+
use ember_core::concurrent::ConcurrentKeyspace;
6+
use ember_core::keyspace::EvictionPolicy;
7+
use std::sync::Arc;
8+
9+
fn bench_concurrent_set(c: &mut Criterion) {
10+
let ks = Arc::new(ConcurrentKeyspace::new(None, EvictionPolicy::NoEviction));
11+
12+
let mut group = c.benchmark_group("concurrent");
13+
group.throughput(Throughput::Elements(1));
14+
15+
group.bench_function("set", |b| {
16+
let mut i = 0u64;
17+
b.iter(|| {
18+
let key = format!("key:{}", i);
19+
i = i.wrapping_add(1);
20+
ks.set(key, Bytes::from_static(b"value"), None);
21+
black_box(())
22+
})
23+
});
24+
25+
// Pre-populate for get benchmark
26+
for i in 0..10000 {
27+
ks.set(format!("key:{}", i), Bytes::from_static(b"value"), None);
28+
}
29+
30+
group.bench_function("get_existing", |b| {
31+
let mut i = 0u64;
32+
b.iter(|| {
33+
let key = format!("key:{}", i % 10000);
34+
i = i.wrapping_add(1);
35+
black_box(ks.get(&key))
36+
})
37+
});
38+
39+
group.bench_function("get_missing", |b| {
40+
let mut i = 0u64;
41+
b.iter(|| {
42+
let key = format!("missing:{}", i);
43+
i = i.wrapping_add(1);
44+
black_box(ks.get(&key))
45+
})
46+
});
47+
48+
group.finish();
49+
}
50+
51+
fn bench_concurrent_multithread(c: &mut Criterion) {
52+
use std::thread;
53+
54+
let ks = Arc::new(ConcurrentKeyspace::new(None, EvictionPolicy::NoEviction));
55+
56+
// Pre-populate
57+
for i in 0..100000 {
58+
ks.set(format!("key:{}", i), Bytes::from_static(b"value"), None);
59+
}
60+
61+
let mut group = c.benchmark_group("concurrent_mt");
62+
group.throughput(Throughput::Elements(8)); // 8 threads
63+
64+
group.bench_function("get_8_threads", |b| {
65+
b.iter(|| {
66+
let handles: Vec<_> = (0..8)
67+
.map(|t| {
68+
let ks = Arc::clone(&ks);
69+
thread::spawn(move || {
70+
for i in 0..1000 {
71+
let key = format!("key:{}", (t * 1000 + i) % 100000);
72+
black_box(ks.get(&key));
73+
}
74+
})
75+
})
76+
.collect();
77+
78+
for h in handles {
79+
h.join().unwrap();
80+
}
81+
})
82+
});
83+
84+
group.bench_function("set_8_threads", |b| {
85+
b.iter(|| {
86+
let handles: Vec<_> = (0..8)
87+
.map(|t| {
88+
let ks = Arc::clone(&ks);
89+
thread::spawn(move || {
90+
for i in 0..1000 {
91+
let key = format!("thread:{}:key:{}", t, i);
92+
ks.set(key, Bytes::from_static(b"value"), None);
93+
}
94+
})
95+
})
96+
.collect();
97+
98+
for h in handles {
99+
h.join().unwrap();
100+
}
101+
})
102+
});
103+
104+
group.finish();
105+
}
106+
107+
criterion_group!(benches, bench_concurrent_set, bench_concurrent_multithread);
108+
criterion_main!(benches);

crates/ember-core/benches/keyspace.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ fn bench_mixed(c: &mut Criterion) {
9393
let mut i = 0u64;
9494
b.iter(|| {
9595
let key = format!("key:{}", i % KEY_COUNT as u64);
96-
if i % 2 == 0 {
96+
if i.is_multiple_of(2) {
9797
let _ = black_box(ks.get(&key));
9898
} else {
9999
black_box(ks.set(key, value.clone(), None));

0 commit comments

Comments
 (0)