Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

factor: Add 32b variant for modular arithmetic #1554

Merged
merged 29 commits into from
Jul 24, 2020
Merged

factor: Add 32b variant for modular arithmetic #1554

merged 29 commits into from
Jul 24, 2020

Conversation

nbraud
Copy link
Contributor

@nbraud nbraud commented Jun 22, 2020

Twice as fast as master

  • Implement and test 32b variant.
  • Refactor to avoid code duplication between the 32 and 64b versions.
  • Use specialised basis for the Miller-Rabin primality test.

@nbraud
Copy link
Contributor Author

nbraud commented Jun 22, 2020

I'm not sure what's the way forward there: ideally, I can make Montgomery generic over a numeric type, which means needing something like num-traits which IIRC isn't the most pleasant thing to work with.

@Arcterus
Copy link
Collaborator

@nbraud I consolidated Montgomery and Montgomery32 into one. The code needs to be cleaned up a bit, but the commit is ef50150. If you’d prefer, I can just push it to your fork.

@nbraud
Copy link
Contributor Author

nbraud commented Jun 24, 2020

I successfully generalised the function for computing modular inverses (which might also be useful if we want to make the trial division table smaller, etc.), but I'm running into a couple of issues trying to refactor Montgomery to make it similarly generic:

  • I don't only need an integer type, but also a type of intermediate values with double the width (i.e. u32 -> u64, u64 -> u128, etc.) ; that can be solved with an associated type, but the conversions get pretty messy.
  • Not being able to use integer literals (i.e. write 2 * x + 1 in source or somesuch) really hurts readability.
  • It would make sense to replace Arithmetic::{from,to}_u64 with a {from,to}_int that takes the datatype's native integer type (i.e. u32 for Montgomery32, u64 for Montgomery64, etc.), so we would need to make the Arithmetic trait itself generic.
  • There are some optimisations which only apply to certain sizes; for instance, in Montgomery64 we can safely assume that 2⁶⁴ > n > 2³² (i.e. n is a u64, but couldn't be a u32 otherwise we would use Montgomery32) and that we only want to reduce 64b integers x mod n, so n² > x and we can use Barrett reduction ( I expect that to be a pretty large win, as the 128b reduction mod n in Montgomery::from_u64 consumes ~20% of the runtime in master )

@nbraud
Copy link
Contributor Author

nbraud commented Jun 24, 2020

I thought I had a trait-based solution for this, but it doesn't work out: where clauses on associated types are unstable and do not support “constraints cycles” (like type DoubleWidth: From<Self> where Self: From<Self::DoubleWidth>)

I guess the way forward is to accept a little bit of code duplication, as it seems like a macro-based solution would not be too good here (even worse maintainability)

@nbraud nbraud marked this pull request as ready for review June 24, 2020 17:35
@nbraud
Copy link
Contributor Author

nbraud commented Jun 24, 2020

@nbraud I consolidated Montgomery and Montgomery32 into one. The code needs to be cleaned up a bit, but the commit is ef50150. If you’d prefer, I can just push it to your fork.

Oops, sorry @Arcterus, the Github UI was hiding this message until I refreshed. I will do have a look, thanks :)

@Arcterus
Copy link
Collaborator

Oh? cp -r failed, but only on one build environment? I guess this is something to investigate later.

@Arcterus
Copy link
Collaborator

I think we need to add RUST_BACKTRACE=1 to the CI and dump stderr on failure, as we have no idea why things fail with sporadic failures like this.

I think we can apply size-specific optimizations by playing around with the traits a bit more.

Copy link
Collaborator

@Arcterus Arcterus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t really have any other changes. I guess just double-check that my casts/conversions are correct if you haven’t already.

src/uu/factor/src/numeric.rs Outdated Show resolved Hide resolved
src/uu/factor/src/numeric.rs Show resolved Hide resolved
@nbraud
Copy link
Contributor Author

nbraud commented Jun 24, 2020

@Arcterus Thanks for the feedback, I'll look later this week; maybe tomorrow if I feel up for it

@nbraud nbraud mentioned this pull request Jun 25, 2020
2 tasks
@nbraud
Copy link
Contributor Author

nbraud commented Jun 25, 2020

I think we can apply size-specific optimizations by playing around with the traits a bit more.

Yes, I think I can do the optimization I had in mind in a pretty straightforward way

@nbraud
Copy link
Contributor Author

nbraud commented Jul 1, 2020

I can't seem to load the log for the failing CI instance :(
Nevermind, looks like it's green now; I guess someone re-ran it?

Copy link
Collaborator

@Arcterus Arcterus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments are rather nit-picky. After you address these, I think the code is good to go.

src/uu/factor/src/numeric.rs Show resolved Hide resolved
fn reduce(&self, x: T::Double) -> T {
let t_bits = T::zero().count_zeros() as usize;

debug_assert!(x < (self.n.as_double()) << t_bits);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like as_double() will confuse people coming from more C-like languages. It sounds like you are converting the value into a f64.

The name could be changed to as_dint() or something like that (although I don’t really like that name either). Another alternative is to find a different name for DoubleInt, although I can’t think of a good name at the moment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh; I had the same thought, but I really couldn't find a better name (except maybe spelling out “double-width” in full, but that doesn't seem more helpful).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Arcterus Were you able to think of a more-helpful name? I wasn't :(

Copy link
Member

@rivy rivy Jul 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my review of the code, it looks like DoubleInt is being used as a dual-precision integer, generally to avoid overflow issues. Correct?

If so, how about DualPrecisionInt (or maybe VariablePrecisionInt)?
That might lead to as_high_precision() and from_high_precision() trait functions and annotations like T::HighPrecision::one().

More verbose, but possibly more informative to readers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Arcterus @rivy I just pushed 96224f6, which switches to hopefully-clearer names :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS: Also documented the type in 33a1c44

src/uu/factor/Cargo.toml Outdated Show resolved Hide resolved
@nbraud
Copy link
Contributor Author

nbraud commented Jul 2, 2020

The build failure is due to one task, Style (macos-latest, macos), failing to install the Rust toolchain.
Why are we even running what's presumably cargo fmt and clippy on 3 different OSes?

@nbraud
Copy link
Contributor Author

nbraud commented Jul 4, 2020

@Arcterus Sorry, this got a bit out of hand, but I ended up doing some serious improvements to the testsuite to be confident that there were no bugs left, ended up finding a couple more issues in miller_rabin (which are now fixed)

@nbraud
Copy link
Contributor Author

nbraud commented Jul 4, 2020

Also, this is currently twice as fast as master :)

@nbraud nbraud mentioned this pull request Jul 4, 2020
3 tasks
@sylvestre
Copy link
Sponsor Contributor

Bravo for the coverage
I am afraid I don't know enough about this kind of math to do a proper review

src/uu/factor/Cargo.toml Outdated Show resolved Hide resolved
@nbraud
Copy link
Contributor Author

nbraud commented Jul 9, 2020

Bravo for the coverage
I am afraid I don't know enough about this kind of math to do a proper review

Thanks regardless 💜

@sylvestre
Copy link
Sponsor Contributor

@nbraud could you please fix the conflicts? sorry

thanks

@nbraud
Copy link
Contributor Author

nbraud commented Jul 15, 2020

@nbraud could you please fix the conflicts? sorry

No need to apologise, I already had a local commit to resolve the merge conflict, I just needed to know which PR it would go to :)

@rivy rivy self-requested a review July 20, 2020 12:16
@rivy
Copy link
Member

rivy commented Jul 20, 2020

Tests are passing.
@nbraud , if you're happy with the PR status, I'll take a quick overview look later today and merge.

Copy link
Member

@rivy rivy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that a bit of name refactoring for DoubleInt, ..., etc. would help improve the code clarity.

fn reduce(&self, x: T::Double) -> T {
let t_bits = T::zero().count_zeros() as usize;

debug_assert!(x < (self.n.as_double()) << t_bits);
Copy link
Member

@rivy rivy Jul 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my review of the code, it looks like DoubleInt is being used as a dual-precision integer, generally to avoid overflow issues. Correct?

If so, how about DualPrecisionInt (or maybe VariablePrecisionInt)?
That might lead to as_high_precision() and from_high_precision() trait functions and annotations like T::HighPrecision::one().

More verbose, but possibly more informative to readers.

src/uu/factor/src/numeric.rs Outdated Show resolved Hide resolved
- `DoubleInt::Double` renamed to `DoubleWidth`
- `{as,from}_double()` renamed to `{as,from}_double_width()`.

This should hopefully clarify that this is not a “double precision”
floating-point type, but an integer type with a larger range (used
for storing intermediate results, typ. from a multiplication)
It was unused, the debug assertions only need `to_u128`.
@nbraud nbraud requested review from rivy and Arcterus July 21, 2020 17:51
@rivy
Copy link
Member

rivy commented Jul 22, 2020

@nbraud , I'm happy with this ...
@Arcterus , any remaining concerns on your end?

If no, I'll merge on Friday and then start getting the the next couple of @nbraud's PRs.

@nbraud
Copy link
Contributor Author

nbraud commented Jul 23, 2020

@rivy Thanks!

@rivy rivy merged commit c6e8a8c into uutils:master Jul 24, 2020
@rivy
Copy link
Member

rivy commented Jul 24, 2020

Merged.
If you're happy with the other two PRs and will rebase them, I'll merge them later today.

rivy added a commit that referenced this pull request Jul 24, 2020
factor: Refactor and improve performance (plus a few bug fixes)
@nbraud nbraud deleted the factor/faster/montgomery32 branch July 24, 2020 21:02
@nbraud nbraud mentioned this pull request Jul 24, 2020
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants