Skip to main content

p3_field_testing/
bench_func.rs

1use alloc::format;
2use alloc::vec::Vec;
3use core::hint::black_box;
4
5use criterion::{BatchSize, Criterion};
6use p3_field::{Algebra, Field, PrimeCharacteristicRing, chunked_linear_combination};
7use rand::distr::StandardUniform;
8use rand::prelude::Distribution;
9use rand::rngs::SmallRng;
10use rand::{RngExt, SeedableRng};
11
12/// Not useful for benchmarking prime fields as multiplication is too fast but
13/// handy for extension fields.
14pub fn benchmark_mul<F: Field>(c: &mut Criterion, name: &str)
15where
16    StandardUniform: Distribution<F>,
17{
18    let mut rng = SmallRng::seed_from_u64(1);
19    let x = rng.random::<F>();
20    let y = rng.random::<F>();
21    c.bench_function(&format!("{name} mul"), |b| {
22        b.iter(|| black_box(black_box(x) * black_box(y)));
23    });
24}
25
26pub fn benchmark_square<F: Field>(c: &mut Criterion, name: &str)
27where
28    StandardUniform: Distribution<F>,
29{
30    let mut rng = SmallRng::seed_from_u64(1);
31    let x = rng.random::<F>();
32    c.bench_function(&format!("{name} square"), |b| {
33        b.iter(|| black_box(black_box(x).square()));
34    });
35}
36
37pub fn benchmark_inv<F: Field>(c: &mut Criterion, name: &str)
38where
39    StandardUniform: Distribution<F>,
40{
41    let mut rng = SmallRng::seed_from_u64(1);
42    let x = rng.random::<F>();
43    c.bench_function(&format!("{name} inv"), |b| {
44        b.iter(|| black_box(black_box(x)).inverse());
45    });
46}
47
48pub fn benchmark_mul_2exp<R: PrimeCharacteristicRing + Copy, const REPS: usize>(
49    c: &mut Criterion,
50    name: &str,
51    val: u64,
52) where
53    StandardUniform: Distribution<R>,
54{
55    let mut rng = SmallRng::seed_from_u64(1);
56    let mut input = Vec::new();
57    for _ in 0..REPS {
58        input.push(rng.random::<R>());
59    }
60    c.bench_function(&format!("{name} mul_2exp_u64 {val}"), |b| {
61        b.iter(|| input.iter_mut().for_each(|i| *i = i.mul_2exp_u64(val)));
62    });
63}
64
65pub fn benchmark_halve<F: Field, const REPS: usize>(c: &mut Criterion, name: &str)
66where
67    StandardUniform: Distribution<F>,
68{
69    let mut rng = SmallRng::seed_from_u64(1);
70    let mut input = Vec::new();
71    for _ in 0..REPS {
72        input.push(rng.random::<F>());
73    }
74    c.bench_function(&format!("{name} halve. Num Reps: {REPS}"), |b| {
75        b.iter(|| input.iter_mut().for_each(|i| *i = i.halve()));
76    });
77}
78
79pub fn benchmark_div_2exp<F: Field, const REPS: usize>(c: &mut Criterion, name: &str, val: u64)
80where
81    StandardUniform: Distribution<F>,
82{
83    let mut rng = SmallRng::seed_from_u64(1);
84    let mut input = Vec::new();
85    for _ in 0..REPS {
86        input.push(rng.random::<F>());
87    }
88    c.bench_function(&format!("{name} div_2exp_u64 {val}"), |b| {
89        b.iter(|| input.iter_mut().for_each(|i| *i = i.div_2exp_u64(val)));
90    });
91}
92
93/// Benchmark the time taken to sum an array [[F; N]; REPS] by summing each array
94/// [F; N] using .sum() method and accumulating the sums into an accumulator.
95///
96/// Making N larger and REPS smaller (vs the opposite) leans the benchmark more sensitive towards
97/// the latency (resp throughput) of the sum method.
98pub fn benchmark_iter_sum<R: PrimeCharacteristicRing + Copy, const N: usize, const REPS: usize>(
99    c: &mut Criterion,
100    name: &str,
101) where
102    StandardUniform: Distribution<R>,
103{
104    let mut rng = SmallRng::seed_from_u64(1);
105    let mut input = Vec::new();
106    for _ in 0..REPS {
107        input.push(rng.random::<[R; N]>());
108    }
109    c.bench_function(&format!("{name} sum/{REPS}, {N}"), |b| {
110        b.iter(|| {
111            let mut acc = R::ZERO;
112            for row in &mut input {
113                acc += row.iter().copied().sum();
114            }
115            acc
116        });
117    });
118}
119
120/// Benchmark the time taken to sum an array [[F; N]; REPS] by summing each array
121/// [F; N] using sum_array method and accumulating the sums into an accumulator.
122///
123/// Making N larger and REPS smaller (vs the opposite) leans the benchmark more sensitive towards
124/// the latency (resp throughput) of the sum method.
125pub fn benchmark_sum_array<R: PrimeCharacteristicRing + Copy, const N: usize, const REPS: usize>(
126    c: &mut Criterion,
127    name: &str,
128) where
129    StandardUniform: Distribution<R>,
130{
131    let mut rng = SmallRng::seed_from_u64(1);
132    let mut input = Vec::new();
133    for _ in 0..REPS {
134        input.push(rng.random::<[R; N]>());
135    }
136    c.bench_function(&format!("{name} tree sum/{REPS}, {N}"), |b| {
137        b.iter(|| {
138            let mut acc = R::ZERO;
139            for row in &mut input {
140                acc += R::sum_array::<N>(row);
141            }
142            acc
143        });
144    });
145}
146
147/// Benchmark the time taken to do dot products on a pair of `[R; N]` arrays.
148///
149/// These numbers get more trustworthy as N increases. Small N leads to the
150/// computation being too fast to be measured accurately.
151pub fn benchmark_dot_array<R: PrimeCharacteristicRing + Copy, const N: usize>(
152    c: &mut Criterion,
153    name: &str,
154) where
155    StandardUniform: Distribution<R>,
156{
157    let mut rng = SmallRng::seed_from_u64(1);
158    let lhs = rng.random::<[R; N]>();
159    let rhs = rng.random::<[R; N]>();
160
161    c.bench_function(&format!("{name} dot product/{N}"), |b| {
162        b.iter(|| black_box(R::dot_product(black_box(&lhs), black_box(&rhs))));
163    });
164}
165
166/// Benchmark the time taken to do mixed dot products on a pair of `[A; N]` and `[F; N]` arrays.
167pub fn benchmark_mixed_dot_array<A: Algebra<F> + Copy, F: Field, const N: usize>(
168    c: &mut Criterion,
169    name: &str,
170) where
171    StandardUniform: Distribution<A> + Distribution<F>,
172{
173    let mut rng = SmallRng::seed_from_u64(1);
174    let a = rng.random::<[A; N]>();
175    let f = rng.random::<[F; N]>();
176    c.bench_function(&format!("{name} mixed dot product/{N}"), |b| {
177        b.iter(|| black_box(A::mixed_dot_product(black_box(&a), black_box(&f))));
178    });
179}
180
181/// Benchmark the time taken to add two slices together.
182pub fn benchmark_add_slices<F: Field, const LENGTH: usize>(c: &mut Criterion, name: &str)
183where
184    StandardUniform: Distribution<F>,
185{
186    let mut rng = SmallRng::seed_from_u64(1);
187    let mut slice_1 = Vec::new();
188    let mut slice_2 = Vec::new();
189    for _ in 0..LENGTH {
190        slice_1.push(rng.random());
191        slice_2.push(rng.random());
192    }
193    c.bench_function(&format!("{name} add slices/{LENGTH}"), |b| {
194        let mut in_slice = slice_1.clone();
195        b.iter(|| {
196            F::add_slices(&mut in_slice, &slice_2);
197        });
198    });
199}
200
201pub fn benchmark_add_latency<R: PrimeCharacteristicRing + Copy, const N: usize>(
202    c: &mut Criterion,
203    name: &str,
204) where
205    StandardUniform: Distribution<R>,
206{
207    c.bench_function(&format!("add-latency/{N} {name}"), |b| {
208        b.iter_batched(
209            || {
210                let mut rng = SmallRng::seed_from_u64(1);
211                let mut vec = Vec::new();
212                for _ in 0..N {
213                    vec.push(rng.random::<R>());
214                }
215                vec
216            },
217            |x| x.iter().fold(R::ZERO, |x, y| x + *y),
218            BatchSize::SmallInput,
219        );
220    });
221}
222
223pub fn benchmark_add_throughput<R: PrimeCharacteristicRing + Copy, const N: usize>(
224    c: &mut Criterion,
225    name: &str,
226) where
227    StandardUniform: Distribution<R>,
228{
229    c.bench_function(&format!("add-throughput/{N} {name}"), |b| {
230        b.iter_batched(
231            || {
232                let mut rng = SmallRng::seed_from_u64(1);
233                (
234                    rng.random::<R>(),
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                )
245            },
246            |(mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j)| {
247                for _ in 0..N {
248                    (a, b, c, d, e, f, g, h, i, j) = (
249                        a + b,
250                        b + c,
251                        c + d,
252                        d + e,
253                        e + f,
254                        f + g,
255                        g + h,
256                        h + i,
257                        i + j,
258                        j + a,
259                    );
260                }
261                (a, b, c, d, e, f, g, h, i, j)
262            },
263            BatchSize::SmallInput,
264        );
265    });
266}
267
268pub fn benchmark_sub_latency<R: PrimeCharacteristicRing + Copy, const N: usize>(
269    c: &mut Criterion,
270    name: &str,
271) where
272    StandardUniform: Distribution<R>,
273{
274    c.bench_function(&format!("sub-latency/{N} {name}"), |b| {
275        b.iter_batched(
276            || {
277                let mut rng = SmallRng::seed_from_u64(1);
278                let mut vec = Vec::new();
279                for _ in 0..N {
280                    vec.push(rng.random::<R>());
281                }
282                vec
283            },
284            |x| x.iter().fold(R::ZERO, |x, y| x - *y),
285            BatchSize::SmallInput,
286        );
287    });
288}
289
290pub fn benchmark_sub_throughput<R: PrimeCharacteristicRing + Copy, const N: usize>(
291    c: &mut Criterion,
292    name: &str,
293) where
294    StandardUniform: Distribution<R>,
295{
296    c.bench_function(&format!("sub-throughput/{N} {name}"), |b| {
297        b.iter_batched(
298            || {
299                let mut rng = SmallRng::seed_from_u64(1);
300                (
301                    rng.random::<R>(),
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                )
312            },
313            |(mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j)| {
314                for _ in 0..N {
315                    (a, b, c, d, e, f, g, h, i, j) = (
316                        a - b,
317                        b - c,
318                        c - d,
319                        d - e,
320                        e - f,
321                        f - g,
322                        g - h,
323                        h - i,
324                        i - j,
325                        j - a,
326                    );
327                }
328                (a, b, c, d, e, f, g, h, i, j)
329            },
330            BatchSize::SmallInput,
331        );
332    });
333}
334
335pub fn benchmark_mul_latency<R: PrimeCharacteristicRing + Copy, const N: usize>(
336    c: &mut Criterion,
337    name: &str,
338) where
339    StandardUniform: Distribution<R>,
340{
341    c.bench_function(&format!("mul-latency/{N} {name}"), |b| {
342        b.iter_batched(
343            || {
344                let mut rng = SmallRng::seed_from_u64(1);
345                let mut vec = Vec::new();
346                for _ in 0..N {
347                    vec.push(rng.random::<R>());
348                }
349                vec
350            },
351            |x| x.iter().fold(R::ONE, |x, y| x * *y),
352            BatchSize::SmallInput,
353        );
354    });
355}
356
357pub fn benchmark_mul_throughput<R: PrimeCharacteristicRing + Copy, const N: usize>(
358    c: &mut Criterion,
359    name: &str,
360) where
361    StandardUniform: Distribution<R>,
362{
363    c.bench_function(&format!("mul-throughput/{N} {name}"), |b| {
364        b.iter_batched(
365            || {
366                let mut rng = SmallRng::seed_from_u64(1);
367                (
368                    rng.random::<R>(),
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                )
379            },
380            |(mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j)| {
381                for _ in 0..N {
382                    (a, b, c, d, e, f, g, h, i, j) = (
383                        a * b,
384                        b * c,
385                        c * d,
386                        d * e,
387                        e * f,
388                        f * g,
389                        g * h,
390                        h * i,
391                        i * j,
392                        j * a,
393                    );
394                }
395                (a, b, c, d, e, f, g, h, i, j)
396            },
397            BatchSize::SmallInput,
398        );
399    });
400}
401
402pub fn benchmark_base_mul_latency<F: Field, A: Algebra<F> + Copy, const N: usize>(
403    c: &mut Criterion,
404    name: &str,
405) where
406    StandardUniform: Distribution<F> + Distribution<A>,
407{
408    c.bench_function(&format!("base_mul-latency/{N} {name}"), |b| {
409        b.iter_batched(
410            || {
411                let mut rng = SmallRng::seed_from_u64(1);
412                let mut vec = Vec::new();
413                for _ in 0..N {
414                    vec.push(rng.random::<F>());
415                }
416                let init_val = rng.random::<A>();
417                (vec, init_val)
418            },
419            |(x, init_val)| x.iter().fold(init_val, |x, y| x * *y),
420            BatchSize::SmallInput,
421        );
422    });
423}
424
425pub fn benchmark_base_mul_throughput<F: Field, A: Algebra<F> + Copy, const N: usize>(
426    c: &mut Criterion,
427    name: &str,
428) where
429    StandardUniform: Distribution<F> + Distribution<A>,
430{
431    c.bench_function(&format!("base_mul-throughput/{N} {name}"), |b| {
432        b.iter_batched(
433            || {
434                let mut rng = SmallRng::seed_from_u64(1);
435                let a_tuple = (
436                    rng.random::<A>(),
437                    rng.random::<A>(),
438                    rng.random::<A>(),
439                    rng.random::<A>(),
440                    rng.random::<A>(),
441                    rng.random::<A>(),
442                    rng.random::<A>(),
443                    rng.random::<A>(),
444                    rng.random::<A>(),
445                    rng.random::<A>(),
446                );
447                let f_tuple = (
448                    rng.random::<F>(),
449                    rng.random::<F>(),
450                    rng.random::<F>(),
451                    rng.random::<F>(),
452                    rng.random::<F>(),
453                    rng.random::<F>(),
454                    rng.random::<F>(),
455                    rng.random::<F>(),
456                    rng.random::<F>(),
457                    rng.random::<F>(),
458                );
459                (a_tuple, f_tuple)
460            },
461            |(
462                (mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j),
463                (a_f, b_f, c_f, d_f, e_f, f_f, g_f, h_f, i_f, j_f),
464            )| {
465                for _ in 0..N {
466                    (a, b, c, d, e, f, g, h, i, j) = (
467                        a * a_f,
468                        b * b_f,
469                        c * c_f,
470                        d * d_f,
471                        e * e_f,
472                        f * f_f,
473                        g * g_f,
474                        h * h_f,
475                        i * i_f,
476                        j * j_f,
477                    );
478                }
479                (a, b, c, d, e, f, g, h, i, j)
480            },
481            BatchSize::SmallInput,
482        );
483    });
484}
485
486/// Benchmarks the `exp_const_u64` implementation for a given `POWER`.
487///
488/// This function measures the throughput of the exponentiation by applying the operation
489/// to a vector of `REPS` random elements.
490pub fn benchmark_exp_const<R: PrimeCharacteristicRing + Copy, const POWER: u64, const REPS: usize>(
491    c: &mut Criterion,
492    name: &str,
493) where
494    StandardUniform: Distribution<R>,
495{
496    let mut rng = SmallRng::seed_from_u64(1);
497    let input: Vec<R> = (0..REPS).map(|_| rng.random()).collect();
498
499    c.bench_function(&format!("{name} exp_const<{POWER}>/{REPS}"), |b| {
500        b.iter_batched(
501            || input.clone(),
502            |mut data| {
503                for x in data.iter_mut() {
504                    *x = x.exp_const_u64::<POWER>();
505                }
506                black_box(data);
507            },
508            BatchSize::SmallInput,
509        );
510    });
511}
512
513pub fn benchmark_neg_latency<R: PrimeCharacteristicRing + Copy, const N: usize>(
514    c: &mut Criterion,
515    name: &str,
516) where
517    StandardUniform: Distribution<R>,
518{
519    c.bench_function(&format!("neg-latency/{N} {name}"), |b| {
520        b.iter_batched(
521            || {
522                let mut rng = SmallRng::seed_from_u64(1);
523                let mut vec = Vec::new();
524                for _ in 0..N {
525                    vec.push(rng.random::<R>());
526                }
527                vec
528            },
529            |x| x.iter().fold(R::ZERO, |acc, y| -(acc + *y)),
530            BatchSize::SmallInput,
531        );
532    });
533}
534
535pub fn benchmark_neg_throughput<R: PrimeCharacteristicRing + Copy, const N: usize>(
536    c: &mut Criterion,
537    name: &str,
538) where
539    StandardUniform: Distribution<R>,
540{
541    c.bench_function(&format!("neg-throughput/{N} {name}"), |b| {
542        b.iter_batched(
543            || {
544                let mut rng = SmallRng::seed_from_u64(1);
545                (
546                    rng.random::<R>(),
547                    rng.random::<R>(),
548                    rng.random::<R>(),
549                    rng.random::<R>(),
550                    rng.random::<R>(),
551                    rng.random::<R>(),
552                    rng.random::<R>(),
553                    rng.random::<R>(),
554                    rng.random::<R>(),
555                    rng.random::<R>(),
556                )
557            },
558            |(mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j)| {
559                for _ in 0..N {
560                    (a, b, c, d, e, f, g, h, i, j) = (-a, -b, -c, -d, -e, -f, -g, -h, -i, -j);
561                }
562                (a, b, c, d, e, f, g, h, i, j)
563            },
564            BatchSize::SmallInput,
565        );
566    });
567}
568
569pub fn benchmark_double_latency<R: PrimeCharacteristicRing + Copy, const N: usize>(
570    c: &mut Criterion,
571    name: &str,
572) where
573    StandardUniform: Distribution<R>,
574{
575    c.bench_function(&format!("double-latency/{N} {name}"), |b| {
576        b.iter_batched(
577            || {
578                let mut rng = SmallRng::seed_from_u64(1);
579                black_box(rng.random::<R>())
580            },
581            |x| {
582                let mut acc = x;
583                for _ in 0..N {
584                    acc = acc.double();
585                }
586                acc
587            },
588            BatchSize::SmallInput,
589        );
590    });
591}
592
593pub fn benchmark_double_throughput<R: PrimeCharacteristicRing + Copy, const N: usize>(
594    c: &mut Criterion,
595    name: &str,
596) where
597    StandardUniform: Distribution<R>,
598{
599    c.bench_function(&format!("double-throughput/{N} {name}"), |b| {
600        b.iter_batched(
601            || {
602                let mut rng = SmallRng::seed_from_u64(1);
603                (
604                    rng.random::<R>(),
605                    rng.random::<R>(),
606                    rng.random::<R>(),
607                    rng.random::<R>(),
608                    rng.random::<R>(),
609                    rng.random::<R>(),
610                    rng.random::<R>(),
611                    rng.random::<R>(),
612                    rng.random::<R>(),
613                    rng.random::<R>(),
614                )
615            },
616            |(mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j)| {
617                for _ in 0..N {
618                    (a, b, c, d, e, f, g, h, i, j) = (
619                        a.double(),
620                        b.double(),
621                        c.double(),
622                        d.double(),
623                        e.double(),
624                        f.double(),
625                        g.double(),
626                        h.double(),
627                        i.double(),
628                        j.double(),
629                    );
630                }
631                (a, b, c, d, e, f, g, h, i, j)
632            },
633            BatchSize::SmallInput,
634        );
635    });
636}
637
638/// Benchmark [`chunked_linear_combination`] across all candidate chunk sizes
639/// (1, 2, 4, 8, 16, 32, 64) on `LEN` elements.
640pub fn benchmark_chunked_linear_combination<F: Field, A: Algebra<F> + Copy, const LEN: usize>(
641    c: &mut Criterion,
642    name: &str,
643) where
644    StandardUniform: Distribution<F> + Distribution<A>,
645{
646    let mut rng = SmallRng::seed_from_u64(1);
647    let values: Vec<A> = (0..LEN).map(|_| rng.random()).collect();
648    let coeffs: Vec<F> = (0..LEN).map(|_| rng.random()).collect();
649
650    macro_rules! bench_chunk {
651        ($($chunk:literal),*) => {$(
652            c.bench_function(
653                &format!("{name} batched_lc/chunk={}, len={LEN}", $chunk),
654                |b| {
655                    b.iter(|| {
656                        chunked_linear_combination::<$chunk, A, F>(
657                            black_box(values.as_slice()),
658                            black_box(coeffs.as_slice()),
659                        )
660                    });
661                },
662            );
663        )*};
664    }
665    bench_chunk!(1, 2, 4, 8, 16, 32, 64);
666}