From afea995e0d52022172adffa9c4bfb0f33c628f40 Mon Sep 17 00:00:00 2001 From: Pratyush Mishra Date: Thu, 2 Oct 2025 12:33:33 -0400 Subject: [PATCH 1/2] Increase threshold for parallelization in multi-miller loops --- ec/src/models/bls12/mod.rs | 34 +++++----- ec/src/models/bn/mod.rs | 42 ++++++------ ec/src/models/bw6/mod.rs | 87 ++++++++++++------------- ec/src/models/mnt4/mod.rs | 3 +- ec/src/models/mnt6/mod.rs | 2 +- ec/src/models/mod.rs | 2 + ec/src/models/parallel_pairing_utils.rs | 33 ++++++++++ 7 files changed, 114 insertions(+), 89 deletions(-) create mode 100644 ec/src/models/parallel_pairing_utils.rs diff --git a/ec/src/models/bls12/mod.rs b/ec/src/models/bls12/mod.rs index 71879bbb0..f5ef6ec60 100644 --- a/ec/src/models/bls12/mod.rs +++ b/ec/src/models/bls12/mod.rs @@ -1,5 +1,8 @@ use crate::{ - models::{short_weierstrass::SWCurveConfig, CurveConfig}, + models::{ + parallel_pairing_utils::threshold_chunked_loop, short_weierstrass::SWCurveConfig, + CurveConfig, + }, pairing::{MillerLoopOutput, Pairing, PairingOutput}, AffineRepr, }; @@ -12,13 +15,10 @@ use ark_ff::{ }, BitIteratorBE, CyclotomicMultSubgroup, Field, PrimeField, }; -use ark_std::{cfg_chunks_mut, marker::PhantomData, vec::*}; +use ark_std::{marker::PhantomData, vec::*}; use educe::Educe; use num_traits::{One, Zero}; -#[cfg(feature = "parallel")] -use rayon::prelude::*; - /// A particular BLS12 group can have G2 being either a multiplicative or a /// divisive twist. pub enum TwistType { @@ -62,23 +62,21 @@ pub trait Bls12Config: 'static + Sized { }) .collect::>(); - let mut f = cfg_chunks_mut!(pairs, 4) - .map(|pairs| { - let mut f = as Pairing>::TargetField::one(); - for i in BitIteratorBE::without_leading_zeros(Self::X).skip(1) { - f.square_in_place(); + let mut f = threshold_chunked_loop(&mut pairs, |pairs| { + let mut f = as Pairing>::TargetField::one(); + for i in BitIteratorBE::without_leading_zeros(Self::X).skip(1) { + f.square_in_place(); + for (p, coeffs) in pairs.iter_mut() { + Bls12::::ell(&mut f, &coeffs.next().unwrap(), &p.0); + } + if i { for (p, coeffs) in pairs.iter_mut() { Bls12::::ell(&mut f, &coeffs.next().unwrap(), &p.0); } - if i { - for (p, coeffs) in pairs.iter_mut() { - Bls12::::ell(&mut f, &coeffs.next().unwrap(), &p.0); - } - } } - f - }) - .product::< as Pairing>::TargetField>(); + } + f + }); if Self::X_IS_NEGATIVE { f.cyclotomic_inverse_in_place(); diff --git a/ec/src/models/bn/mod.rs b/ec/src/models/bn/mod.rs index 97845c4df..6a3de4c7f 100644 --- a/ec/src/models/bn/mod.rs +++ b/ec/src/models/bn/mod.rs @@ -1,5 +1,8 @@ use crate::{ - models::{short_weierstrass::SWCurveConfig, CurveConfig}, + models::{ + parallel_pairing_utils::threshold_chunked_loop, short_weierstrass::SWCurveConfig, + CurveConfig, + }, pairing::{MillerLoopOutput, Pairing, PairingOutput}, }; use ark_ff::{ @@ -11,14 +14,11 @@ use ark_ff::{ }, CyclotomicMultSubgroup, }; -use ark_std::{cfg_chunks_mut, marker::PhantomData, vec::*}; +use ark_std::{marker::PhantomData, vec::*}; use educe::Educe; use itertools::Itertools; use num_traits::One; -#[cfg(feature = "parallel")] -use rayon::prelude::*; - pub enum TwistType { M, D, @@ -64,28 +64,26 @@ pub trait BnConfig: 'static + Sized { }) .collect::>(); - let mut f = cfg_chunks_mut!(pairs, 4) - .map(|pairs| { - let mut f = as Pairing>::TargetField::one(); - for i in (1..Self::ATE_LOOP_COUNT.len()).rev() { - if i != Self::ATE_LOOP_COUNT.len() - 1 { - f.square_in_place(); - } + let mut f = threshold_chunked_loop(&mut pairs, |pairs| { + let mut f = as Pairing>::TargetField::one(); + for i in (1..Self::ATE_LOOP_COUNT.len()).rev() { + if i != Self::ATE_LOOP_COUNT.len() - 1 { + f.square_in_place(); + } + for (p, coeffs) in pairs.iter_mut() { + Bn::::ell(&mut f, &coeffs.next().unwrap(), &p.0); + } + + let bit = Self::ATE_LOOP_COUNT[i - 1]; + if bit == 1 || bit == -1 { for (p, coeffs) in pairs.iter_mut() { Bn::::ell(&mut f, &coeffs.next().unwrap(), &p.0); } - - let bit = Self::ATE_LOOP_COUNT[i - 1]; - if bit == 1 || bit == -1 { - for (p, coeffs) in pairs.iter_mut() { - Bn::::ell(&mut f, &coeffs.next().unwrap(), &p.0); - } - } } - f - }) - .product::< as Pairing>::TargetField>(); + } + f + }); if Self::X_IS_NEGATIVE { f.cyclotomic_inverse_in_place(); diff --git a/ec/src/models/bw6/mod.rs b/ec/src/models/bw6/mod.rs index 810ec30b3..58de15002 100644 --- a/ec/src/models/bw6/mod.rs +++ b/ec/src/models/bw6/mod.rs @@ -1,5 +1,8 @@ use crate::{ - models::{short_weierstrass::SWCurveConfig, CurveConfig}, + models::{ + parallel_pairing_utils::threshold_chunked_loop, short_weierstrass::SWCurveConfig, + CurveConfig, + }, pairing::{MillerLoopOutput, Pairing, PairingOutput}, }; use ark_ff::{ @@ -10,16 +13,12 @@ use ark_ff::{ }, BitIteratorBE, CyclotomicMultSubgroup, }; -use ark_std::cfg_chunks_mut; use educe::Educe; use itertools::Itertools; use num_traits::One; use ark_std::{marker::PhantomData, vec::*}; -#[cfg(feature = "parallel")] -use rayon::prelude::*; - pub enum TwistType { M, D, @@ -102,23 +101,21 @@ pub trait BW6Config: 'static + Eq + Sized { .unzip::<_, _, Vec<_>, Vec<_>>(); // compute f_u which we can later re-use for the 2nd loop - let mut f_u = cfg_chunks_mut!(pairs_1, 4) - .map(|pairs| { - let mut f = as Pairing>::TargetField::one(); - for i in BitIteratorBE::without_leading_zeros(Self::ATE_LOOP_COUNT_1).skip(1) { - f.square_in_place(); + let mut f_u = threshold_chunked_loop(&mut pairs_1, |pairs| { + let mut f = as Pairing>::TargetField::one(); + for i in BitIteratorBE::without_leading_zeros(Self::X).skip(1) { + f.square_in_place(); + for (p, coeffs) in pairs.iter_mut() { + BW6::::ell(&mut f, &coeffs.next().unwrap(), &p.0); + } + if i { for (p, coeffs) in pairs.iter_mut() { BW6::::ell(&mut f, &coeffs.next().unwrap(), &p.0); } - if i { - for (p, coeffs) in pairs.iter_mut() { - BW6::::ell(&mut f, &coeffs.next().unwrap(), &p.0); - } - } } - f - }) - .product::< as Pairing>::TargetField>(); + } + f + }); let f_u_inv; @@ -130,40 +127,36 @@ pub trait BW6Config: 'static + Eq + Sized { } // f_1(P) = f_(u+1)(P) = f_u(P) * l([u]q, q)(P) - let mut f_1 = cfg_chunks_mut!(pairs_1, 4) - .map(|pairs| { - pairs.iter_mut().fold(f_u, |mut f, (p, coeffs)| { - BW6::::ell(&mut f, &coeffs.next().unwrap(), &p.0); - f - }) + let mut f_1 = threshold_chunked_loop(&mut pairs_1, |pairs| { + pairs.iter_mut().fold(f_u, |mut f, (p, coeffs)| { + BW6::::ell(&mut f, &coeffs.next().unwrap(), &p.0); + f }) - .product::< as Pairing>::TargetField>(); + }); - let mut f_2 = cfg_chunks_mut!(pairs_2, 4) - .map(|pairs| { - let mut f = f_u; - for i in (1..Self::ATE_LOOP_COUNT_2.len()).rev() { - f.square_in_place(); + let mut f_2 = threshold_chunked_loop(&mut pairs_2, |pairs| { + let mut f = f_u; + for i in (1..Self::ATE_LOOP_COUNT_2.len()).rev() { + f.square_in_place(); - for (p, ref mut coeffs) in pairs.iter_mut() { - BW6::::ell(&mut f, &coeffs.next().unwrap(), &p.0); - } + for (p, ref mut coeffs) in pairs.iter_mut() { + BW6::::ell(&mut f, &coeffs.next().unwrap(), &p.0); + } - let bit = Self::ATE_LOOP_COUNT_2[i - 1]; - if bit == 1 { - f *= &f_u; - } else if bit == -1 { - f *= &f_u_inv; - } else { - continue; - } - for &mut (p, ref mut coeffs) in pairs.iter_mut() { - BW6::::ell(&mut f, &coeffs.next().unwrap(), &p.0); - } + let bit = Self::ATE_LOOP_COUNT_2[i - 1]; + if bit == 1 { + f *= &f_u; + } else if bit == -1 { + f *= &f_u_inv; + } else { + continue; } - f - }) - .product::< as Pairing>::TargetField>(); + for &mut (p, ref mut coeffs) in pairs.iter_mut() { + BW6::::ell(&mut f, &coeffs.next().unwrap(), &p.0); + } + } + f + }); if Self::ATE_LOOP_COUNT_2_IS_NEGATIVE { f_2.cyclotomic_inverse_in_place(); diff --git a/ec/src/models/mnt4/mod.rs b/ec/src/models/mnt4/mod.rs index 72a3ef7e0..6a6319e5d 100644 --- a/ec/src/models/mnt4/mod.rs +++ b/ec/src/models/mnt4/mod.rs @@ -53,9 +53,10 @@ pub trait MNT4Config: 'static + Sized { .zip_eq(b) .map(|(a, b)| (a.into(), b.into())) .collect::>(); - let result = ark_std::cfg_into_iter!(pairs) + let result = ark_std::cfg_into_iter!(pairs, 1000) .map(|(a, b)| MNT4::ate_miller_loop(&a, &b)) .product(); + MillerLoopOutput(result) } diff --git a/ec/src/models/mnt6/mod.rs b/ec/src/models/mnt6/mod.rs index bcff96d74..2acfe7e35 100644 --- a/ec/src/models/mnt6/mod.rs +++ b/ec/src/models/mnt6/mod.rs @@ -54,7 +54,7 @@ pub trait MNT6Config: 'static + Sized { .zip_eq(b) .map(|(a, b)| (a.into(), b.into())) .collect::>(); - let result = ark_std::cfg_into_iter!(pairs) + let result = ark_std::cfg_into_iter!(pairs, 1000) .map(|(a, b)| MNT6::ate_miller_loop(&a, &b)) .product(); MillerLoopOutput(result) diff --git a/ec/src/models/mod.rs b/ec/src/models/mod.rs index 25d880e60..9528d4a7b 100644 --- a/ec/src/models/mod.rs +++ b/ec/src/models/mod.rs @@ -6,6 +6,8 @@ pub mod bw6; pub mod mnt4; pub mod mnt6; +mod parallel_pairing_utils; + pub mod short_weierstrass; pub mod twisted_edwards; diff --git a/ec/src/models/parallel_pairing_utils.rs b/ec/src/models/parallel_pairing_utils.rs new file mode 100644 index 000000000..d0e614dbe --- /dev/null +++ b/ec/src/models/parallel_pairing_utils.rs @@ -0,0 +1,33 @@ +use ark_std::cfg_chunks_mut; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +/// Executes the function `func` over `data` in parallel if the data length exceeds a threshold, +/// otherwise processes the entire data slice sequentially. +/// +/// This is an internal helper function for use in multi-pairing and multi-miller-loop computations. +/// +/// +/// # Parameters +/// - `data`: A mutable slice of elements to be processed. +/// - `func`: A function that takes mutable chunk of `data` and returns a value of type `R`. +/// +/// # Parallelism +/// Uses parallel processing via Rayon if the `parallel` feature is enabled and the data length is greater than 1000. +/// +/// # Type Parameters +/// - `T`: The element type of the data slice. Must be `Send`. +/// - `F`: The function type. Must be `Fn(&mut [T]) -> R + Send + Sync`. +/// - `R`: The result type. Must implement `std::iter::Product` and `Send`. In internal usage, is the target field type of the pairing. +pub(super) fn threshold_chunked_loop(data: &mut [T], func: F) -> R +where + F: Fn(&mut [T]) -> R + Send + Sync, + R: std::iter::Product + Send, + T: Send, +{ + if data.len() > 1000 { + cfg_chunks_mut!(data, 32).map(func).product::() + } else { + func(data) + } +} From b7b8a0bdce8fdcf0c49d221f9f220142b894c76f Mon Sep 17 00:00:00 2001 From: Pratyush Mishra Date: Thu, 2 Oct 2025 13:11:08 -0400 Subject: [PATCH 2/2] Fix bug --- ec/src/models/bw6/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ec/src/models/bw6/mod.rs b/ec/src/models/bw6/mod.rs index 58de15002..51f06e170 100644 --- a/ec/src/models/bw6/mod.rs +++ b/ec/src/models/bw6/mod.rs @@ -103,7 +103,7 @@ pub trait BW6Config: 'static + Eq + Sized { // compute f_u which we can later re-use for the 2nd loop let mut f_u = threshold_chunked_loop(&mut pairs_1, |pairs| { let mut f = as Pairing>::TargetField::one(); - for i in BitIteratorBE::without_leading_zeros(Self::X).skip(1) { + for i in BitIteratorBE::without_leading_zeros(Self::ATE_LOOP_COUNT_1).skip(1) { f.square_in_place(); for (p, coeffs) in pairs.iter_mut() { BW6::::ell(&mut f, &coeffs.next().unwrap(), &p.0);