diff --git a/Cargo.toml b/Cargo.toml index 6e3c6597652955e21c40416340addf866640f292..ec3431564628300cb3bfcd2f51174dabbd4972e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,16 @@ harness = false name = "commit" harness = false +[[bench]] +name = "field_operations" +path = "benches/operations/field.rs" +harness = false + +[[bench]] +name = "curve_group_operations" +path = "benches/operations/curve_group.rs" +harness = false + [[example]] name = "bench_commit" path = "examples/benches/commit.rs" diff --git a/benches/README.md b/benches/README.md index 5feeb4cfe8d74614a80c9758eaddfbffcad4941a..6f72baa6bf8fd1dbae9650480564282020f72314 100644 --- a/benches/README.md +++ b/benches/README.md @@ -14,11 +14,34 @@ python scripts/plot/benches.py results.ndjson --bench linalg python scripts/plot/benches.py results.ndjson --bench setup ``` +## atomic operations +```nushell +cargo criterion --output-format verbose --message-format json --bench field_operations out> field.ndjson +cargo criterion --output-format verbose --message-format json --bench curve_group_operations out> curve.ndjson +``` +```nushell +def read-atomic-ops []: list -> record { + where reason == "benchmark-complete" + | select id mean.estimate + | rename --column { mean_estimate: "mean" } + | sort-by id + | update id { parse "{op} on {curve}" } + | flatten --all + | group-by op --to-table + | reject items.op + | update items { transpose -r | into record } + | transpose -r + | into record +} +python scripts/plot/multi_bar.py (open field.ndjson | read-atomic-ops | to json) --title "field operations" -l "time (in ns)" +python scripts/plot/multi_bar.py (open curve.ndjson | read-atomic-ops | to json) --title "curve group operations" -l "time (in ns)" +``` + ## oneshot benchmarks these are benchmarks that run a single measurement, implemented as _examples_ in `examples/benches/`. -## commit +### commit ```nushell let res = cargo run --example bench_commit | lines diff --git a/benches/operations/curve_group.rs b/benches/operations/curve_group.rs new file mode 100644 index 0000000000000000000000000000000000000000..7a4047f9eca71921670ab5dbc725fda2c51f0110 --- /dev/null +++ b/benches/operations/curve_group.rs @@ -0,0 +1,112 @@ +// see `benches/README.md` +use std::time::Duration; + +use ark_ec::CurveGroup; +use ark_ff::PrimeField; + +use criterion::{criterion_group, criterion_main, Criterion}; + +fn random_sampling_template<G: CurveGroup>(c: &mut Criterion, curve: &str) { + let mut rng = ark_std::test_rng(); + + c.bench_function(&format!("random sampling on {}", curve), |b| { + b.iter(|| { + let _ = G::rand(&mut rng); + }); + }); +} + +fn group_template<F: PrimeField, G: CurveGroup<ScalarField = F>>(c: &mut Criterion, curve: &str) { + let mut rng = ark_std::test_rng(); + + c.bench_function(&format!("addition on {}", curve), |b| { + b.iter(|| { + let g1 = G::rand(&mut rng); + let g2 = G::rand(&mut rng); + g1 + g2 + }); + }); + + c.bench_function(&format!("substraction on {}", curve), |b| { + b.iter(|| { + let g1 = G::rand(&mut rng); + let g2 = G::rand(&mut rng); + g1 - g2 + }); + }); + + c.bench_function(&format!("double on {}", curve), |b| { + b.iter(|| { + let g1 = G::rand(&mut rng); + g1.double() + }); + }); + + c.bench_function(&format!("scalar multiplication on {}", curve), |b| { + b.iter(|| { + let g1 = G::rand(&mut rng); + let f1 = F::rand(&mut rng); + g1.mul(f1) + }); + }); +} + +fn curve_group_template<F: PrimeField, G: CurveGroup<ScalarField = F>>( + c: &mut Criterion, + curve: &str, +) { + let mut rng = ark_std::test_rng(); + + c.bench_function(&format!("into affine on {}", curve), |b| { + b.iter(|| { + let g1 = G::rand(&mut rng); + g1.into_affine() + }); + }); + + c.bench_function(&format!("from affine on {}", curve), |b| { + b.iter(|| { + let g1_affine = G::rand(&mut rng).into_affine(); + let _: G = g1_affine.into(); + }); + }); + + c.bench_function(&format!("affine addition on {}", curve), |b| { + b.iter(|| { + let g1_affine = G::rand(&mut rng).into_affine(); + let g2_affine = G::rand(&mut rng).into_affine(); + g1_affine + g2_affine + }); + }); + + c.bench_function(&format!("affine scalar multiplication on {}", curve), |b| { + b.iter(|| { + let g1_affine = G::rand(&mut rng).into_affine(); + let f1 = F::rand(&mut rng); + g1_affine * f1 + }); + }); +} + +macro_rules! bench { + ($c:ident, $b:ident, G1=$g:ident, name=$n:expr) => { + random_sampling_template::<$c::$g>($b, $n); + group_template::<$c::Fr, $c::$g>($b, $n); + curve_group_template::<$c::Fr, $c::$g>($b, $n); + }; +} + +fn bench(c: &mut Criterion) { + bench!(ark_bls12_381, c, G1 = G1Projective, name = "BLS12-381"); + bench!(ark_bn254, c, G1 = G1Projective, name = "BN-254"); + bench!(ark_pallas, c, G1 = Projective, name = "PALLAS"); +} + +criterion_group!( + name = benches; + config = Criterion::default() + .warm_up_time(Duration::from_secs_f32(0.5)) + .sample_size(10); + targets = bench +); +criterion_main!(benches); diff --git a/benches/operations/field.rs b/benches/operations/field.rs new file mode 100644 index 0000000000000000000000000000000000000000..f5d85bbd1c7b9a455eb4fd1517168f4c12d80e58 --- /dev/null +++ b/benches/operations/field.rs @@ -0,0 +1,132 @@ +// see `benches/README.md` +use std::time::Duration; + +use ark_ff::PrimeField; + +use criterion::{criterion_group, criterion_main, Criterion}; + +fn random_sampling_template<F: PrimeField>(c: &mut Criterion, curve: &str) { + let mut rng = ark_std::test_rng(); + + c.bench_function(&format!("random sampling on {}", curve), |b| { + b.iter(|| { + let _ = F::rand(&mut rng); + }); + }); +} + +fn additive_group_template<F: PrimeField>(c: &mut Criterion, curve: &str) { + let mut rng = ark_std::test_rng(); + + c.bench_function(&format!("addition on {}", curve), |b| { + b.iter(|| { + let f1 = F::rand(&mut rng); + let f2 = F::rand(&mut rng); + f1 + f2 + }); + }); + + c.bench_function(&format!("substraction on {}", curve), |b| { + b.iter(|| { + let f1 = F::rand(&mut rng); + let f2 = F::rand(&mut rng); + f1 - f2 + }); + }); + + c.bench_function(&format!("double on {}", curve), |b| { + b.iter(|| { + let f1 = F::rand(&mut rng); + f1.double() + }); + }); + + c.bench_function(&format!("multiplication on {}", curve), |b| { + b.iter(|| { + let f1 = F::rand(&mut rng); + let f2 = F::rand(&mut rng); + f1 * f2 + }); + }); +} + +fn field_template<F: PrimeField>(c: &mut Criterion, curve: &str) { + let mut rng = ark_std::test_rng(); + + c.bench_function(&format!("square on {}", curve), |b| { + b.iter(|| { + let f1 = F::rand(&mut rng); + f1.square() + }); + }); + + c.bench_function(&format!("inverse on {}", curve), |b| { + b.iter(|| { + let f1 = F::rand(&mut rng); + f1.inverse() + }); + }); + + c.bench_function(&format!("legendre on {}", curve), |b| { + b.iter(|| { + let f1 = F::rand(&mut rng); + f1.legendre() + }); + }); + + c.bench_function(&format!("sqrt on {}", curve), |b| { + b.iter(|| { + let f1 = F::rand(&mut rng); + if f1.legendre().is_qr() { + let _ = f1.sqrt(); + } + }); + }); +} + +fn prime_field_template<F: PrimeField>(c: &mut Criterion, curve: &str) { + let mut rng = ark_std::test_rng(); + + c.bench_function(&format!("exponentiation on {}", curve), |b| { + b.iter(|| { + let f1 = F::rand(&mut rng); + f1.pow(F::MODULUS) + }); + }); +} + +fn conversions_template<F: PrimeField>(c: &mut Criterion, curve: &str) { + let mut rng = ark_std::test_rng(); + + c.bench_function(&format!("bigint conversion on {}", curve), |b| { + b.iter(|| { + let f1 = F::rand(&mut rng); + f1.into_bigint() + }); + }); +} + +macro_rules! bench { + ($c:ident, $b:ident, F=$f:ident, name=$n:expr) => { + random_sampling_template::<$c::$f>($b, $n); + additive_group_template::<$c::$f>($b, $n); + field_template::<$c::$f>($b, $n); + prime_field_template::<$c::$f>($b, $n); + conversions_template::<$c::$f>($b, $n); + }; +} + +fn bench(c: &mut Criterion) { + bench!(ark_bls12_381, c, F = Fr, name = "BLS12-381"); + bench!(ark_bn254, c, F = Fr, name = "BN-254"); + bench!(ark_pallas, c, F = Fr, name = "PALLAS"); +} + +criterion_group!( + name = benches; + config = Criterion::default() + .warm_up_time(Duration::from_secs_f32(0.5)) + .sample_size(10); + targets = bench +); +criterion_main!(benches); diff --git a/scripts/plot/multi_bar.py b/scripts/plot/multi_bar.py new file mode 100644 index 0000000000000000000000000000000000000000..2cbee35add2192151b86ec840b16907d5bc18eb7 --- /dev/null +++ b/scripts/plot/multi_bar.py @@ -0,0 +1,125 @@ +# see `benches/README.md` +from typing import Dict, List + +import matplotlib.pyplot as plt +import numpy as np +import json +import sys +import argparse + + +# convert raw data into groups and measurements +# +# # Example +# input: +# ```json +# { +# "age": { +# "alice": 31, +# "bob": 28, +# "charlie": 44 +# }, +# "height": { +# "alice": 1.50, +# "bob": 1.82, +# "charlie": 1.65 +# }, +# "weight": { +# "alice": 65.3, +# "bob": 98.1, +# "charlie": 68.7 +# } +# } +# ``` +# +# output: +# ``` +# groups = ["age", "height", "weight"] +# measurements = { +# "alice": [1.50, 31, 65.3], +# "bob": [1.82, 28, 98.1], +# "charlie": [1.65, 44, 68.7] +# } +# ``` +def extract(data: Dict[str, Dict[str, float]]) -> (List[str], Dict[str, List[float]]): + groups = list(data.keys()) + + measurements = {} + for x in data[groups[0]].keys(): + measurements[x] = [data[g][x] for g in groups] + + return (groups, measurements) + + +# plot multi bars +# +# # Example +# input can be the output of [`extract`] +def plot_multi_bar( + groups: List[str], + measurements: Dict[str, List[float]], + title: str, + y_label: str, + labels_locations: List[float] = None, + width: float = 0.25, + nb_legend_cols: int = 3, + legend_loc: str = "upper left", + plot_layout: str = "constrained", + save: str = None, +): + if labels_locations is None: + labels_locations = np.arange(len(groups)) + + fig, ax = plt.subplots(layout=plot_layout) + + multiplier = 0 + for attribute, measurement in measurements.items(): + offset = width * multiplier + rects = ax.bar(labels_locations + offset, measurement, width, label=attribute) + ax.bar_label(rects, padding=3) + multiplier += 1 + + ax.set_ylabel(y_label) + ax.set_title(title) + ax.set_xticks(labels_locations + width, groups) + ax.legend(loc=legend_loc, ncols=nb_legend_cols) + + if save is not None: + fig.set_size_inches((16, 9), forward=False) + fig.savefig(save, dpi=500) + + print(f"plot saved as `{save}`") + else: + plt.show() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("data", type=str, help=""" + the actual data to show in a multibar plot, here is an example: + { + "age": { + "alice": 31, + "bob": 28, + "charlie": 44 + }, + "height": { + "alice": 1.50, + "bob": 1.82, + "charlie": 1.65 + }, + "weight": { + "alice": 65.3, + "bob": 98.1, + "charlie": 68.7 + } + } + """) + parser.add_argument("--title", "-t", type=str, help="the title of the multibar plot") + parser.add_argument("--label", "-l", type=str, help="the measurement label of the multibar plot") + parser.add_argument("--save", "-s", type=str, help="a path to save the figure to") + args = parser.parse_args() + + groups, measurements = extract(json.loads(args.data)) + + plot_multi_bar(groups, measurements, args.title, args.label, save=args.save)