@@ -271,10 +271,8 @@ fn benchmark_block_sizes(c: &mut Criterion) {
271271 for block_size in block_sizes {
272272 group. throughput ( Throughput :: Elements ( size as u64 ) ) ;
273273 group. bench_function ( format ! ( "compress_{block_size}" ) , |b| {
274- b. iter ( || {
275- let mut codec = FastPFOR :: new ( DEFAULT_PAGE_SIZE , block_size) ;
276- black_box ( compress_data ( & mut codec, black_box ( & data) ) )
277- } ) ;
274+ let mut codec = FastPFOR :: new ( DEFAULT_PAGE_SIZE , block_size) ;
275+ b. iter ( || black_box ( compress_data ( & mut codec, black_box ( & data) ) ) ) ;
278276 } ) ;
279277 }
280278
@@ -286,8 +284,8 @@ fn benchmark_block_sizes(c: &mut Criterion) {
286284
287285 group. throughput ( Throughput :: Elements ( size as u64 ) ) ;
288286 group. bench_function ( format ! ( "decompress_{block_size}" ) , |b| {
287+ let mut codec = FastPFOR :: new ( DEFAULT_PAGE_SIZE , block_size) ;
289288 b. iter ( || {
290- let mut codec = FastPFOR :: new ( DEFAULT_PAGE_SIZE , block_size) ;
291289 black_box ( decompress_data ( & mut codec, black_box ( & compressed) , size) ) ;
292290 } ) ;
293291 } ) ;
@@ -335,12 +333,124 @@ fn benchmark_compression_ratio(c: &mut Criterion) {
335333 group. finish ( ) ;
336334}
337335
336+ /// Compare encoding and decoding speed of the C++ `FastPFor128` codec against
337+ /// the pure-Rust `FastPFOR` codec with `BLOCK_SIZE_128`.
338+ ///
339+ /// Both implementations process identical input data so the numbers are directly
340+ /// comparable. The benchmark is only compiled when the `cpp` feature is enabled.
341+ ///
342+ /// Each sub-benchmark is labelled `cpp/<pattern>/<size>` or
343+ /// `rust/<pattern>/<size>` so Criterion can show them side-by-side.
344+ #[ cfg( feature = "cpp" ) ]
345+ fn benchmark_cpp_vs_rust ( c : & mut Criterion ) {
346+ use fastpfor:: cpp:: { Codec32 , FastPFor128Codec } ;
347+
348+ fn cpp_encode ( codec : & FastPFor128Codec , data : & [ u32 ] ) -> Vec < u32 > {
349+ let mut out = vec ! [ 0u32 ; data. len( ) * 2 + 1024 ] ;
350+ let encoded = codec. encode32 ( data, & mut out) . unwrap ( ) ;
351+ let new_len = encoded. len ( ) ;
352+ out. truncate ( new_len) ;
353+ out
354+ }
355+
356+ fn cpp_decode ( codec : & FastPFor128Codec , compressed : & [ u32 ] , original_len : usize ) -> usize {
357+ let mut out = vec ! [ 0u32 ; original_len] ;
358+ codec. decode32 ( compressed, & mut out) . unwrap ( ) . len ( )
359+ }
360+
361+ let patterns: & [ ( & str , DataGeneratorFn ) ] = & [
362+ (
363+ "uniform_small_value_distribution" ,
364+ generate_uniform_data_small_value_distribution,
365+ ) ,
366+ (
367+ "uniform_large_value_distribution" ,
368+ generate_uniform_data_large_value_distribution,
369+ ) ,
370+ ( "clustered" , generate_clustered_data) ,
371+ ( "sequential" , generate_sequential_data) ,
372+ ( "sparse" , generate_sparse_data) ,
373+ ] ;
374+
375+ // Encoding
376+
377+ let mut group = c. benchmark_group ( "cpp_vs_rust/encode" ) ;
378+ for & size in SIZES {
379+ for ( name, generator) in patterns {
380+ let data = generator ( size) ;
381+ group. throughput ( Throughput :: Elements ( size as u64 ) ) ;
382+
383+ group. bench_with_input (
384+ BenchmarkId :: new ( format ! ( "cpp/{name}" ) , size) ,
385+ & data,
386+ |b, data| {
387+ let codec = FastPFor128Codec :: new ( ) ;
388+ b. iter ( || black_box ( cpp_encode ( & codec, black_box ( data) ) ) ) ;
389+ } ,
390+ ) ;
391+
392+ group. bench_with_input (
393+ BenchmarkId :: new ( format ! ( "rust/{name}" ) , size) ,
394+ & data,
395+ |b, data| {
396+ let mut codec = FastPFOR :: new ( DEFAULT_PAGE_SIZE , BLOCK_SIZE_128 ) ;
397+ b. iter ( || black_box ( compress_data ( & mut codec, black_box ( data) ) ) ) ;
398+ } ,
399+ ) ;
400+ }
401+ }
402+ group. finish ( ) ;
403+
404+ // Decoding
405+
406+ let mut group = c. benchmark_group ( "cpp_vs_rust/decode" ) ;
407+ for & size in SIZES {
408+ for ( name, generator) in patterns {
409+ let data = generator ( size) ;
410+
411+ // Pre-compress once outside the timed loop for each implementation.
412+ let cpp_codec = FastPFor128Codec :: new ( ) ;
413+ let cpp_compressed = cpp_encode ( & cpp_codec, & data) ;
414+ let rust_compressed = prepare_compressed_data ( & data, BLOCK_SIZE_128 ) ;
415+
416+ group. throughput ( Throughput :: Elements ( size as u64 ) ) ;
417+
418+ group. bench_with_input (
419+ BenchmarkId :: new ( format ! ( "cpp/{name}" ) , size) ,
420+ & cpp_compressed,
421+ |b, compressed| {
422+ let codec = FastPFor128Codec :: new ( ) ;
423+ b. iter ( || black_box ( cpp_decode ( & codec, black_box ( compressed) , size) ) ) ;
424+ } ,
425+ ) ;
426+
427+ group. bench_with_input (
428+ BenchmarkId :: new ( format ! ( "rust/{name}" ) , size) ,
429+ & rust_compressed,
430+ |b, compressed| {
431+ let mut codec = FastPFOR :: new ( DEFAULT_PAGE_SIZE , BLOCK_SIZE_128 ) ;
432+ b. iter ( || black_box ( decompress_data ( & mut codec, black_box ( compressed) , size) ) ) ;
433+ } ,
434+ ) ;
435+ }
436+ }
437+ group. finish ( ) ;
438+ }
439+
338440criterion_group ! (
339441 benches,
340442 benchmark_compression,
341443 benchmark_decompression,
342444 benchmark_roundtrip,
343445 benchmark_block_sizes,
344- benchmark_compression_ratio
446+ benchmark_compression_ratio,
345447) ;
448+
449+ #[ cfg( feature = "cpp" ) ]
450+ criterion_group ! ( cpp_benches, benchmark_cpp_vs_rust) ;
451+
452+ #[ cfg( not( feature = "cpp" ) ) ]
346453criterion_main ! ( benches) ;
454+
455+ #[ cfg( feature = "cpp" ) ]
456+ criterion_main ! ( benches, cpp_benches) ;
0 commit comments