diff --git a/crypto/stark/src/constraints/zerofier.rs b/crypto/stark/src/constraints/zerofier.rs index ba22098de..a91f739cd 100644 --- a/crypto/stark/src/constraints/zerofier.rs +++ b/crypto/stark/src/constraints/zerofier.rs @@ -115,13 +115,36 @@ pub fn zerofier_evaluations_on_extended_domain( .collect() } +/// `1/(z^N − 1)`, the half of the zerofier that does NOT depend on the +/// constraint. +/// +/// Hoisted out of [`evaluate_zerofier`] because the caller evaluates every +/// constraint of an AIR at the SAME `z` and `trace_length`: computing it inside +/// meant one extension-field `pow` and one extension-field `inv` **per +/// constraint** instead of one per AIR. Measured in the recursion guest, where +/// that loop is hot: `evaluate_zerofier` was 15.8% of the guest's cycles and +/// 88% of that was exactly this `inv` + `pow`. +pub fn zerofier_base_inv(z: &FieldElement, trace_length: usize) -> FieldElement +where + F: IsSubFieldOf, + E: IsField, +{ + (-FieldElement::::one() + z.pow(trace_length)) + .inv() + .unwrap() +} + /// Evaluation of the constraint's zerofier at some point `z`, which may be in /// a field extension. +/// +/// `base_inv` is [`zerofier_base_inv`] for this `z`/`trace_length` — the caller +/// computes it once and passes it for every constraint of the AIR. pub fn evaluate_zerofier( meta: &ConstraintMeta, z: &FieldElement, trace_primitive_root: &FieldElement, trace_length: usize, + base_inv: &FieldElement, ) -> FieldElement where F: IsSubFieldOf, @@ -134,9 +157,5 @@ where acc * -(root.clone() - z.clone()) }); - // 1/(z^N − 1), times the end-exemptions correction. - (-FieldElement::::one() + z.pow(trace_length)) - .inv() - .unwrap() - * &end_exemptions_eval + base_inv * &end_exemptions_eval } diff --git a/crypto/stark/src/lookup.rs b/crypto/stark/src/lookup.rs index ceda5417a..55675a92d 100644 --- a/crypto/stark/src/lookup.rs +++ b/crypto/stark/src/lookup.rs @@ -824,7 +824,12 @@ pub struct AirWithBuses< constraint_set: CS, /// The LogUp layout: the framework generates the LogUp (extension) /// constraints from this and appends them after the `constraint_set` ones. - logup: LogUpLayout, + /// Behind `Arc` for the same reason as `constraint_program`: cloning an + /// `AirWithBuses` copied the interaction list TWICE (here and in + /// `auxiliary_trace_build_data`), and the in-VM verifier clones one AIR per + /// table per epoch. Measured on the recursion guest, clone+drop of + /// `Vec` was 9.2% of its cycles while computing nothing. + logup: std::sync::Arc, /// Idx-ordered metadata for all transition constraints, DERIVED at /// construction: `constraint_set.meta()` (base prefix) followed by the /// LogUp emission's derived metadata (ext). @@ -838,7 +843,7 @@ pub struct AirWithBuses< /// program (16-25K nodes on the big tables) per epoch/shard instance. constraint_program: std::sync::OnceLock>>, - auxiliary_trace_build_data: AuxiliaryTraceBuildData, + auxiliary_trace_build_data: std::sync::Arc, boundary_constraint_builder: PhantomData<(B, PI)>, /// Commitment to precomputed columns (if this is a preprocessed table) preprocessed_commitment: Option, @@ -913,7 +918,10 @@ impl< // Base-field (table) constraints come from the constraint set; LogUp // (extension) constraints are appended by the framework from the layout. let num_interactions = auxiliary_trace_build_data.interactions.len(); - let logup = LogUpLayout::from_interactions(auxiliary_trace_build_data.interactions.clone()); + let logup = std::sync::Arc::new(LogUpLayout::from_interactions( + auxiliary_trace_build_data.interactions.clone(), + )); + let auxiliary_trace_build_data = std::sync::Arc::new(auxiliary_trace_build_data); let num_term_columns = logup.num_term_columns; // meta = constraint_set base-prefix meta + appended LogUp ext meta, diff --git a/crypto/stark/src/prover.rs b/crypto/stark/src/prover.rs index faf512a72..8e428a1c8 100644 --- a/crypto/stark/src/prover.rs +++ b/crypto/stark/src/prover.rs @@ -4186,12 +4186,19 @@ pub trait IsStarkProver< let mut denominators = vec![FieldElement::::zero(); air.num_transition_constraints()]; + // `1/(z^N - 1)` is the same for EVERY constraint of this AIR, so it is + // computed once here instead of once per constraint inside the loop. + let zerofier_base_inv = crate::constraints::zerofier::zerofier_base_inv::< + Field, + FieldExtension, + >(z, trace_length); air.constraints_meta().iter().for_each(|m| { denominators[m.constraint_idx] = crate::constraints::zerofier::evaluate_zerofier( m, z, &domain.trace_primitive_root, trace_length, + &zerofier_base_inv, ); }); let transition_sum = transition_evals diff --git a/crypto/stark/src/verifier.rs b/crypto/stark/src/verifier.rs index 44add9c21..e2ac71bee 100644 --- a/crypto/stark/src/verifier.rs +++ b/crypto/stark/src/verifier.rs @@ -397,12 +397,19 @@ pub trait IsStarkVerifier< let mut denominators = vec![FieldElement::::zero(); air.num_transition_constraints()]; + // `1/(z^N - 1)` is the same for EVERY constraint of this AIR, so it is + // computed once here instead of once per constraint inside the loop. + let zerofier_base_inv = crate::constraints::zerofier::zerofier_base_inv::< + Field, + FieldExtension, + >(&challenges.z, trace_length); air.constraints_meta().iter().for_each(|m| { denominators[m.constraint_idx] = crate::constraints::zerofier::evaluate_zerofier( m, &challenges.z, &domain.trace_primitive_root, trace_length, + &zerofier_base_inv, ); });