use std::hint::black_box;
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use p3_binary_field::{BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128};
use p3_field::{BasedVectorSpace, Field, PrimeCharacteristicRing};
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
const REPS: usize = 1000;
const LANES: usize = 10;
macro_rules! bench_level {
($c:expr, $t:ty, $bits:literal) => {{
let mut rng = SmallRng::seed_from_u64(1);
let operands: Vec<$t> = (0..REPS).map(|_| rng.random::<$t>()).collect();
let mut group = $c.benchmark_group(concat!("mul/", $bits));
group.bench_function("reference/latency", |b| {
b.iter(|| {
black_box(&operands)
.iter()
.fold(<$t>::ONE, |acc, &y| acc.reference_mul(y))
});
});
group.bench_function("dispatched/latency", |b| {
b.iter(|| {
black_box(&operands)
.iter()
.fold(<$t>::ONE, |acc, &y| acc * y)
});
});
group.bench_function("reference/throughput", |b| {
b.iter(|| {
let mut acc = [<$t>::ONE; LANES];
for chunk in black_box(&operands).chunks_exact(LANES) {
for (lane, &y) in acc.iter_mut().zip(chunk) {
*lane = lane.reference_mul(y);
}
}
acc
});
});
group.bench_function("dispatched/throughput", |b| {
b.iter(|| {
let mut acc = [<$t>::ONE; LANES];
for chunk in black_box(&operands).chunks_exact(LANES) {
for (lane, &y) in acc.iter_mut().zip(chunk) {
*lane *= y;
}
}
acc
});
});
group.finish();
}};
}
macro_rules! bench_square_level {
($c:expr, $t:ty, $bits:literal) => {{
let mut rng = SmallRng::seed_from_u64(1);
let operands: Vec<$t> = (0..REPS).map(|_| rng.random::<$t>()).collect();
let mut group = $c.benchmark_group(concat!("square/", $bits));
group.bench_function("square/latency", |b| {
b.iter(|| {
black_box(&operands)
.iter()
.fold(<$t>::ONE, |acc, &y| (acc + y).square())
});
});
group.bench_function("mul/latency", |b| {
b.iter(|| {
black_box(&operands).iter().fold(<$t>::ONE, |acc, &y| {
let x = acc + y;
x * x
})
});
});
group.bench_function("square/throughput", |b| {
b.iter(|| {
let mut acc = [<$t>::ONE; LANES];
for chunk in black_box(&operands).chunks_exact(LANES) {
for (lane, &y) in acc.iter_mut().zip(chunk) {
*lane = (*lane + y).square();
}
}
acc
});
});
group.bench_function("mul/throughput", |b| {
b.iter(|| {
let mut acc = [<$t>::ONE; LANES];
for chunk in black_box(&operands).chunks_exact(LANES) {
for (lane, &y) in acc.iter_mut().zip(chunk) {
let x = *lane + y;
*lane = x * x;
}
}
acc
});
});
group.finish();
}};
}
fn bench_mul(c: &mut Criterion) {
bench_level!(c, BinaryField8, 8);
bench_level!(c, BinaryField16, 16);
bench_level!(c, BinaryField32, 32);
bench_level!(c, BinaryField64, 64);
bench_level!(c, BinaryField128, 128);
}
fn bench_square(c: &mut Criterion) {
bench_square_level!(c, BinaryField8, 8);
bench_square_level!(c, BinaryField16, 16);
bench_square_level!(c, BinaryField32, 32);
bench_square_level!(c, BinaryField64, 64);
bench_square_level!(c, BinaryField128, 128);
}
fn bench_inverse(c: &mut Criterion) {
let mut rng = SmallRng::seed_from_u64(1);
let operands: Vec<BinaryField128> = (0..REPS)
.map(|_| {
let x = rng.random::<BinaryField128>();
if x.is_zero() { BinaryField128::ONE } else { x }
})
.collect();
let mut group = c.benchmark_group("inverse/128");
group.bench_function("dispatched", |b| {
b.iter(|| {
black_box(&operands)
.iter()
.fold(BinaryField128::ZERO, |acc, &y| acc + y.inverse())
});
});
group.finish();
}
fn bench_mul_alpha(c: &mut Criterion) {
let mut alpha_bytes = [0u8; 16];
alpha_bytes[4] = 1;
let alpha = BinaryField128::from_le_bytes(alpha_bytes);
let mut rng = SmallRng::seed_from_u64(1);
let operands: Vec<BinaryField128> = (0..REPS).map(|_| rng.random()).collect();
let mut group = c.benchmark_group("mul_alpha/128");
group.bench_function("reference", |b| {
b.iter(|| {
black_box(&operands)
.iter()
.fold(BinaryField128::ZERO, |acc, &y| acc + y.reference_mul(alpha))
});
});
group.bench_function("dispatched", |b| {
b.iter(|| {
black_box(&operands)
.iter()
.fold(BinaryField128::ZERO, |acc, &y| acc + y * alpha)
});
});
group.finish();
}
const FLATTEN_LEN: usize = 1 << 20;
fn bench_flatten_to_base(c: &mut Criterion) {
let mut rng = SmallRng::seed_from_u64(1);
let elems: Vec<BinaryField128> = (0..FLATTEN_LEN).map(|_| rng.random()).collect();
let mut group = c.benchmark_group("flatten_to_base/BinaryField128");
group.bench_function("p3-field default", |b| {
b.iter_batched(
|| elems.clone(),
|v| {
let out: Vec<BinaryField8> = v
.into_iter()
.flat_map(|x| {
BasedVectorSpace::<BinaryField8>::as_basis_coefficients_slice(&x).to_vec()
})
.collect();
black_box(out)
},
BatchSize::PerIteration,
);
});
group.bench_function("tower override", |b| {
b.iter_batched(
|| elems.clone(),
|v| black_box(<BinaryField128 as BasedVectorSpace<BinaryField8>>::flatten_to_base(v)),
BatchSize::PerIteration,
);
});
group.finish();
}
criterion_group!(
benches,
bench_mul,
bench_square,
bench_inverse,
bench_mul_alpha,
bench_flatten_to_base
);
criterion_main!(benches);