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

Optimize muldiv #4494

Merged
merged 4 commits into from
Aug 1, 2023
Merged

Optimize muldiv #4494

merged 4 commits into from
Aug 1, 2023

Conversation

0xVolosnikov
Copy link
Contributor

@0xVolosnikov 0xVolosnikov commented Jul 31, 2023

This pull request contains two small changes to the mulDiv function to slightly optimize gas.

Reduce stack operations in mulDiv

The product of x and y to obtain the least significant bits of the result is moved above, to the place where the variable prod0 is declared.

https://github.com/vladyan18/openzeppelin-contracts/blob/e54956d8453f04f59491511df8d353dbaab98202/contracts/utils/math/Math.sol#L127

uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
  let mm := mulmod(x, y, not(0))
  prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}

Rationale: with compiler optimization enabled, the old implementation led to unnecessary initialization of the variable by zero. As a result, this initialization also added unnecessary stack operations below in the code. Since this code is in an unchecked block, calculating the product in assembly does not provide gas savings by itself.

Gas tests, small inputs. Optimizer 200 runs, 0.8.20
Gas before: 198
Gas after: 190 (-4%)

Simplify twos calculation in mulDiv

An easier method was used to factor powers of two out of denominator due to which the number of necessary operations is reduced by one.

https://github.com/vladyan18/openzeppelin-contracts/blob/e54956d8453f04f59491511df8d353dbaab98202/contracts/utils/math/Math.sol#L165

uint256 twos = denominator & (0 - denominator);

Rationale: the previous implementation uses a unary negation operation and ends up needing more operations to get the same result. The proposed line gives the same result inside an unchecked block with fewer operations.

Gas tests, big inputs. Optimizer 200 runs, 0.8.20
Gas before: 497
Gas after: 494 (-0.6%)

PR Checklist

  • Tests
  • Documentation
  • Changeset entry (run npx changeset add)

Probably new tests and documentation are not required for this pull request.

@changeset-bot
Copy link

changeset-bot bot commented Jul 31, 2023

🦋 Changeset detected

Latest commit: 9a86e03

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
openzeppelin-solidity Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@@ -163,8 +162,7 @@ library Math {
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.

// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
uint256 twos = denominator & (0 - denominator);
Copy link
Collaborator

Choose a reason for hiding this comment

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

That is indeed a good solution !

Copy link
Collaborator

Choose a reason for hiding this comment

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

For the record, before solidity v8, the code used to be uint256 twos = -denominator & denominator; but solidity 0.8 forbid the -x syntax for unsigned integers. Doing (0-x) indeed fixes it.

Amxx
Amxx previously approved these changes Jul 31, 2023
Copy link
Collaborator

@Amxx Amxx left a comment

Choose a reason for hiding this comment

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

These are great findings !

@Amxx Amxx requested review from frangio and ernestognw July 31, 2023 19:43
Copy link
Member

@ernestognw ernestognw left a comment

Choose a reason for hiding this comment

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

Thanks!

@Amxx Amxx merged commit 48cc8a9 into OpenZeppelin:master Aug 1, 2023
@gitpoap-bot
Copy link

gitpoap-bot bot commented Aug 1, 2023

Congrats, your important contribution to this open-source project has earned you a GitPOAP!

GitPOAP: 2023 OpenZeppelin Contracts Contributor:

GitPOAP: 2023 OpenZeppelin Contracts Contributor GitPOAP Badge

Head to gitpoap.io & connect your GitHub account to mint!

Learn more about GitPOAPs here.

@0xVolosnikov 0xVolosnikov deleted the optimize-muldiv branch August 1, 2023 13:38
This was referenced Sep 11, 2024
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