1use alloc::format;
2use alloc::vec::Vec;
3use core::hint::black_box;
4use core::ops::Div;
5
6use criterion::{BatchSize, Criterion};
7use p3_field::{Algebra, Field, PrimeCharacteristicRing, chunked_linear_combination};
8use rand::distr::StandardUniform;
9use rand::prelude::Distribution;
10use rand::rngs::SmallRng;
11use rand::{RngExt, SeedableRng};
12
13pub fn benchmark_mul<F: Field>(c: &mut Criterion, name: &str)
16where
17 StandardUniform: Distribution<F>,
18{
19 let mut rng = SmallRng::seed_from_u64(1);
20 let x = rng.random::<F>();
21 let y = rng.random::<F>();
22 c.bench_function(&format!("{name} mul"), |b| {
23 b.iter(|| black_box(black_box(x) * black_box(y)));
24 });
25}
26
27pub fn benchmark_square<F: Field>(c: &mut Criterion, name: &str)
28where
29 StandardUniform: Distribution<F>,
30{
31 let mut rng = SmallRng::seed_from_u64(1);
32 let x = rng.random::<F>();
33 c.bench_function(&format!("{name} square"), |b| {
34 b.iter(|| black_box(black_box(x).square()));
35 });
36}
37
38pub fn benchmark_inv<F: Field>(c: &mut Criterion, name: &str)
39where
40 StandardUniform: Distribution<F>,
41{
42 let mut rng = SmallRng::seed_from_u64(1);
43 let x = rng.random::<F>();
44 c.bench_function(&format!("{name} inv"), |b| {
45 b.iter(|| black_box(black_box(x)).inverse());
46 });
47}
48
49pub fn benchmark_mul_2exp<R: PrimeCharacteristicRing + Copy, const REPS: usize>(
50 c: &mut Criterion,
51 name: &str,
52 val: u64,
53) where
54 StandardUniform: Distribution<R>,
55{
56 let mut rng = SmallRng::seed_from_u64(1);
57 let mut input = Vec::new();
58 for _ in 0..REPS {
59 input.push(rng.random::<R>());
60 }
61 c.bench_function(&format!("{name} mul_2exp_u64 {val}"), |b| {
62 b.iter(|| input.iter_mut().for_each(|i| *i = i.mul_2exp_u64(val)));
63 });
64}
65
66pub fn benchmark_halve<F: Field, const REPS: usize>(c: &mut Criterion, name: &str)
67where
68 StandardUniform: Distribution<F>,
69{
70 let mut rng = SmallRng::seed_from_u64(1);
71 let mut input = Vec::new();
72 for _ in 0..REPS {
73 input.push(rng.random::<F>());
74 }
75 c.bench_function(&format!("{name} halve. Num Reps: {REPS}"), |b| {
76 b.iter(|| input.iter_mut().for_each(|i| *i = i.halve()));
77 });
78}
79
80pub fn benchmark_div_2exp<F: Field, const REPS: usize>(c: &mut Criterion, name: &str, val: u64)
81where
82 StandardUniform: Distribution<F>,
83{
84 let mut rng = SmallRng::seed_from_u64(1);
85 let mut input = Vec::new();
86 for _ in 0..REPS {
87 input.push(rng.random::<F>());
88 }
89 c.bench_function(&format!("{name} div_2exp_u64 {val}"), |b| {
90 b.iter(|| input.iter_mut().for_each(|i| *i = i.div_2exp_u64(val)));
91 });
92}
93
94pub fn benchmark_iter_sum<R: PrimeCharacteristicRing + Copy, const N: usize, const REPS: usize>(
100 c: &mut Criterion,
101 name: &str,
102) where
103 StandardUniform: Distribution<R>,
104{
105 let mut rng = SmallRng::seed_from_u64(1);
106 let mut input = Vec::new();
107 for _ in 0..REPS {
108 input.push(rng.random::<[R; N]>());
109 }
110 c.bench_function(&format!("{name} sum/{REPS}, {N}"), |b| {
111 b.iter(|| {
112 let mut acc = R::ZERO;
113 for row in &mut input {
114 acc += row.iter().copied().sum();
115 }
116 acc
117 });
118 });
119}
120
121pub fn benchmark_sum_array<R: PrimeCharacteristicRing + Copy, const N: usize, const REPS: usize>(
127 c: &mut Criterion,
128 name: &str,
129) where
130 StandardUniform: Distribution<R>,
131{
132 let mut rng = SmallRng::seed_from_u64(1);
133 let mut input = Vec::new();
134 for _ in 0..REPS {
135 input.push(rng.random::<[R; N]>());
136 }
137 c.bench_function(&format!("{name} tree sum/{REPS}, {N}"), |b| {
138 b.iter(|| {
139 let mut acc = R::ZERO;
140 for row in &mut input {
141 acc += R::sum_array::<N>(row);
142 }
143 acc
144 });
145 });
146}
147
148pub fn benchmark_dot_array<R: PrimeCharacteristicRing + Copy, const N: usize>(
153 c: &mut Criterion,
154 name: &str,
155) where
156 StandardUniform: Distribution<R>,
157{
158 let mut rng = SmallRng::seed_from_u64(1);
159 let lhs = rng.random::<[R; N]>();
160 let rhs = rng.random::<[R; N]>();
161
162 c.bench_function(&format!("{name} dot product/{N}"), |b| {
163 b.iter(|| black_box(R::dot_product(black_box(&lhs), black_box(&rhs))));
164 });
165}
166
167pub fn benchmark_mixed_dot_array<A: Algebra<F> + Copy, F: Field, const N: usize>(
169 c: &mut Criterion,
170 name: &str,
171) where
172 StandardUniform: Distribution<A> + Distribution<F>,
173{
174 let mut rng = SmallRng::seed_from_u64(1);
175 let a = rng.random::<[A; N]>();
176 let f = rng.random::<[F; N]>();
177 c.bench_function(&format!("{name} mixed dot product/{N}"), |b| {
178 b.iter(|| black_box(A::mixed_dot_product(black_box(&a), black_box(&f))));
179 });
180}
181
182pub fn benchmark_add_slices<F: Field, const LENGTH: usize>(c: &mut Criterion, name: &str)
184where
185 StandardUniform: Distribution<F>,
186{
187 let mut rng = SmallRng::seed_from_u64(1);
188 let mut slice_1 = Vec::new();
189 let mut slice_2 = Vec::new();
190 for _ in 0..LENGTH {
191 slice_1.push(rng.random());
192 slice_2.push(rng.random());
193 }
194 c.bench_function(&format!("{name} add slices/{LENGTH}"), |b| {
195 let mut in_slice = slice_1.clone();
196 b.iter(|| {
197 F::add_slices(&mut in_slice, &slice_2);
198 });
199 });
200}
201
202pub fn benchmark_add_latency<R: PrimeCharacteristicRing + Copy, const N: usize>(
203 c: &mut Criterion,
204 name: &str,
205) where
206 StandardUniform: Distribution<R>,
207{
208 c.bench_function(&format!("add-latency/{N} {name}"), |b| {
209 b.iter_batched(
210 || {
211 let mut rng = SmallRng::seed_from_u64(1);
212 let mut vec = Vec::new();
213 for _ in 0..N {
214 vec.push(rng.random::<R>());
215 }
216 vec
217 },
218 |x| x.iter().fold(R::ZERO, |x, y| x + *y),
219 BatchSize::SmallInput,
220 );
221 });
222}
223
224pub fn benchmark_add_throughput<R: PrimeCharacteristicRing + Copy, const N: usize>(
225 c: &mut Criterion,
226 name: &str,
227) where
228 StandardUniform: Distribution<R>,
229{
230 c.bench_function(&format!("add-throughput/{N} {name}"), |b| {
231 b.iter_batched(
232 || {
233 let mut rng = SmallRng::seed_from_u64(1);
234 (
235 rng.random::<R>(),
236 rng.random::<R>(),
237 rng.random::<R>(),
238 rng.random::<R>(),
239 rng.random::<R>(),
240 rng.random::<R>(),
241 rng.random::<R>(),
242 rng.random::<R>(),
243 rng.random::<R>(),
244 rng.random::<R>(),
245 )
246 },
247 |(mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j)| {
248 for _ in 0..N {
249 (a, b, c, d, e, f, g, h, i, j) = (
250 a + b,
251 b + c,
252 c + d,
253 d + e,
254 e + f,
255 f + g,
256 g + h,
257 h + i,
258 i + j,
259 j + a,
260 );
261 }
262 (a, b, c, d, e, f, g, h, i, j)
263 },
264 BatchSize::SmallInput,
265 );
266 });
267}
268
269pub fn benchmark_sub_latency<R: PrimeCharacteristicRing + Copy, const N: usize>(
270 c: &mut Criterion,
271 name: &str,
272) where
273 StandardUniform: Distribution<R>,
274{
275 c.bench_function(&format!("sub-latency/{N} {name}"), |b| {
276 b.iter_batched(
277 || {
278 let mut rng = SmallRng::seed_from_u64(1);
279 let mut vec = Vec::new();
280 for _ in 0..N {
281 vec.push(rng.random::<R>());
282 }
283 vec
284 },
285 |x| x.iter().fold(R::ZERO, |x, y| x - *y),
286 BatchSize::SmallInput,
287 );
288 });
289}
290
291pub fn benchmark_sub_throughput<R: PrimeCharacteristicRing + Copy, const N: usize>(
292 c: &mut Criterion,
293 name: &str,
294) where
295 StandardUniform: Distribution<R>,
296{
297 c.bench_function(&format!("sub-throughput/{N} {name}"), |b| {
298 b.iter_batched(
299 || {
300 let mut rng = SmallRng::seed_from_u64(1);
301 (
302 rng.random::<R>(),
303 rng.random::<R>(),
304 rng.random::<R>(),
305 rng.random::<R>(),
306 rng.random::<R>(),
307 rng.random::<R>(),
308 rng.random::<R>(),
309 rng.random::<R>(),
310 rng.random::<R>(),
311 rng.random::<R>(),
312 )
313 },
314 |(mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j)| {
315 for _ in 0..N {
316 (a, b, c, d, e, f, g, h, i, j) = (
317 a - b,
318 b - c,
319 c - d,
320 d - e,
321 e - f,
322 f - g,
323 g - h,
324 h - i,
325 i - j,
326 j - a,
327 );
328 }
329 (a, b, c, d, e, f, g, h, i, j)
330 },
331 BatchSize::SmallInput,
332 );
333 });
334}
335
336pub fn benchmark_mul_latency<R: PrimeCharacteristicRing + Copy, const N: usize>(
337 c: &mut Criterion,
338 name: &str,
339) where
340 StandardUniform: Distribution<R>,
341{
342 c.bench_function(&format!("mul-latency/{N} {name}"), |b| {
343 b.iter_batched(
344 || {
345 let mut rng = SmallRng::seed_from_u64(1);
346 let mut vec = Vec::new();
347 for _ in 0..N {
348 vec.push(rng.random::<R>());
349 }
350 vec
351 },
352 |x| x.iter().fold(R::ONE, |x, y| x * *y),
353 BatchSize::SmallInput,
354 );
355 });
356}
357
358pub fn benchmark_mul_throughput<R: PrimeCharacteristicRing + Copy, const N: usize>(
359 c: &mut Criterion,
360 name: &str,
361) where
362 StandardUniform: Distribution<R>,
363{
364 c.bench_function(&format!("mul-throughput/{N} {name}"), |b| {
365 b.iter_batched(
366 || {
367 let mut rng = SmallRng::seed_from_u64(1);
368 (
369 rng.random::<R>(),
370 rng.random::<R>(),
371 rng.random::<R>(),
372 rng.random::<R>(),
373 rng.random::<R>(),
374 rng.random::<R>(),
375 rng.random::<R>(),
376 rng.random::<R>(),
377 rng.random::<R>(),
378 rng.random::<R>(),
379 )
380 },
381 |(mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j)| {
382 for _ in 0..N {
383 (a, b, c, d, e, f, g, h, i, j) = (
384 a * b,
385 b * c,
386 c * d,
387 d * e,
388 e * f,
389 f * g,
390 g * h,
391 h * i,
392 i * j,
393 j * a,
394 );
395 }
396 (a, b, c, d, e, f, g, h, i, j)
397 },
398 BatchSize::SmallInput,
399 );
400 });
401}
402
403pub fn benchmark_div_latency<R: PrimeCharacteristicRing + Copy + Div<Output = R>, const N: usize>(
404 c: &mut Criterion,
405 name: &str,
406) where
407 StandardUniform: Distribution<R>,
408{
409 c.bench_function(&format!("div-latency/{N} {name}"), |b| {
410 b.iter_batched(
411 || {
412 let mut rng = SmallRng::seed_from_u64(1);
413 let init = rng.random::<R>();
414 let mut vec = Vec::with_capacity(N);
415 for _ in 0..N {
416 vec.push(rng.random::<R>());
417 }
418 (init, vec)
419 },
420 |(init, vec)| vec.iter().fold(init, |x, y| x / *y),
421 BatchSize::SmallInput,
422 );
423 });
424}
425
426pub fn benchmark_div_throughput<
427 R: PrimeCharacteristicRing + Copy + Div<Output = R>,
428 const N: usize,
429>(
430 c: &mut Criterion,
431 name: &str,
432) where
433 StandardUniform: Distribution<R>,
434{
435 c.bench_function(&format!("div-throughput/{N} {name}"), |b| {
436 b.iter_batched(
437 || {
438 let mut rng = SmallRng::seed_from_u64(1);
439 (
440 rng.random::<R>(),
441 rng.random::<R>(),
442 rng.random::<R>(),
443 rng.random::<R>(),
444 rng.random::<R>(),
445 rng.random::<R>(),
446 rng.random::<R>(),
447 rng.random::<R>(),
448 rng.random::<R>(),
449 rng.random::<R>(),
450 )
451 },
452 |(mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j)| {
453 for _ in 0..N {
454 (a, b, c, d, e, f, g, h, i, j) = (
455 a / b,
456 b / c,
457 c / d,
458 d / e,
459 e / f,
460 f / g,
461 g / h,
462 h / i,
463 i / j,
464 j / a,
465 );
466 }
467 (a, b, c, d, e, f, g, h, i, j)
468 },
469 BatchSize::SmallInput,
470 );
471 });
472}
473
474pub fn benchmark_base_mul_latency<F: Field, A: Algebra<F> + Copy, const N: usize>(
475 c: &mut Criterion,
476 name: &str,
477) where
478 StandardUniform: Distribution<F> + Distribution<A>,
479{
480 c.bench_function(&format!("base_mul-latency/{N} {name}"), |b| {
481 b.iter_batched(
482 || {
483 let mut rng = SmallRng::seed_from_u64(1);
484 let mut vec = Vec::new();
485 for _ in 0..N {
486 vec.push(rng.random::<F>());
487 }
488 let init_val = rng.random::<A>();
489 (vec, init_val)
490 },
491 |(x, init_val)| x.iter().fold(init_val, |x, y| x * *y),
492 BatchSize::SmallInput,
493 );
494 });
495}
496
497pub fn benchmark_base_mul_throughput<F: Field, A: Algebra<F> + Copy, const N: usize>(
498 c: &mut Criterion,
499 name: &str,
500) where
501 StandardUniform: Distribution<F> + Distribution<A>,
502{
503 c.bench_function(&format!("base_mul-throughput/{N} {name}"), |b| {
504 b.iter_batched(
505 || {
506 let mut rng = SmallRng::seed_from_u64(1);
507 let a_tuple = (
508 rng.random::<A>(),
509 rng.random::<A>(),
510 rng.random::<A>(),
511 rng.random::<A>(),
512 rng.random::<A>(),
513 rng.random::<A>(),
514 rng.random::<A>(),
515 rng.random::<A>(),
516 rng.random::<A>(),
517 rng.random::<A>(),
518 );
519 let f_tuple = (
520 rng.random::<F>(),
521 rng.random::<F>(),
522 rng.random::<F>(),
523 rng.random::<F>(),
524 rng.random::<F>(),
525 rng.random::<F>(),
526 rng.random::<F>(),
527 rng.random::<F>(),
528 rng.random::<F>(),
529 rng.random::<F>(),
530 );
531 (a_tuple, f_tuple)
532 },
533 |(
534 (mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j),
535 (a_f, b_f, c_f, d_f, e_f, f_f, g_f, h_f, i_f, j_f),
536 )| {
537 for _ in 0..N {
538 (a, b, c, d, e, f, g, h, i, j) = (
539 a * a_f,
540 b * b_f,
541 c * c_f,
542 d * d_f,
543 e * e_f,
544 f * f_f,
545 g * g_f,
546 h * h_f,
547 i * i_f,
548 j * j_f,
549 );
550 }
551 (a, b, c, d, e, f, g, h, i, j)
552 },
553 BatchSize::SmallInput,
554 );
555 });
556}
557
558pub fn benchmark_exp_const<R: PrimeCharacteristicRing + Copy, const POWER: u64, const REPS: usize>(
563 c: &mut Criterion,
564 name: &str,
565) where
566 StandardUniform: Distribution<R>,
567{
568 let mut rng = SmallRng::seed_from_u64(1);
569 let input: Vec<R> = (0..REPS).map(|_| rng.random()).collect();
570
571 c.bench_function(&format!("{name} exp_const<{POWER}>/{REPS}"), |b| {
572 b.iter_batched(
573 || input.clone(),
574 |mut data| {
575 for x in data.iter_mut() {
576 *x = x.exp_const_u64::<POWER>();
577 }
578 black_box(data);
579 },
580 BatchSize::SmallInput,
581 );
582 });
583}
584
585pub fn benchmark_neg_latency<R: PrimeCharacteristicRing + Copy, const N: usize>(
586 c: &mut Criterion,
587 name: &str,
588) where
589 StandardUniform: Distribution<R>,
590{
591 c.bench_function(&format!("neg-latency/{N} {name}"), |b| {
592 b.iter_batched(
593 || {
594 let mut rng = SmallRng::seed_from_u64(1);
595 let mut vec = Vec::new();
596 for _ in 0..N {
597 vec.push(rng.random::<R>());
598 }
599 vec
600 },
601 |x| x.iter().fold(R::ZERO, |acc, y| -(acc + *y)),
602 BatchSize::SmallInput,
603 );
604 });
605}
606
607pub fn benchmark_neg_throughput<R: PrimeCharacteristicRing + Copy, const N: usize>(
608 c: &mut Criterion,
609 name: &str,
610) where
611 StandardUniform: Distribution<R>,
612{
613 c.bench_function(&format!("neg-throughput/{N} {name}"), |b| {
614 b.iter_batched(
615 || {
616 let mut rng = SmallRng::seed_from_u64(1);
617 (
618 rng.random::<R>(),
619 rng.random::<R>(),
620 rng.random::<R>(),
621 rng.random::<R>(),
622 rng.random::<R>(),
623 rng.random::<R>(),
624 rng.random::<R>(),
625 rng.random::<R>(),
626 rng.random::<R>(),
627 rng.random::<R>(),
628 )
629 },
630 |(mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j)| {
631 for _ in 0..N {
632 (a, b, c, d, e, f, g, h, i, j) = (-a, -b, -c, -d, -e, -f, -g, -h, -i, -j);
633 }
634 (a, b, c, d, e, f, g, h, i, j)
635 },
636 BatchSize::SmallInput,
637 );
638 });
639}
640
641pub fn benchmark_double_latency<R: PrimeCharacteristicRing + Copy, const N: usize>(
642 c: &mut Criterion,
643 name: &str,
644) where
645 StandardUniform: Distribution<R>,
646{
647 c.bench_function(&format!("double-latency/{N} {name}"), |b| {
648 b.iter_batched(
649 || {
650 let mut rng = SmallRng::seed_from_u64(1);
651 black_box(rng.random::<R>())
652 },
653 |x| {
654 let mut acc = x;
655 for _ in 0..N {
656 acc = acc.double();
657 }
658 acc
659 },
660 BatchSize::SmallInput,
661 );
662 });
663}
664
665pub fn benchmark_double_throughput<R: PrimeCharacteristicRing + Copy, const N: usize>(
666 c: &mut Criterion,
667 name: &str,
668) where
669 StandardUniform: Distribution<R>,
670{
671 c.bench_function(&format!("double-throughput/{N} {name}"), |b| {
672 b.iter_batched(
673 || {
674 let mut rng = SmallRng::seed_from_u64(1);
675 (
676 rng.random::<R>(),
677 rng.random::<R>(),
678 rng.random::<R>(),
679 rng.random::<R>(),
680 rng.random::<R>(),
681 rng.random::<R>(),
682 rng.random::<R>(),
683 rng.random::<R>(),
684 rng.random::<R>(),
685 rng.random::<R>(),
686 )
687 },
688 |(mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j)| {
689 for _ in 0..N {
690 (a, b, c, d, e, f, g, h, i, j) = (
691 a.double(),
692 b.double(),
693 c.double(),
694 d.double(),
695 e.double(),
696 f.double(),
697 g.double(),
698 h.double(),
699 i.double(),
700 j.double(),
701 );
702 }
703 (a, b, c, d, e, f, g, h, i, j)
704 },
705 BatchSize::SmallInput,
706 );
707 });
708}
709
710pub fn benchmark_chunked_linear_combination<F: Field, A: Algebra<F> + Copy, const LEN: usize>(
713 c: &mut Criterion,
714 name: &str,
715) where
716 StandardUniform: Distribution<F> + Distribution<A>,
717{
718 let mut rng = SmallRng::seed_from_u64(1);
719 let values: Vec<A> = (0..LEN).map(|_| rng.random()).collect();
720 let coeffs: Vec<F> = (0..LEN).map(|_| rng.random()).collect();
721
722 macro_rules! bench_chunk {
723 ($($chunk:literal),*) => {$(
724 c.bench_function(
725 &format!("{name} batched_lc/chunk={}, len={LEN}", $chunk),
726 |b| {
727 b.iter(|| {
728 chunked_linear_combination::<$chunk, A, F>(
729 black_box(values.as_slice()),
730 black_box(coeffs.as_slice()),
731 )
732 });
733 },
734 );
735 )*};
736 }
737 bench_chunk!(1, 2, 4, 8, 16, 32, 64);
738}
739
740#[macro_export]
762macro_rules! bench_packed_extension_field {
763 ($base:ty, $($label:ident = $ef:ty),+ $(,)?) => {
764 const REPS: usize = 100;
766 const L_REPS: usize = 10 * REPS;
767
768 $(
769 fn $label(c: &mut criterion::Criterion) {
770 type Packed = <$ef as p3_field::ExtensionField<$base>>::ExtensionPacking;
771 let name = stringify!($ef);
772 $crate::bench_func::benchmark_add_throughput::<Packed, REPS>(c, name);
773 $crate::bench_func::benchmark_add_latency::<Packed, L_REPS>(c, name);
774 $crate::bench_func::benchmark_mul_throughput::<Packed, REPS>(c, name);
775 $crate::bench_func::benchmark_mul_latency::<Packed, L_REPS>(c, name);
776 $crate::bench_func::benchmark_div_throughput::<Packed, REPS>(c, name);
777 $crate::bench_func::benchmark_div_latency::<Packed, L_REPS>(c, name);
778 }
779 )+
780
781 criterion::criterion_group!(packed_ext_benches, $($label),+);
782 criterion::criterion_main!(packed_ext_benches);
783 };
784}