@@ -4,10 +4,16 @@ extern crate criterion;
4
4
use criterion:: { black_box, BatchSize , Criterion } ;
5
5
use rand:: prelude:: * ;
6
6
use rand_distr:: { Distribution , Normal } ;
7
- use sieve_cache:: { ShardedSieveCache , SieveCache , SyncSieveCache } ;
7
+ use sieve_cache:: SieveCache ;
8
8
use std:: sync:: Arc ;
9
9
use std:: thread;
10
10
11
+ #[ cfg( feature = "sync" ) ]
12
+ use sieve_cache:: SyncSieveCache ;
13
+
14
+ #[ cfg( feature = "sharded" ) ]
15
+ use sieve_cache:: ShardedSieveCache ;
16
+
11
17
/// Benchmark sequential access patterns with the base SieveCache implementation.
12
18
///
13
19
/// This benchmark measures the performance of sequential insert and get operations
@@ -108,146 +114,162 @@ fn bench_composite_normal(c: &mut Criterion) {
108
114
} ) ;
109
115
}
110
116
111
- /// Benchmark comparing different thread-safe cache implementations in a high-concurrency scenario.
112
- ///
113
- /// This benchmark measures the performance difference between:
114
- /// 1. SyncSieveCache - using a single mutex for the entire cache
115
- /// 2. ShardedSieveCache - using multiple mutexes (default 16 shards)
116
- /// 3. ShardedSieveCache with 32 shards - higher shard count
117
- ///
118
- /// The test simulates multiple threads performing random operations (inserts and lookups)
119
- /// concurrently, which should highlight the benefits of the sharded approach in
120
- /// reducing lock contention.
121
- fn bench_concurrent_access ( c : & mut Criterion ) {
122
- let mut group = c. benchmark_group ( "concurrent_access" ) ;
123
- group. sample_size ( 10 ) ; // Reduce sample size for these expensive benchmarks
124
-
125
- // Set up benchmark parameters
126
- const CACHE_SIZE : usize = 10000 ;
127
- const NUM_THREADS : usize = 8 ;
128
- const OPS_PER_THREAD : usize = 1000 ;
129
-
130
- // Generic benchmark function to reduce code duplication
131
- let run_concurrent_benchmark = |cache : Arc < dyn CacheInterface < u64 , u64 > > | {
132
- let mut handles = Vec :: with_capacity ( NUM_THREADS ) ;
133
-
134
- for thread_id in 0 ..NUM_THREADS {
135
- let cache_clone = Arc :: clone ( & cache) ;
136
- let handle = thread:: spawn ( move || {
137
- // Use a seeded RNG for reproducibility, with different seeds per thread
138
- let mut rng = StdRng :: seed_from_u64 ( thread_id as u64 ) ;
139
-
140
- for i in 0 ..OPS_PER_THREAD {
141
- // Use a key range that creates some contention but also some distribution
142
- let key = rng. random_range ( 0 ..1000 ) ;
143
-
144
- // Mix operations: 40% inserts, 60% reads
145
- if i % 10 < 4 {
146
- black_box ( cache_clone. insert ( key, key) ) ;
147
- } else {
148
- black_box ( cache_clone. get ( & key) ) ;
149
- }
150
- }
151
- } ) ;
152
- handles. push ( handle) ;
153
- }
117
+ // Interface trait to allow treating both cache implementations uniformly
118
+ trait CacheInterface < K , V > : Send + Sync {
119
+ fn insert ( & self , key : K , value : V ) -> bool ;
120
+ fn get ( & self , key : & K ) -> Option < V > ;
121
+ }
154
122
155
- for handle in handles {
156
- handle . join ( ) . unwrap ( ) ;
157
- }
158
- } ;
123
+ // Only compile concurrent benchmark when both thread-safe implementations are available
124
+ # [ cfg ( all ( feature = "sync" , feature = "sharded" ) ) ]
125
+ mod concurrent_benchmarks {
126
+ use super :: * ;
159
127
160
- // Benchmark with SyncSieveCache (single mutex)
161
- group . bench_function ( "sync_cache" , |b| {
162
- b . iter_batched (
163
- || {
164
- // Setup for each iteration
165
- Arc :: new ( SyncSieveCacheAdapter (
166
- SyncSieveCache :: new ( CACHE_SIZE ) . unwrap ( ) ,
167
- ) )
168
- } ,
169
- |cache| run_concurrent_benchmark ( cache ) ,
170
- BatchSize :: SmallInput ,
171
- ) ;
172
- } ) ;
128
+ /// Benchmark comparing different thread-safe cache implementations in a high-concurrency scenario.
129
+ ///
130
+ /// This benchmark measures the performance difference between:
131
+ /// 1. SyncSieveCache - using a single mutex for the entire cache
132
+ /// 2. ShardedSieveCache - using multiple mutexes (default 16 shards)
133
+ /// 3. ShardedSieveCache with 32 shards - higher shard count
134
+ ///
135
+ /// The test simulates multiple threads performing random operations (inserts and lookups )
136
+ /// concurrently, which should highlight the benefits of the sharded approach in
137
+ /// reducing lock contention.
138
+ pub fn bench_concurrent_access ( c : & mut Criterion ) {
139
+ let mut group = c . benchmark_group ( "concurrent_access" ) ;
140
+ group . sample_size ( 10 ) ; // Reduce sample size for these expensive benchmarks
173
141
174
- // Benchmark with ShardedSieveCache (default: 16 mutexes)
175
- group. bench_function ( "sharded_cache_16_shards" , |b| {
176
- b. iter_batched (
177
- || {
178
- // Setup for each iteration
179
- Arc :: new ( ShardedSieveCacheAdapter (
180
- ShardedSieveCache :: new ( CACHE_SIZE ) . unwrap ( ) ,
181
- ) )
182
- } ,
183
- |cache| run_concurrent_benchmark ( cache) ,
184
- BatchSize :: SmallInput ,
185
- ) ;
186
- } ) ;
142
+ // Set up benchmark parameters
143
+ const CACHE_SIZE : usize = 10000 ;
144
+ const NUM_THREADS : usize = 8 ;
145
+ const OPS_PER_THREAD : usize = 1000 ;
187
146
188
- // Benchmark with different shard counts
189
- group. bench_function ( "sharded_cache_32_shards" , |b| {
190
- b. iter_batched (
191
- || {
192
- // Setup for each iteration
193
- Arc :: new ( ShardedSieveCacheAdapter (
194
- ShardedSieveCache :: with_shards ( CACHE_SIZE , 32 ) . unwrap ( ) ,
195
- ) )
196
- } ,
197
- |cache| run_concurrent_benchmark ( cache) ,
198
- BatchSize :: SmallInput ,
199
- ) ;
200
- } ) ;
147
+ // Generic benchmark function to reduce code duplication
148
+ let run_concurrent_benchmark = |cache : Arc < dyn CacheInterface < u64 , u64 > > | {
149
+ let mut handles = Vec :: with_capacity ( NUM_THREADS ) ;
201
150
202
- group. finish ( ) ;
203
- }
151
+ for thread_id in 0 ..NUM_THREADS {
152
+ let cache_clone = Arc :: clone ( & cache) ;
153
+ let handle = thread:: spawn ( move || {
154
+ // Use a seeded RNG for reproducibility, with different seeds per thread
155
+ let mut rng = StdRng :: seed_from_u64 ( thread_id as u64 ) ;
204
156
205
- // Interface trait to allow treating both cache implementations uniformly
206
- trait CacheInterface < K , V > : Send + Sync {
207
- fn insert ( & self , key : K , value : V ) -> bool ;
208
- fn get ( & self , key : & K ) -> Option < V > ;
209
- }
157
+ for i in 0 ..OPS_PER_THREAD {
158
+ // Use a key range that creates some contention but also some distribution
159
+ let key = rng. random_range ( 0 ..1000 ) ;
210
160
211
- // Adapter for SyncSieveCache
212
- struct SyncSieveCacheAdapter < K : Eq + std:: hash:: Hash + Clone + Send + Sync , V : Clone + Send + Sync > (
213
- SyncSieveCache < K , V > ,
214
- ) ;
161
+ // Mix operations: 40% inserts, 60% reads
162
+ if i % 10 < 4 {
163
+ black_box ( cache_clone. insert ( key, key) ) ;
164
+ } else {
165
+ black_box ( cache_clone. get ( & key) ) ;
166
+ }
167
+ }
168
+ } ) ;
169
+ handles. push ( handle) ;
170
+ }
215
171
216
- impl < K : Eq + std:: hash:: Hash + Clone + Send + Sync , V : Clone + Send + Sync > CacheInterface < K , V >
217
- for SyncSieveCacheAdapter < K , V >
218
- {
219
- fn insert ( & self , key : K , value : V ) -> bool {
220
- self . 0 . insert ( key, value)
221
- }
172
+ for handle in handles {
173
+ handle. join ( ) . unwrap ( ) ;
174
+ }
175
+ } ;
176
+
177
+ // Benchmark with SyncSieveCache (single mutex)
178
+ group. bench_function ( "sync_cache" , |b| {
179
+ b. iter_batched (
180
+ || {
181
+ // Setup for each iteration
182
+ Arc :: new ( SyncSieveCacheAdapter (
183
+ SyncSieveCache :: new ( CACHE_SIZE ) . unwrap ( ) ,
184
+ ) )
185
+ } ,
186
+ |cache| run_concurrent_benchmark ( cache) ,
187
+ BatchSize :: SmallInput ,
188
+ ) ;
189
+ } ) ;
190
+
191
+ // Benchmark with ShardedSieveCache (default: 16 mutexes)
192
+ group. bench_function ( "sharded_cache_16_shards" , |b| {
193
+ b. iter_batched (
194
+ || {
195
+ // Setup for each iteration
196
+ Arc :: new ( ShardedSieveCacheAdapter (
197
+ ShardedSieveCache :: new ( CACHE_SIZE ) . unwrap ( ) ,
198
+ ) )
199
+ } ,
200
+ |cache| run_concurrent_benchmark ( cache) ,
201
+ BatchSize :: SmallInput ,
202
+ ) ;
203
+ } ) ;
222
204
223
- fn get ( & self , key : & K ) -> Option < V > {
224
- self . 0 . get ( key)
205
+ // Benchmark with different shard counts
206
+ group. bench_function ( "sharded_cache_32_shards" , |b| {
207
+ b. iter_batched (
208
+ || {
209
+ // Setup for each iteration
210
+ Arc :: new ( ShardedSieveCacheAdapter (
211
+ ShardedSieveCache :: with_shards ( CACHE_SIZE , 32 ) . unwrap ( ) ,
212
+ ) )
213
+ } ,
214
+ |cache| run_concurrent_benchmark ( cache) ,
215
+ BatchSize :: SmallInput ,
216
+ ) ;
217
+ } ) ;
218
+
219
+ group. finish ( ) ;
225
220
}
226
- }
227
221
228
- // Adapter for ShardedSieveCache
229
- struct ShardedSieveCacheAdapter <
230
- K : Eq + std:: hash:: Hash + Clone + Send + Sync ,
231
- V : Clone + Send + Sync ,
232
- > ( ShardedSieveCache < K , V > ) ;
233
-
234
- impl < K : Eq + std:: hash:: Hash + Clone + Send + Sync , V : Clone + Send + Sync > CacheInterface < K , V >
235
- for ShardedSieveCacheAdapter < K , V >
236
- {
237
- fn insert ( & self , key : K , value : V ) -> bool {
238
- self . 0 . insert ( key, value)
222
+ // Adapter for SyncSieveCache
223
+ struct SyncSieveCacheAdapter < K : Eq + std:: hash:: Hash + Clone + Send + Sync , V : Clone + Send + Sync > (
224
+ SyncSieveCache < K , V > ,
225
+ ) ;
226
+
227
+ impl < K : Eq + std:: hash:: Hash + Clone + Send + Sync , V : Clone + Send + Sync > CacheInterface < K , V >
228
+ for SyncSieveCacheAdapter < K , V >
229
+ {
230
+ fn insert ( & self , key : K , value : V ) -> bool {
231
+ self . 0 . insert ( key, value)
232
+ }
233
+
234
+ fn get ( & self , key : & K ) -> Option < V > {
235
+ self . 0 . get ( key)
236
+ }
239
237
}
240
238
241
- fn get ( & self , key : & K ) -> Option < V > {
242
- self . 0 . get ( key)
239
+ // Adapter for ShardedSieveCache
240
+ struct ShardedSieveCacheAdapter <
241
+ K : Eq + std:: hash:: Hash + Clone + Send + Sync ,
242
+ V : Clone + Send + Sync ,
243
+ > ( ShardedSieveCache < K , V > ) ;
244
+
245
+ impl < K : Eq + std:: hash:: Hash + Clone + Send + Sync , V : Clone + Send + Sync > CacheInterface < K , V >
246
+ for ShardedSieveCacheAdapter < K , V >
247
+ {
248
+ fn insert ( & self , key : K , value : V ) -> bool {
249
+ self . 0 . insert ( key, value)
250
+ }
251
+
252
+ fn get ( & self , key : & K ) -> Option < V > {
253
+ self . 0 . get ( key)
254
+ }
243
255
}
244
256
}
245
257
258
+ #[ cfg( all( feature = "sync" , feature = "sharded" ) ) ]
246
259
criterion_group ! (
247
260
benches,
248
261
bench_sequence,
249
262
bench_composite,
250
263
bench_composite_normal,
251
- bench_concurrent_access
264
+ concurrent_benchmarks :: bench_concurrent_access
252
265
) ;
266
+
267
+ #[ cfg( not( all( feature = "sync" , feature = "sharded" ) ) ) ]
268
+ criterion_group ! (
269
+ benches,
270
+ bench_sequence,
271
+ bench_composite,
272
+ bench_composite_normal
273
+ ) ;
274
+
253
275
criterion_main ! ( benches) ;
0 commit comments