From ef8f75890d40eb630b241ae7cda4504498176f28 Mon Sep 17 00:00:00 2001 From: Pratyush Mishra Date: Wed, 31 May 2023 14:55:08 -0700 Subject: [PATCH 1/5] Reduce benchmark startup time and StarkNet field --- bench-templates/src/macros/ec.rs | 44 ++++++++++++++++------------- bench-templates/src/macros/field.rs | 18 ++++++------ bench-templates/src/macros/mod.rs | 24 ++++++++++++---- ec/src/scalar_mul/mod.rs | 2 +- test-curves/Cargo.toml | 5 ++++ test-curves/benches/starknet_fp.rs | 4 +++ test-curves/src/lib.rs | 2 ++ test-curves/src/starknet_fp.rs | 15 ++++++++++ 8 files changed, 80 insertions(+), 34 deletions(-) create mode 100644 test-curves/benches/starknet_fp.rs create mode 100644 test-curves/src/starknet_fp.rs diff --git a/bench-templates/src/macros/ec.rs b/bench-templates/src/macros/ec.rs index d8abde7f2..a29d3e1ab 100644 --- a/bench-templates/src/macros/ec.rs +++ b/bench-templates/src/macros/ec.rs @@ -28,16 +28,24 @@ macro_rules! ec_bench { let mut rng = ark_std::test_rng(); let mut arithmetic = c.benchmark_group(format!("Arithmetic for {name}")); + // Efficient sampling of inputs for benchmarking + let mut g_left = <$Group>::rand(&mut rng); + let mut g_right = <$Group>::rand(&mut rng); let group_elements_left = (0..SAMPLES) - .map(|_| <$Group>::rand(&mut rng)) + .map(|_| g_left.double()) .collect::>(); - let group_elements_right = (0..SAMPLES) - .map(|_| <$Group>::rand(&mut rng)) + let mut group_elements_right = (0..SAMPLES) + .map(|_| g_right.double()) .collect::>(); + group_elements_right.reverse(); let group_elements_right_affine = <$Group>::normalize_batch(&group_elements_right); + + // Sample scalars let scalars = (0..SAMPLES) .map(|_| Scalar::rand(&mut rng)) .collect::>(); + + // Conduct benchmarks arithmetic.bench_function("Addition", |b| { let mut i = 0; b.iter(|| { @@ -99,7 +107,7 @@ macro_rules! ec_bench { } fn serialization(c: &mut $crate::criterion::Criterion) { - use ark_ec::CurveGroup; + use ark_ec::{AdditiveGroup, CurveGroup}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::UniformRand; @@ -108,9 +116,9 @@ macro_rules! ec_bench { let name = format!("{}::{}", $curve_name, stringify!($Group)); let mut rng = ark_std::test_rng(); - let v: Vec<_> = (0..SAMPLES) - .map(|_| <$Group>::rand(&mut rng)) - .collect(); + // Efficient sampling of inputs for benchmarking + let g = <$Group>::rand(&mut rng); + let v: Vec<_> = (0..SAMPLES).map(|_| g.double()).collect(); let v = <$Group>::normalize_batch(&v); let mut bytes = Vec::with_capacity(1000); let v_compressed = v @@ -129,6 +137,7 @@ macro_rules! ec_bench { bytes }) .collect::>(); + // Start benchmarks let mut serialization = c.benchmark_group(format!("Serialization for {name}")); serialization.bench_function( @@ -205,7 +214,7 @@ macro_rules! ec_bench { } fn msm_131072(c: &mut $crate::criterion::Criterion) { - use ark_ec::{scalar_mul::variable_base::VariableBaseMSM, CurveGroup}; + use ark_ec::{scalar_mul::variable_base::VariableBaseMSM, CurveGroup, AdditiveGroup}; use ark_ff::PrimeField; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::UniformRand; @@ -215,18 +224,15 @@ macro_rules! ec_bench { let name = format!("{}::{}", $curve_name, stringify!($Group)); let mut rng = ark_std::test_rng(); - let v: Vec<_> = (0..SAMPLES) - .map(|_| <$Group>::rand(&mut rng)) - .collect(); - let v = <$Group>::normalize_batch(&v); - let scalars: Vec<_> = (0..SAMPLES) - .map(|_| Scalar::rand(&mut rng).into_bigint()) - .collect(); c.bench_function(&format!("MSM for {name}"), |b| { - b.iter(|| { - let result: $Group = VariableBaseMSM::msm_bigint(&v, &scalars); - result - }) + let g = <$Group>::rand(&mut rng); + let v: Vec<_> = (0..SAMPLES).map(|_| g.double()).collect(); + let v = <$Group>::normalize_batch(&v); + + let scalars: Vec<_> = (0..SAMPLES) + .map(|_| Scalar::rand(&mut rng).into_bigint()) + .collect(); + b.iter(|| <$Group>::msm_bigint(&v, &scalars)) }); } diff --git a/bench-templates/src/macros/field.rs b/bench-templates/src/macros/field.rs index 03391f51b..271e3b15e 100644 --- a/bench-templates/src/macros/field.rs +++ b/bench-templates/src/macros/field.rs @@ -340,22 +340,24 @@ macro_rules! prime_field { v2[i].num_bits() }) }); - let v_bits_le = v2 - .iter() - .map(|s| ark_ff::BitIteratorLE::new(s).collect::>()) - .collect::>(); + bits.bench_function("From Little-Endian bits", |b| { + let v_bits_le = v2 + .iter() + .map(|s| ark_ff::BitIteratorLE::new(s).collect::>()) + .collect::>(); let mut i = 0; b.iter(|| { i = (i + 1) % SAMPLES; BigInt::from_bits_be(&v_bits_le[i]); }) }); - let v_bits_be = v1 - .iter() - .map(|s| ark_ff::BitIteratorBE::new(s).collect::>()) - .collect::>(); + bits.bench_function("From Big-Endian bits", |b| { + let v_bits_be = v1 + .iter() + .map(|s| ark_ff::BitIteratorBE::new(s).collect::>()) + .collect::>(); let mut i = 0; b.iter(|| { i = (i + 1) % SAMPLES; diff --git a/bench-templates/src/macros/mod.rs b/bench-templates/src/macros/mod.rs index 919e51c2b..550d362d9 100644 --- a/bench-templates/src/macros/mod.rs +++ b/bench-templates/src/macros/mod.rs @@ -29,12 +29,12 @@ macro_rules! bench { paste! { criterion_main!( - [<$G1:lower>]::benches, - [<$G2:lower>]::benches, [<$Fr:lower>]::benches, [<$Fq:lower>]::benches, [<$FqExt:lower>]::benches, [<$FqTarget:lower>]::benches, + [<$G1:lower>]::benches, + [<$G2:lower>]::benches, pairing::benches ); } @@ -45,15 +45,15 @@ macro_rules! bench { ScalarField = $Fr:ident, BaseField = $Fq:ident, ) => { - $crate::ec_bench!($name, $G); $crate::f_bench!(prime, $name, $Fr); $crate::f_bench!(extension, $name, $Fq); + $crate::ec_bench!($name, $G); paste! { criterion_main!( - [<$G:lower>]::benches, [<$Fr:lower>]::benches, [<$Fq:lower>]::benches, + [<$G:lower>]::benches, ); } }; @@ -63,15 +63,27 @@ macro_rules! bench { ScalarField = $Fr:ident, PrimeBaseField = $Fq:ident, ) => { - $crate::ec_bench!($name, $G); $crate::f_bench!(prime, $name, $Fr); $crate::f_bench!(prime, $name, $Fq); + $crate::ec_bench!($name, $G); paste! { criterion_main!( - [<$G:lower>]::benches, [<$Fr:lower>]::benches, [<$Fq:lower>]::benches, + [<$G:lower>]::benches, + ); + } + }; + ( + Name = $name:expr, + PrimeField = $Fp:ident, + ) => { + $crate::f_bench!(prime, $name, $Fp); + + paste! { + criterion_main!( + [<$Fp:lower>]::benches, ); } }; diff --git a/ec/src/scalar_mul/mod.rs b/ec/src/scalar_mul/mod.rs index 1ae3a1e99..295597576 100644 --- a/ec/src/scalar_mul/mod.rs +++ b/ec/src/scalar_mul/mod.rs @@ -4,8 +4,8 @@ pub mod wnaf; pub mod fixed_base; pub mod variable_base; -use crate::PrimeGroup; use crate::short_weierstrass::{Affine, Projective, SWCurveConfig}; +use crate::PrimeGroup; use ark_ff::{AdditiveGroup, Zero}; use ark_std::{ ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign}, diff --git a/test-curves/Cargo.toml b/test-curves/Cargo.toml index 358fdc2ba..2f8931e59 100644 --- a/test-curves/Cargo.toml +++ b/test-curves/Cargo.toml @@ -70,3 +70,8 @@ harness = false name = "mnt6_753" path = "benches/mnt6_753.rs" harness = false + +[[bench]] +name = "starknet_fp" +path = "benches/starknet_fp.rs" +harness = false diff --git a/test-curves/benches/starknet_fp.rs b/test-curves/benches/starknet_fp.rs new file mode 100644 index 000000000..288fc6406 --- /dev/null +++ b/test-curves/benches/starknet_fp.rs @@ -0,0 +1,4 @@ +use ark_algebra_bench_templates::*; +use ark_test_curves::starknet_fp::Fq; + +bench!(Name = "StarkNetFp", PrimeField = Fq,); diff --git a/test-curves/src/lib.rs b/test-curves/src/lib.rs index 9337089fa..7eb0ceef3 100644 --- a/test-curves/src/lib.rs +++ b/test-curves/src/lib.rs @@ -33,3 +33,5 @@ pub mod bn384_small_two_adicity; pub mod secp256k1; pub mod fp128; + +pub mod starknet_fp; diff --git a/test-curves/src/starknet_fp.rs b/test-curves/src/starknet_fp.rs new file mode 100644 index 000000000..2b2dc6bb1 --- /dev/null +++ b/test-curves/src/starknet_fp.rs @@ -0,0 +1,15 @@ +//! Prime field `Fq` where `q` is the StarkNet prime. +use ark_ff::fields::{Fp256, MontBackend}; + +#[derive(ark_ff::MontConfig)] +#[modulus = "3618502788666131213697322783095070105623107215331596699973092056135872020481"] +#[generator = "3"] +pub struct FqConfig; +pub type Fq = Fp256>; + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + test_field!(fp; Fq; mont_prime_field); +} From 7b1262505bbc370aec7a30d56466ccd32ef86704 Mon Sep 17 00:00:00 2001 From: Pratyush Mishra Date: Sun, 14 Jan 2024 18:18:51 -0800 Subject: [PATCH 2/5] Comment tweak Co-authored-by: Marcin --- test-curves/src/starknet_fp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-curves/src/starknet_fp.rs b/test-curves/src/starknet_fp.rs index 2b2dc6bb1..397fc594e 100644 --- a/test-curves/src/starknet_fp.rs +++ b/test-curves/src/starknet_fp.rs @@ -1,4 +1,4 @@ -//! Prime field `Fq` where `q` is the StarkNet prime. +//! Prime field `Fq` where `q = 2^251 + 17 * 2^192 + 1` is the StarkNet prime. use ark_ff::fields::{Fp256, MontBackend}; #[derive(ark_ff::MontConfig)] From fffa69dda038ae57bf051c185427ea352014b83d Mon Sep 17 00:00:00 2001 From: Pratyush Mishra Date: Mon, 15 Jan 2024 22:43:50 -0500 Subject: [PATCH 3/5] Work --- bench-templates/Cargo.toml | 1 + bench-templates/src/lib.rs | 2 ++ bench-templates/src/macros/ec.rs | 11 ++++++----- test-templates/src/fields.rs | 7 +++++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/bench-templates/Cargo.toml b/bench-templates/Cargo.toml index eef16ed44..db1577cb8 100644 --- a/bench-templates/Cargo.toml +++ b/bench-templates/Cargo.toml @@ -25,6 +25,7 @@ ark-ec.workspace = true ark-ff.workspace = true ark-serialize.workspace = true paste.workspace = true +rayon.workspace = true [features] asm = [ "ark-ff/asm" ] diff --git a/bench-templates/src/lib.rs b/bench-templates/src/lib.rs index 323b242f3..2c3586e09 100644 --- a/bench-templates/src/lib.rs +++ b/bench-templates/src/lib.rs @@ -8,3 +8,5 @@ pub extern crate criterion; pub use criterion::*; pub use paste::paste; + +pub use rayon; diff --git a/bench-templates/src/macros/ec.rs b/bench-templates/src/macros/ec.rs index a29d3e1ab..e15c8bfe9 100644 --- a/bench-templates/src/macros/ec.rs +++ b/bench-templates/src/macros/ec.rs @@ -21,6 +21,7 @@ macro_rules! ec_bench { use ark_ff::AdditiveGroup; use ark_ec::{CurveGroup, PrimeGroup}; use ark_std::UniformRand; + use $crate::rayon::prelude::*; let name = format!("{}::{}", $curve_name, stringify!($Group)); type Scalar = <$Group as PrimeGroup>::ScalarField; @@ -28,14 +29,14 @@ macro_rules! ec_bench { let mut rng = ark_std::test_rng(); let mut arithmetic = c.benchmark_group(format!("Arithmetic for {name}")); - // Efficient sampling of inputs for benchmarking - let mut g_left = <$Group>::rand(&mut rng); - let mut g_right = <$Group>::rand(&mut rng); + // Faster sampling of inputs for benchmarking let group_elements_left = (0..SAMPLES) - .map(|_| g_left.double()) + .into_par_iter() + .map(|_| <$Group>::rand(&mut ark_std::rand::thread_rng())) .collect::>(); let mut group_elements_right = (0..SAMPLES) - .map(|_| g_right.double()) + .into_par_iter() + .map(|_| <$Group>::rand(&mut ark_std::rand::thread_rng())) .collect::>(); group_elements_right.reverse(); let group_elements_right_affine = <$Group>::normalize_batch(&group_elements_right); diff --git a/test-templates/src/fields.rs b/test-templates/src/fields.rs index d82c580e5..af33c8ad2 100644 --- a/test-templates/src/fields.rs +++ b/test-templates/src/fields.rs @@ -359,9 +359,12 @@ macro_rules! __test_field { #[test] fn test_fft() { use ark_ff::FftField; + use $crate::num_bigint::BigUint; + let two_adic_pow = (BigUint::one() << <$field>::TWO_ADICITY).to_u64_digits(); assert_eq!( - <$field>::TWO_ADIC_ROOT_OF_UNITY.pow([1 << <$field>::TWO_ADICITY]), - <$field>::one() + <$field>::TWO_ADIC_ROOT_OF_UNITY.pow(two_adic_pow), + <$field>::one(), + "2-adicity is incorrect or 2-adic root of unity is invalid" ); if let Some(small_subgroup_base) = <$field>::SMALL_SUBGROUP_BASE { From 81faacdef0ff5f7545d174bd9b9f641dbea663f4 Mon Sep 17 00:00:00 2001 From: Pratyush Mishra Date: Mon, 15 Jan 2024 22:44:17 -0500 Subject: [PATCH 4/5] WIP on improving FFTField to support large two-adicity --- ff/src/fields/fft_friendly.rs | 50 +++++++++++++++++++++++++---------- ff/src/fields/utils.rs | 13 ++++++--- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/ff/src/fields/fft_friendly.rs b/ff/src/fields/fft_friendly.rs index 74367e0f6..00029f2d5 100644 --- a/ff/src/fields/fft_friendly.rs +++ b/ff/src/fields/fft_friendly.rs @@ -1,3 +1,5 @@ +use num_bigint::BigUint; + /// The interface for fields that are able to be used in FFTs. pub trait FftField: crate::Field { /// The generator of the multiplicative group of the field @@ -31,21 +33,22 @@ pub trait FftField: crate::Field { /// of order n for the larger subgroup generated by /// `FftConfig::LARGE_SUBGROUP_ROOT_OF_UNITY` /// (for n = 2^i * FftConfig::SMALL_SUBGROUP_BASE^j for some i, j). - fn get_root_of_unity(n: u64) -> Option { + fn get_root_of_unity(n: impl Into) -> Option { let mut omega: Self; - if let Some(large_subgroup_root_of_unity) = Self::LARGE_SUBGROUP_ROOT_OF_UNITY { - let q = Self::SMALL_SUBGROUP_BASE.expect( - "LARGE_SUBGROUP_ROOT_OF_UNITY should only be set in conjunction with SMALL_SUBGROUP_BASE", - ) as u64; - let small_subgroup_base_adicity = Self::SMALL_SUBGROUP_BASE_ADICITY.expect( - "LARGE_SUBGROUP_ROOT_OF_UNITY should only be set in conjunction with SMALL_SUBGROUP_BASE_ADICITY", - ); - - let q_adicity = crate::utils::k_adicity(q, n); - let q_part = q.checked_pow(q_adicity)?; + let n = n.into(); + let two = BigUint::from(2u64); + let max_subgroup_size = Self::max_subgroup_size(); + if n > max_subgroup_size { + return None; + } else if let Some(large_subgroup_root) = Self::LARGE_SUBGROUP_ROOT_OF_UNITY { + let q = Self::SMALL_SUBGROUP_BASE?; + let small_subgroup_base_adicity = Self::SMALL_SUBGROUP_BASE_ADICITY?; + let q_adicity = crate::utils::k_adicity(q as u64, &n); + let q: BigUint = q.into(); + let q_part = q.pow(q_adicity); - let two_adicity = crate::utils::k_adicity(2, n); - let two_part = 2u64.checked_pow(two_adicity)?; + let two_adicity = crate::utils::k_adicity(2, &n); + let two_part = two.pow(two_adicity); if n != two_part * q_part || (two_adicity > Self::TWO_ADICITY) @@ -54,7 +57,7 @@ pub trait FftField: crate::Field { return None; } - omega = large_subgroup_root_of_unity; + omega = large_subgroup_root; for _ in q_adicity..small_subgroup_base_adicity { omega = omega.pow([q as u64]); } @@ -80,4 +83,23 @@ pub trait FftField: crate::Field { } Some(omega) } + + fn max_subgroup_size() -> BigUint { + let two = BigUint::from(2u64); + let two_part = two.pow(Self::TWO_ADICITY); + if Self::SMALL_SUBGROUP_BASE.is_none() { + two_part + } else { + let (q, q_adicity) = Self::SMALL_SUBGROUP_BASE + .and_then(|q| { + let q_adicity = Self::SMALL_SUBGROUP_BASE_ADICITY?; + Some((BigUint::from(q), q_adicity)) + }) + .expect( + "LARGE_SUBGROUP_ROOT_OF_UNITY should only be set in conjunction with SMALL_SUBGROUP_BASE", + ); + let q_part = q.pow(q_adicity); + q_part * two_part + } + } } diff --git a/ff/src/fields/utils.rs b/ff/src/fields/utils.rs index e670317b3..f0d928bd1 100644 --- a/ff/src/fields/utils.rs +++ b/ff/src/fields/utils.rs @@ -1,10 +1,15 @@ +use num_bigint::BigUint; +use num_traits::{One, Zero}; + /// Calculates the k-adicity of n, i.e., the number of trailing 0s in a base-k /// representation. -pub fn k_adicity(k: u64, mut n: u64) -> u32 { +pub fn k_adicity(k: u64, mut n: &BigUint) -> u32 { + let mut n = n.clone(); + let one = BigUint::one(); let mut r = 0; - while n > 1 { - if n % k == 0 { - r += 1; + while n > one { + if (n % k).is_zero() { + r += 1u32; n /= k; } else { return r; From 7ca337b64e43e8a301ebe4611e4e0a30674ce271 Mon Sep 17 00:00:00 2001 From: Pratyush Mishra Date: Mon, 15 Jan 2024 22:45:10 -0500 Subject: [PATCH 5/5] Tweak --- ff/src/fields/fft_friendly.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ff/src/fields/fft_friendly.rs b/ff/src/fields/fft_friendly.rs index 00029f2d5..9b5696da4 100644 --- a/ff/src/fields/fft_friendly.rs +++ b/ff/src/fields/fft_friendly.rs @@ -59,7 +59,7 @@ pub trait FftField: crate::Field { omega = large_subgroup_root; for _ in q_adicity..small_subgroup_base_adicity { - omega = omega.pow([q as u64]); + omega = omega.pow(q.to_u64_digits()); } for _ in two_adicity..Self::TWO_ADICITY {