Skip to content

Commit c415c78

Browse files
committed
Improve
1 parent 5c374b4 commit c415c78

File tree

5 files changed

+536
-16
lines changed

5 files changed

+536
-16
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
zig-cache
33
*~
44
Cargo.lock
5+
6+
**/.claude/settings.local.json

README.md

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,11 @@ assert_eq!(cache.len(), 1);
4444
assert_eq!(cache.capacity(), 100000);
4545
```
4646

47-
## Thread-safe Cache Example
47+
## Thread-safe Cache Examples
4848

49-
You can also use the thread-safe wrapper `SyncSieveCache` for concurrent access:
49+
### Basic Thread-safe Cache
50+
51+
You can use the thread-safe wrapper `SyncSieveCache` for concurrent access:
5052

5153
```rust
5254
use sieve_cache::SyncSieveCache;
@@ -62,7 +64,7 @@ cache.insert("foo".to_string(), "foocontent".to_string());
6264
// Access the cache from another thread.
6365
let handle = thread::spawn(move || {
6466
cache_clone.insert("bar".to_string(), "barcontent".to_string());
65-
67+
6668
// Retrieve a value from the cache. Returns a clone of the value.
6769
assert_eq!(cache_clone.get(&"foo".to_string()), Some("foocontent".to_string()));
6870
});
@@ -87,3 +89,55 @@ cache.with_lock(|inner_cache| {
8789
});
8890
```
8991

92+
### Sharded Thread-safe Cache
93+
94+
For higher concurrency, you can use the sharded implementation `ShardedSieveCache` which uses multiple internal locks to reduce contention:
95+
96+
```rust
97+
use sieve_cache::ShardedSieveCache;
98+
use std::thread;
99+
use std::sync::Arc;
100+
101+
// Create a sharded cache with default shard count (16)
102+
let cache = Arc::new(ShardedSieveCache::new(100000).unwrap());
103+
104+
// Or specify a custom number of shards
105+
// let cache = Arc::new(ShardedSieveCache::with_shards(100000, 32).unwrap());
106+
107+
// Insert key/value pairs from the main thread
108+
cache.insert("foo".to_string(), "foocontent".to_string());
109+
110+
// Create multiple worker threads
111+
let mut handles = vec![];
112+
for i in 0..8 {
113+
let cache_clone = Arc::clone(&cache);
114+
let handle = thread::spawn(move || {
115+
// Each thread inserts multiple values
116+
for j in 0..100 {
117+
let key = format!("key_thread{}_item{}", i, j);
118+
let value = format!("value_{}", j);
119+
cache_clone.insert(key, value);
120+
}
121+
});
122+
handles.push(handle);
123+
}
124+
125+
// Wait for all threads to complete
126+
for handle in handles {
127+
handle.join().unwrap();
128+
}
129+
130+
// Perform operations specific to a key's shard
131+
cache.with_key_lock(&"foo", |shard| {
132+
shard.insert("related_key1".to_string(), "value1".to_string());
133+
shard.insert("related_key2".to_string(), "value2".to_string());
134+
});
135+
136+
// Get the number of entries across all shards
137+
assert_eq!(cache.len(), 803); // 800 from threads + 1 "foo" + 2 related keys
138+
139+
// Get cache statistics
140+
println!("Cache has {} shards with total capacity {}",
141+
cache.num_shards(), cache.capacity());
142+
```
143+

benches/criterion.rs

Lines changed: 119 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
extern crate criterion;
33

44
use criterion::{black_box, Criterion};
5-
use rand::rng;
6-
use rand_distr::{Distribution, Normal, Uniform};
7-
use sieve_cache::SieveCache;
5+
use rand::prelude::*;
6+
use rand::thread_rng;
7+
use rand_distr::{Distribution, Normal};
8+
use sieve_cache::{ShardedSieveCache, SieveCache, SyncSieveCache};
9+
use std::sync::Arc;
10+
use std::thread;
811

912
fn bench_sequence(c: &mut Criterion) {
1013
c.bench_function("bench_sequence", |b| {
@@ -27,18 +30,18 @@ fn bench_sequence(c: &mut Criterion) {
2730
fn bench_composite(c: &mut Criterion) {
2831
c.bench_function("bench_composite", |b| {
2932
let mut cache: SieveCache<u64, (Vec<u8>, u64)> = SieveCache::new(68).unwrap();
30-
let mut rng = rng();
31-
let uniform = Uniform::new(0, 100).unwrap();
32-
let mut rand_iter = uniform.sample_iter(&mut rng);
33+
let mut rng = thread_rng();
34+
3335
b.iter(|| {
3436
for _ in 1..1000 {
35-
let n = rand_iter.next().unwrap();
37+
let n = rng.gen_range(0..100);
3638
black_box(cache.insert(n, (vec![0u8; 12], n)));
3739
}
3840
});
41+
3942
b.iter(|| {
4043
for _ in 1..1000 {
41-
let n = rand_iter.next().unwrap();
44+
let n = rng.gen_range(0..100);
4245
black_box(cache.get(&n));
4346
}
4447
});
@@ -53,28 +56,131 @@ fn bench_composite_normal(c: &mut Criterion) {
5356
let mut cache: SieveCache<u64, (Vec<u8>, u64)> = SieveCache::new(SIGMA as usize).unwrap();
5457

5558
// This should roughly cover all elements (within 3-sigma)
56-
let mut rng = rng();
59+
let mut rng = thread_rng();
5760
let normal = Normal::new(50.0, SIGMA).unwrap();
58-
let mut rand_iter = normal.sample_iter(&mut rng).map(|x| (x as u64) % 100);
61+
5962
b.iter(|| {
6063
for _ in 1..1000 {
61-
let n = rand_iter.next().unwrap();
64+
let sample = normal.sample(&mut rng);
65+
let n = (sample as u64) % 100;
6266
black_box(cache.insert(n, (vec![0u8; 12], n)));
6367
}
6468
});
69+
6570
b.iter(|| {
6671
for _ in 1..1000 {
67-
let n = rand_iter.next().unwrap();
72+
let sample = normal.sample(&mut rng);
73+
let n = (sample as u64) % 100;
6874
black_box(cache.get(&n));
6975
}
7076
});
7177
});
7278
}
7379

80+
// Benchmark to compare thread-safe implementations in high-concurrency scenario
81+
fn bench_concurrent_access(c: &mut Criterion) {
82+
let mut group = c.benchmark_group("concurrent_access");
83+
84+
// Set up benchmark parameters
85+
const CACHE_SIZE: usize = 10000;
86+
const NUM_THREADS: usize = 8;
87+
const OPS_PER_THREAD: usize = 1000;
88+
89+
// Benchmark with SyncSieveCache (single mutex)
90+
group.bench_function("sync_cache", |b| {
91+
b.iter(|| {
92+
let cache = Arc::new(SyncSieveCache::new(CACHE_SIZE).unwrap());
93+
let mut handles = Vec::with_capacity(NUM_THREADS);
94+
95+
for _ in 0..NUM_THREADS {
96+
let cache_clone = Arc::clone(&cache);
97+
let handle = thread::spawn(move || {
98+
let mut rng = thread_rng();
99+
100+
for _ in 0..OPS_PER_THREAD {
101+
let key = rng.gen_range(0..1000);
102+
if rng.gen::<bool>() {
103+
black_box(cache_clone.insert(key, key));
104+
} else {
105+
black_box(cache_clone.get(&key));
106+
}
107+
}
108+
});
109+
handles.push(handle);
110+
}
111+
112+
for handle in handles {
113+
handle.join().unwrap();
114+
}
115+
});
116+
});
117+
118+
// Benchmark with ShardedSieveCache (multiple mutexes)
119+
group.bench_function("sharded_cache", |b| {
120+
b.iter(|| {
121+
let cache = Arc::new(ShardedSieveCache::new(CACHE_SIZE).unwrap());
122+
let mut handles = Vec::with_capacity(NUM_THREADS);
123+
124+
for _ in 0..NUM_THREADS {
125+
let cache_clone = Arc::clone(&cache);
126+
let handle = thread::spawn(move || {
127+
let mut rng = thread_rng();
128+
129+
for _ in 0..OPS_PER_THREAD {
130+
let key = rng.gen_range(0..1000);
131+
if rng.gen::<bool>() {
132+
black_box(cache_clone.insert(key, key));
133+
} else {
134+
black_box(cache_clone.get(&key));
135+
}
136+
}
137+
});
138+
handles.push(handle);
139+
}
140+
141+
for handle in handles {
142+
handle.join().unwrap();
143+
}
144+
});
145+
});
146+
147+
// Benchmark with different shard counts
148+
group.bench_function("sharded_cache_32_shards", |b| {
149+
b.iter(|| {
150+
let cache = Arc::new(ShardedSieveCache::with_shards(CACHE_SIZE, 32).unwrap());
151+
let mut handles = Vec::with_capacity(NUM_THREADS);
152+
153+
for _ in 0..NUM_THREADS {
154+
let cache_clone = Arc::clone(&cache);
155+
let handle = thread::spawn(move || {
156+
let mut rng = thread_rng();
157+
158+
for _ in 0..OPS_PER_THREAD {
159+
let key = rng.gen_range(0..1000);
160+
if rng.gen::<bool>() {
161+
black_box(cache_clone.insert(key, key));
162+
} else {
163+
black_box(cache_clone.get(&key));
164+
}
165+
}
166+
});
167+
handles.push(handle);
168+
}
169+
170+
for handle in handles {
171+
handle.join().unwrap();
172+
}
173+
});
174+
});
175+
176+
group.finish();
177+
}
178+
74179
criterion_group!(
75180
benches,
76181
bench_sequence,
77182
bench_composite,
78-
bench_composite_normal
183+
bench_composite_normal,
184+
bench_concurrent_access
79185
);
80186
criterion_main!(benches);

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ use std::borrow::Borrow;
44
use std::hash::Hash;
55
use std::{collections::HashMap, ptr::NonNull};
66

7+
mod sharded;
78
mod sync;
9+
10+
pub use sharded::ShardedSieveCache;
811
pub use sync::SyncSieveCache;
912

1013
struct Node<K: Eq + Hash + Clone, V> {

0 commit comments

Comments
 (0)