#![cfg(feature = "parallel")]
use compressed_intvec::seq::{Codec, LESeqVec, SeqVec};
use criterion::{BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main};
use rand::{RngExt, SeedableRng, rngs::SmallRng};
use rayon::prelude::*;
use std::time::Duration;
fn generate_power_law_sequences(rng: &mut SmallRng, num_sequences: usize) -> Vec<Vec<u32>> {
let max_value = 10_000u32;
(0..num_sequences)
.map(|_| {
let r: f64 = rng.random();
let len = if r < 0.5 {
rng.random_range(1..=5)
} else if r < 0.85 {
rng.random_range(5..=20)
} else if r < 0.97 {
rng.random_range(20..=100)
} else {
rng.random_range(100..=500)
};
(0..len).map(|_| rng.random_range(1..=max_value)).collect()
})
.collect()
}
fn generate_fixed_length_sequences(
rng: &mut SmallRng,
num_sequences: usize,
seq_length: usize,
) -> Vec<Vec<u32>> {
let max_value = 10_000u32;
(0..num_sequences)
.map(|_| {
(0..seq_length)
.map(|_| rng.random_range(1..=max_value))
.collect()
})
.collect()
}
fn count_total_elements(sequences: &[Vec<u32>]) -> u64 {
sequences.iter().map(|s| s.len() as u64).sum()
}
fn benchmark_iter_vs_par_iter(c: &mut Criterion) {
let mut rng = SmallRng::seed_from_u64(42);
let sequence_counts = [100, 500, 1_000, 5_000, 10_000, 50_000, 100_000];
let mut group = c.benchmark_group("SeqParallel/iter_vs_par_iter");
for &num_sequences in &sequence_counts {
let sequences = generate_power_law_sequences(&mut rng, num_sequences);
let total_elements = count_total_elements(&sequences);
let seqvec: LESeqVec<u32> = SeqVec::builder()
.codec(Codec::Delta)
.build(&sequences)
.expect("Failed to build SeqVec");
group.throughput(Throughput::Elements(total_elements));
group.bench_with_input(
BenchmarkId::new("Baseline_seq", num_sequences),
&sequences,
|b, seqs| {
b.iter(|| {
let sum: u64 = seqs
.iter()
.map(|s| s.iter().map(|&v| v as u64).sum::<u64>())
.sum();
black_box(sum)
})
},
);
group.bench_with_input(
BenchmarkId::new("Baseline_par", num_sequences),
&sequences,
|b, seqs| {
b.iter(|| {
let sum: u64 = seqs
.par_iter()
.map(|s| s.iter().map(|&v| v as u64).sum::<u64>())
.sum();
black_box(sum)
})
},
);
group.bench_with_input(
BenchmarkId::new("SeqVec_iter", num_sequences),
&seqvec,
|b, vec| {
b.iter(|| {
let sum: u64 = vec.iter().map(|s| s.map(|v| v as u64).sum::<u64>()).sum();
black_box(sum)
})
},
);
group.bench_with_input(
BenchmarkId::new("SeqVec_par_iter", num_sequences),
&seqvec,
|b, vec| {
b.iter(|| {
let sum: u64 = vec
.par_iter()
.map(|s| s.iter().map(|&v| v as u64).sum::<u64>())
.sum();
black_box(sum)
})
},
);
}
group.finish();
}
fn benchmark_par_for_each_vs_par_iter(c: &mut Criterion) {
let mut rng = SmallRng::seed_from_u64(42);
let sequence_counts = [1_000, 5_000, 10_000, 50_000, 100_000];
let mut group = c.benchmark_group("SeqParallel/par_for_each_vs_par_iter");
for &num_sequences in &sequence_counts {
let sequences = generate_power_law_sequences(&mut rng, num_sequences);
let total_elements = count_total_elements(&sequences);
let seqvec: LESeqVec<u32> = SeqVec::builder()
.codec(Codec::Delta)
.build(&sequences)
.expect("Failed to build SeqVec");
group.throughput(Throughput::Elements(total_elements));
group.bench_with_input(
BenchmarkId::new("par_iter_sum", num_sequences),
&seqvec,
|b, vec| {
b.iter(|| {
let sum: u64 = vec
.par_iter()
.map(|s| s.iter().map(|&v| v as u64).sum::<u64>())
.sum();
black_box(sum)
})
},
);
group.bench_with_input(
BenchmarkId::new("par_for_each_sum", num_sequences),
&seqvec,
|b, vec| {
b.iter(|| {
let sums: Vec<u64> = vec.par_for_each(|seq| seq.map(|v| v as u64).sum());
let total: u64 = sums.iter().sum();
black_box(total)
})
},
);
group.bench_with_input(
BenchmarkId::new("par_for_each_reduce_sum", num_sequences),
&seqvec,
|b, vec| {
b.iter(|| {
let total: u64 = vec.par_for_each_reduce(
|seq| seq.map(|v| v as u64).sum::<u64>(),
|| 0u64,
|a, b| a + b,
);
black_box(total)
})
},
);
group.bench_with_input(
BenchmarkId::new("iter_sum", num_sequences),
&seqvec,
|b, vec| {
b.iter(|| {
let sum: u64 = vec.iter().map(|s| s.map(|v| v as u64).sum::<u64>()).sum();
black_box(sum)
})
},
);
}
group.finish();
}
fn benchmark_par_for_each_operations(c: &mut Criterion) {
let mut rng = SmallRng::seed_from_u64(42);
const NUM_SEQUENCES: usize = 50_000;
let sequences = generate_power_law_sequences(&mut rng, NUM_SEQUENCES);
let total_elements = count_total_elements(&sequences);
let seqvec: LESeqVec<u32> = SeqVec::builder()
.codec(Codec::Delta)
.build(&sequences)
.expect("Failed to build SeqVec");
let mut group = c.benchmark_group("SeqParallel/par_for_each_operations");
group.throughput(Throughput::Elements(total_elements));
group.bench_function("sum", |b| {
b.iter(|| {
let sums: Vec<u64> = seqvec.par_for_each(|seq| seq.map(|v| v as u64).sum());
black_box(sums)
})
});
group.bench_function("count", |b| {
b.iter(|| {
let counts: Vec<usize> = seqvec.par_for_each(|seq| seq.count());
black_box(counts)
})
});
group.bench_function("max", |b| {
b.iter(|| {
let maxes: Vec<Option<u32>> = seqvec.par_for_each(|seq| seq.max());
black_box(maxes)
})
});
group.bench_function("first_element", |b| {
b.iter(|| {
let firsts: Vec<Option<u32>> = seqvec.par_for_each(|seq| seq.into_iter().next());
black_box(firsts)
})
});
group.finish();
}
fn benchmark_par_decode_many_scaling(c: &mut Criterion) {
let mut rng = SmallRng::seed_from_u64(42);
const NUM_SEQUENCES: usize = 100_000;
let sequences = generate_power_law_sequences(&mut rng, NUM_SEQUENCES);
let seqvec: LESeqVec<u32> = SeqVec::builder()
.codec(Codec::Delta)
.build(&sequences)
.expect("Failed to build SeqVec");
let batch_sizes = [10, 50, 100, 500, 1_000, 5_000, 10_000, 50_000];
let mut group = c.benchmark_group("SeqParallel/decode_many_scaling");
for &batch_size in &batch_sizes {
let indices: Vec<usize> = (0..batch_size)
.map(|_| rng.random_range(0..NUM_SEQUENCES))
.collect();
let total_elements: u64 = indices.iter().map(|&i| sequences[i].len() as u64).sum();
group.throughput(Throughput::Elements(total_elements));
group.bench_with_input(
BenchmarkId::new("decode_many", batch_size),
&indices,
|b, idx| {
b.iter(|| {
let results = seqvec.decode_many(idx).unwrap();
black_box(results)
})
},
);
group.bench_with_input(
BenchmarkId::new("par_decode_many", batch_size),
&indices,
|b, idx| {
b.iter(|| {
let results = seqvec.par_decode_many(idx).unwrap();
black_box(results)
})
},
);
}
group.finish();
}
fn benchmark_par_for_each_many_scaling(c: &mut Criterion) {
let mut rng = SmallRng::seed_from_u64(42);
const NUM_SEQUENCES: usize = 100_000;
let sequences = generate_power_law_sequences(&mut rng, NUM_SEQUENCES);
let seqvec: LESeqVec<u32> = SeqVec::builder()
.codec(Codec::Delta)
.build(&sequences)
.expect("Failed to build SeqVec");
let batch_sizes = [100, 1_000, 5_000, 10_000, 50_000];
let mut group = c.benchmark_group("SeqParallel/par_for_each_many_scaling");
for &batch_size in &batch_sizes {
let indices: Vec<usize> = (0..batch_size)
.map(|_| rng.random_range(0..NUM_SEQUENCES))
.collect();
let total_elements: u64 = indices.iter().map(|&i| sequences[i].len() as u64).sum();
group.throughput(Throughput::Elements(total_elements));
group.bench_with_input(
BenchmarkId::new("par_decode_many_sum", batch_size),
&indices,
|b, idx| {
b.iter(|| {
let results = seqvec.par_decode_many(idx).unwrap();
let sum: u64 = results
.iter()
.map(|s| s.iter().map(|&v| v as u64).sum::<u64>())
.sum();
black_box(sum)
})
},
);
group.bench_with_input(
BenchmarkId::new("par_for_each_many_sum", batch_size),
&indices,
|b, idx| {
b.iter(|| {
let sums = seqvec
.par_for_each_many(idx, |seq| seq.map(|v| v as u64).sum::<u64>())
.unwrap();
let total: u64 = sums.iter().sum();
black_box(total)
})
},
);
}
group.finish();
}
fn benchmark_into_vecs(c: &mut Criterion) {
let mut rng = SmallRng::seed_from_u64(42);
let sequence_counts = [1_000, 5_000, 10_000, 50_000];
let mut group = c.benchmark_group("SeqParallel/into_vecs");
for &num_sequences in &sequence_counts {
let sequences = generate_power_law_sequences(&mut rng, num_sequences);
let total_elements = count_total_elements(&sequences);
group.throughput(Throughput::Elements(total_elements));
group.bench_function(BenchmarkId::new("into_vecs", num_sequences), |b| {
b.iter_batched(
|| {
SeqVec::builder()
.codec(Codec::Delta)
.build(&sequences)
.expect("Failed to build SeqVec")
},
|vec: LESeqVec<u32>| {
let results = vec.into_vecs();
black_box(results)
},
criterion::BatchSize::SmallInput,
)
});
group.bench_function(BenchmarkId::new("par_into_vecs", num_sequences), |b| {
b.iter_batched(
|| {
SeqVec::builder()
.codec(Codec::Delta)
.build(&sequences)
.expect("Failed to build SeqVec")
},
|vec: LESeqVec<u32>| {
let results = vec.par_into_vecs();
black_box(results)
},
criterion::BatchSize::SmallInput,
)
});
}
group.finish();
}
fn benchmark_sequence_length_effect(c: &mut Criterion) {
let mut rng = SmallRng::seed_from_u64(42);
const NUM_SEQUENCES: usize = 10_000;
let sequence_lengths = [5, 20, 50, 100, 500];
let mut group = c.benchmark_group("SeqParallel/sequence_length_effect");
for &seq_len in &sequence_lengths {
let sequences = generate_fixed_length_sequences(&mut rng, NUM_SEQUENCES, seq_len);
let total_elements = (NUM_SEQUENCES * seq_len) as u64;
let seqvec: LESeqVec<u32> = SeqVec::builder()
.codec(Codec::Delta)
.build(&sequences)
.expect("Failed to build SeqVec");
group.throughput(Throughput::Elements(total_elements));
group.bench_function(BenchmarkId::new("iter", seq_len), |b| {
b.iter(|| {
let sum: u64 = seqvec
.iter()
.map(|s| s.map(|v| v as u64).sum::<u64>())
.sum();
black_box(sum)
})
});
group.bench_function(BenchmarkId::new("par_iter", seq_len), |b| {
b.iter(|| {
let sum: u64 = seqvec
.par_iter()
.map(|s| s.iter().map(|&v| v as u64).sum::<u64>())
.sum();
black_box(sum)
})
});
group.bench_function(BenchmarkId::new("par_for_each", seq_len), |b| {
b.iter(|| {
let total: u64 = seqvec.par_for_each_reduce(
|seq| seq.map(|v| v as u64).sum::<u64>(),
|| 0u64,
|a, b| a + b,
);
black_box(total)
})
});
}
group.finish();
}
fn benchmark_stored_lengths_effect(c: &mut Criterion) {
let mut rng = SmallRng::seed_from_u64(42);
const NUM_SEQUENCES: usize = 50_000;
let sequences = generate_power_law_sequences(&mut rng, NUM_SEQUENCES);
let total_elements = count_total_elements(&sequences);
let seqvec_no_lengths: LESeqVec<u32> = SeqVec::builder()
.codec(Codec::Delta)
.store_lengths(false)
.build(&sequences)
.expect("Failed to build SeqVec without lengths");
let seqvec_with_lengths: LESeqVec<u32> = SeqVec::builder()
.codec(Codec::Delta)
.store_lengths(true)
.build(&sequences)
.expect("Failed to build SeqVec with lengths");
let mut group = c.benchmark_group("SeqParallel/stored_lengths_effect");
group.throughput(Throughput::Elements(total_elements));
group.bench_function("par_iter_no_lengths", |b| {
b.iter(|| {
let sum: u64 = seqvec_no_lengths
.par_iter()
.map(|s| s.iter().map(|&v| v as u64).sum::<u64>())
.sum();
black_box(sum)
})
});
group.bench_function("par_iter_with_lengths", |b| {
b.iter(|| {
let sum: u64 = seqvec_with_lengths
.par_iter()
.map(|s| s.iter().map(|&v| v as u64).sum::<u64>())
.sum();
black_box(sum)
})
});
group.bench_function("par_for_each_no_lengths", |b| {
b.iter(|| {
let total: u64 = seqvec_no_lengths.par_for_each_reduce(
|seq| seq.map(|v| v as u64).sum::<u64>(),
|| 0u64,
|a, b| a + b,
);
black_box(total)
})
});
group.bench_function("par_for_each_with_lengths", |b| {
b.iter(|| {
let total: u64 = seqvec_with_lengths.par_for_each_reduce(
|seq| seq.map(|v| v as u64).sum::<u64>(),
|| 0u64,
|a, b| a + b,
);
black_box(total)
})
});
group.finish();
}
criterion_group! {
name = benches;
config = Criterion::default()
.sample_size(20)
.warm_up_time(Duration::from_millis(500))
.measurement_time(Duration::from_secs(3));
targets =
benchmark_iter_vs_par_iter,
benchmark_par_for_each_vs_par_iter,
benchmark_par_for_each_operations,
benchmark_par_decode_many_scaling,
benchmark_par_for_each_many_scaling,
benchmark_into_vecs,
benchmark_sequence_length_effect,
benchmark_stored_lengths_effect
}
criterion_main!(benches);