-
Notifications
You must be signed in to change notification settings - Fork 6.1k
8355719: Reduce memory consumption of BigInteger.pow() #24690
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
Conversation
👋 Welcome back fabioromano1! A progress list of the required criteria for merging this PR into |
@fabioromano1 This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 229 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@rgiulietti) but any other Committer may sponsor as well. ➡️ To flag this PR as ready for integration with the above commit message, type |
@fabioromano1 The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
@rgiulietti In this PR I've implemented nth root for BigIntegers using Newton's method and optimized BigInteger.pow(int) method. I've also uploaded a proof of convergence for the recurrence used to compute nth root. |
|
} else { | ||
if ((long)bitLength() * exponent / Integer.SIZE > MAX_MAG_LENGTH) { | ||
if ((bitLength() - 1L) * exponent >= (long) MAX_MAG_LENGTH << 5) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if ((bitLength() - 1L) * exponent >= (long) MAX_MAG_LENGTH << 5) { | |
if ((bitLength() - 1L) * exponent >= 32L * MAX_MAG_LENGTH) { |
or
if ((bitLength() - 1L) * exponent >= (long) MAX_MAG_LENGTH << 5) { | |
if ((bitLength() - 1L) * exponent >= (long) Integer.SIZE * MAX_MAG_LENGTH) { |
Both variant are easier to read, more honest, and exactly as efficient as with the shift. The right-hand sides are compile-time constants, so they have no impact on runtime performance.
More generally, the runtime compilers are perfectly capable to optimize multiplications by constant powers of 2 and replace them with shifts, even if the other operand is not a constant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rgiulietti What about (bitLength() - 1L) * exponent >= Integer.MAX_VALUE
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right, but you probably want
if ((bitLength() - 1L) * exponent >= (long) MAX_MAG_LENGTH << 5) { | |
if ((bitLength() - 1L) * exponent > Integer.MAX_VALUE) { |
I mean >
rather than >=
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right, but you probably want
No, the sufficient condition to get the overflow is (bitLength() - 1L) * exponent + 1L > Integer.MAX_VALUE
, which is equivalent to that one I wrote above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At some point you proposed
(bitLength() - 1L) * exponent >= (long) MAX_MAG_LENGTH << 5
Given the value of MAX_MAG_LENGTH
, which is 2^26, this is equivalent to
(bitLength() - 1L) * exponent >= 1L << 31
that is, to
(bitLength() - 1L) * exponent > Integer.MAX_VALUE
What am I missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition A := (bitLength() - 1L) * exponent + 1L > Integer.MAX_VALUE
is more accurate, as it compares the bit length of the result, in fact B := (bitLength() - 1L) * exponent >= (long) MAX_MAG_LENGTH << 5
implies A
, but A
does not imply B
. The BigInteger
s can have a mag length up to MAX_MAG_LENGTH
, but MAX_MAG_LENGTH * Integer.SIZE > Integer.MAX_VALUE
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK.
But then your original expression
(((bitLength() - 1L) * exponent) >>> 5) + 1L > MAX_MAG_LENGTH
was a bit too restrictive as well, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK.
But then your original expression
(((bitLength() - 1L) * exponent) >>> 5) + 1L > MAX_MAG_LENGTH
was a bit too restrictive as well, right?
On the contrary, it was too loose, as it admitted a bit length equal to MAX_MAG_LENGTH * Integer.SIZE == 2^31 > Integer.MAX_VALUE
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, if B strictly implies A, then B is more restrictive (stronger).
Since B is equivalent to your original formulation, to me it means that it was more restrictive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, if B strictly implies A, then B is more restrictive (stronger). Since B is equivalent to your original formulation, to me it means that it was more restrictive.
Exactly.
The large number algorithm loop in Did you make sure that the existing tests also cover the |
JMH benchmarks look good for the random exponents as well. |
@rgiulietti There are already the tests for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Last nits.
I'll approve the PR in the next couple of days for last minute small changes from you.
Please update the 2nd copyright year in Otherwise looks good. Thanks for yet another nice contribution! |
@rgiulietti Could be useful this optimization for |
There are endless optimizations and fast paths that one could conceive, but at some point we must decide to stop and turn our attention to other aspects of the platform. I think we have reached the turning point for this great PR. Besides, the performance gains of yet another fast-path might be negligible in general usage, at the cost of another 50 lines or so of code that would probably only benefit very narrow use cases. |
@rgiulietti For me, now the code is ready. |
Good. Let's give another 24 hours for people around the globe to chime in for last minute comments. |
/integrate |
@fabioromano1 |
/sponsor |
Going to push as commit 1c5eb37.
Your commit was automatically rebased without conflicts. |
@rgiulietti @fabioromano1 Pushed as commit 1c5eb37. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
This PR optimizes
BigInteger.pow(int)
method. The primary enhancement inpow()
is not concerned most on execution time, but rather in memory optimization, because the PR implementation does the "shift of the exponent" squaring the result rather than the base, so the base is not squared like in the current implementation, and this permits to save about half of the memory.Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/24690/head:pull/24690
$ git checkout pull/24690
Update a local copy of the PR:
$ git checkout pull/24690
$ git pull https://git.openjdk.org/jdk.git pull/24690/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 24690
View PR using the GUI difftool:
$ git pr show -t 24690
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/24690.diff
Using Webrev
Link to Webrev Comment