Minor improve on division and implement Montgomery reducer - #77
Merged
Conversation
Mirror the multiplication threshold env-var mechanism for division: when the `tuning` feature is active, `DASHU_THRESHOLD_SIMPLE_DIV` overrides the compile-time schoolbook / divide-and-conquer crossover. Values below 3 are unsupported (the divide-and-conquer split needs at least 2 low words). Co-Authored-By: Claude <noreply@anthropic.com>
Implement an optimized Montgomery modular arithmetic suite under integer/src/monty/, mirroring the structure of integer/src/modular/. Two new public types: MontgomeryRepr (precomputed Montgomery constants for an odd modulus, analogous to ConstDivisor) and Montgomery<'a> (a value in Montgomery form, analogous to Reduced), with multiplication, squaring, addition, subtraction, negation, doubling, exponentiation, and inversion. Single/double-word moduli delegate to num-modular's Montgomery<Word>/<DoubleWord>. The multi-word (Large) path uses word-aligned REDC: the operand product reuses the crate's fast multiply (Karatsuba/Toom-3/NTT), and the reduction is word-by-word with a per-word n0 = -m^-1 mod 2^WORD_BITS (computed by table-free Hensel lifting). Values are kept in the canonical range [0, m). Cross-validated against the Barrett-style modular::Reduced across Single/Double/Large moduli (integer/tests/monty.rs). Co-Authored-By: Claude <noreply@anthropic.com>
Switch the multi-word Montgomery reduction from single-word (one limb/iteration) to double-word (two limbs/iteration via the existing addmul_2 kernel, n0_dword = -m^-1 mod 2^(2*WORD_BITS)), which halves modulus memory traffic. The exit path (residue/inv, sparse input) keeps the single-word REDC, which measures faster there. Microbenchmarks show mul/sqr/pow 10-19% faster than single-word, and inv now faster than either all-single or all-double. Also add integer/benches/modular.rs comparing the Barrett (ConstDivisor) and Montgomery backends across modular mul/sqr/add/sub/pow/inv and modulus sizes (256..16384 bits). Co-Authored-By: Claude <noreply@anthropic.com>
Drop the single-word n0 field (derive it on demand as the low word of n0_dword via n0_word()) and the r_mod_m field (compute the Montgomery form of 1 on demand as REDC(R^2 mod m) in mul::one_large). This leaves the ring storing only modulus, n0_dword, and r2_mod_m — the minimal set needed for REDC and entering Montgomery form. Co-Authored-By: Claude <noreply@anthropic.com>
… use) Add a 'When to use Montgomery vs Barrett' section to the monty module docs and a Performance note on Montgomery::inv, explaining that Montgomery wins for mul/sqr/pow (~256-4096 bits) but Barrett (modular::Reduced) is substantially faster for inverses, since a Montgomery inverse must exit Montgomery form, run ext-GCD, and re-enter. Also corrects the module doc (word-by-word REDC with the addmul_2 kernel, not 'Almost-Montgomery' or fast-multiply correction) and updates the changelog entry to match. Co-Authored-By: Claude <noreply@anthropic.com>
Only 'Efficient Software Implementations of Modular Exponentiation' (Gueron) is relevant to this module; the 'Incomplete Reduction' (Yanik/Savaş/Koç) reference is removed. The Gueron paper now lives as a normal // XXX code comment in pow.rs pointing to AMM as a possible future optimization, rather than as module-level documentation. Co-Authored-By: Claude <noreply@anthropic.com>
…pointer-identity in mul - sqr() and pow_nontrivial: build the squared result directly from scratch memory via sqr_normalized_large instead of cloning raw then overwriting, saving one Box<[Word]> allocation and an s-word copy per squaring. - mul_in_place_large: replace O(n) Box<[Word]> value comparison with O(1) pointer-identity check (lhs.0.as_ptr() == rhs.0.as_ptr()) to detect self-multiplication (a *= a). Distinct-but-equal values are handled correctly by the general mul path. - Extract finish_monty_product helper (REDC + canonicalize) shared by mul_normalized_large and sqr_normalized_large. fix(div): clamp DASHU_THRESHOLD_SIMPLE_DIV to minimum of 3 - The divide-and-conquer algorithm requires n_lo >= 2, which implies a threshold >= 3. The env-var override now enforces this floor instead of relying solely on the compile-time const_assert! on the default. - Added explicit ::<usize> type annotation on parse() to disambiguate the v.max(3) call. Co-Authored-By: Claude <noreply@anthropic.com>
…perands The &Montgomery * &Montgomery impl previously cloned self then used mul_assign, which completely overwrites the cloned buffer via copy_from_slice from scratch memory. For Large (multi-word) operands this wasted one Box<[Word]> allocation and an s-word copy. Now the Large case builds the result directly from scratch via mul_normalized_large (or sqr_normalized_large when lhs and rhs share the same allocation), saving one allocation and copy per multiply. Single/Double (Copy types) keep the existing clone path — it is zero-cost for them. Add, sub, and neg on references are intentionally unchanged: their in-place operations read-then-write lhs.0, so the clone correctly seeds the output buffer with the operand value. Co-Authored-By: Claude <noreply@anthropic.com>
- Add forward_modular_binop_to_assign!, impl_modular_commutative_op_for_ref!, impl_modular_binop_ref_ref_by_clone!, forward_modular_binop_to_ref_ref! macros in helper_macros.rs, parameterized by target type. Apply them to both Montgomery and Reduced Add/Sub/Mul/Div, eliminating the by-value/by-ref/assign boilerplate per operator. - Move add_mul_word_same_len_in_place, add_mul_word_in_place, sub_mul_word_same_len_in_place from mul/mod.rs to mul/simple.rs (alongside the other schoolbook kernels), and widen add_mul_dword_same_len_in_place from pub(crate) to pub. Old mul::* paths still work via re-exports. - Extract simple::MIN_LEN = 3 in div/simple.rs (mirroring mul::karatsuba::MIN_LEN); the threshold tuning override now clamps against this named constant. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CokieMiner
pushed a commit
to CokieMiner/dashu
that referenced
this pull request
Jun 25, 2026
* Add DASHU_THRESHOLD_SIMPLE_DIV runtime override for division threshold Mirror the multiplication threshold env-var mechanism for division: when the `tuning` feature is active, `DASHU_THRESHOLD_SIMPLE_DIV` overrides the compile-time schoolbook / divide-and-conquer crossover. Values below 3 are unsupported (the divide-and-conquer split needs at least 2 low words). Co-Authored-By: Claude <noreply@anthropic.com> * Add Montgomery modular arithmetic (monty module) Implement an optimized Montgomery modular arithmetic suite under integer/src/monty/, mirroring the structure of integer/src/modular/. Two new public types: MontgomeryRepr (precomputed Montgomery constants for an odd modulus, analogous to ConstDivisor) and Montgomery<'a> (a value in Montgomery form, analogous to Reduced), with multiplication, squaring, addition, subtraction, negation, doubling, exponentiation, and inversion. Single/double-word moduli delegate to num-modular's Montgomery<Word>/<DoubleWord>. The multi-word (Large) path uses word-aligned REDC: the operand product reuses the crate's fast multiply (Karatsuba/Toom-3/NTT), and the reduction is word-by-word with a per-word n0 = -m^-1 mod 2^WORD_BITS (computed by table-free Hensel lifting). Values are kept in the canonical range [0, m). Cross-validated against the Barrett-style modular::Reduced across Single/Double/Large moduli (integer/tests/monty.rs). Co-Authored-By: Claude <noreply@anthropic.com> * Use double-word Montgomery REDC and add modular benchmark Switch the multi-word Montgomery reduction from single-word (one limb/iteration) to double-word (two limbs/iteration via the existing addmul_2 kernel, n0_dword = -m^-1 mod 2^(2*WORD_BITS)), which halves modulus memory traffic. The exit path (residue/inv, sparse input) keeps the single-word REDC, which measures faster there. Microbenchmarks show mul/sqr/pow 10-19% faster than single-word, and inv now faster than either all-single or all-double. Also add integer/benches/modular.rs comparing the Barrett (ConstDivisor) and Montgomery backends across modular mul/sqr/add/sub/pow/inv and modulus sizes (256..16384 bits). Co-Authored-By: Claude <noreply@anthropic.com> * Eliminate redundant n0 and r_mod_m fields from MontgomeryLargeRepr Drop the single-word n0 field (derive it on demand as the low word of n0_dword via n0_word()) and the r_mod_m field (compute the Montgomery form of 1 on demand as REDC(R^2 mod m) in mul::one_large). This leaves the ring storing only modulus, n0_dword, and r2_mod_m — the minimal set needed for REDC and entering Montgomery form. Co-Authored-By: Claude <noreply@anthropic.com> * Document Montgomery vs Barrett tradeoff (prefer Barrett for inv-heavy use) Add a 'When to use Montgomery vs Barrett' section to the monty module docs and a Performance note on Montgomery::inv, explaining that Montgomery wins for mul/sqr/pow (~256-4096 bits) but Barrett (modular::Reduced) is substantially faster for inverses, since a Montgomery inverse must exit Montgomery form, run ext-GCD, and re-enter. Also corrects the module doc (word-by-word REDC with the addmul_2 kernel, not 'Almost-Montgomery' or fast-multiply correction) and updates the changelog entry to match. Co-Authored-By: Claude <noreply@anthropic.com> * Drop the j56 reference; move the Gueron paper to a code comment Only 'Efficient Software Implementations of Modular Exponentiation' (Gueron) is relevant to this module; the 'Incomplete Reduction' (Yanik/Savaş/Koç) reference is removed. The Gueron paper now lives as a normal // XXX code comment in pow.rs pointing to AMM as a possible future optimization, rather than as module-level documentation. Co-Authored-By: Claude <noreply@anthropic.com> * perf(monty): eliminate redundant clone+overwrite in sqr and pow, use pointer-identity in mul - sqr() and pow_nontrivial: build the squared result directly from scratch memory via sqr_normalized_large instead of cloning raw then overwriting, saving one Box<[Word]> allocation and an s-word copy per squaring. - mul_in_place_large: replace O(n) Box<[Word]> value comparison with O(1) pointer-identity check (lhs.0.as_ptr() == rhs.0.as_ptr()) to detect self-multiplication (a *= a). Distinct-but-equal values are handled correctly by the general mul path. - Extract finish_monty_product helper (REDC + canonicalize) shared by mul_normalized_large and sqr_normalized_large. fix(div): clamp DASHU_THRESHOLD_SIMPLE_DIV to minimum of 3 - The divide-and-conquer algorithm requires n_lo >= 2, which implies a threshold >= 3. The env-var override now enforces this floor instead of relying solely on the compile-time const_assert! on the default. - Added explicit ::<usize> type annotation on parse() to disambiguate the v.max(3) call. Co-Authored-By: Claude <noreply@anthropic.com> * perf(monty): eliminate clone in &Montgomery * &Montgomery for Large operands The &Montgomery * &Montgomery impl previously cloned self then used mul_assign, which completely overwrites the cloned buffer via copy_from_slice from scratch memory. For Large (multi-word) operands this wasted one Box<[Word]> allocation and an s-word copy. Now the Large case builds the result directly from scratch via mul_normalized_large (or sqr_normalized_large when lhs and rhs share the same allocation), saving one allocation and copy per multiply. Single/Double (Copy types) keep the existing clone path — it is zero-cost for them. Add, sub, and neg on references are intentionally unchanged: their in-place operations read-then-write lhs.0, so the clone correctly seeds the output buffer with the operand value. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(int): dedup modular binop impls and reorganize mul/div helpers - Add forward_modular_binop_to_assign!, impl_modular_commutative_op_for_ref!, impl_modular_binop_ref_ref_by_clone!, forward_modular_binop_to_ref_ref! macros in helper_macros.rs, parameterized by target type. Apply them to both Montgomery and Reduced Add/Sub/Mul/Div, eliminating the by-value/by-ref/assign boilerplate per operator. - Move add_mul_word_same_len_in_place, add_mul_word_in_place, sub_mul_word_same_len_in_place from mul/mod.rs to mul/simple.rs (alongside the other schoolbook kernels), and widen add_mul_dword_same_len_in_place from pub(crate) to pub. Old mul::* paths still work via re-exports. - Extract simple::MIN_LEN = 3 in div/simple.rs (mirroring mul::karatsuba::MIN_LEN); the threshold tuning override now clamps against this named constant. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Jacob Zhong <jacob@rimbot.com> Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.