Skip to content

Gas opt: skip remaining calculation when not necessary #448

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

Merged

Conversation

naps62
Copy link
Contributor

@naps62 naps62 commented Jun 8, 2022

Motivation

Critical code paths for order fulfillment are preemptively computing duration, elapsed and remaining timestamps for all orders.
In particular, remaining can always be derived from the other two, and it's only actually necessary when startAmount != endAmount. For static-price orders, it ends up never actually being used (both _locateCurrentAmount and _applyFraction branch out and never use it).

This opens the door to a couple of optimizations:

  • we can remove this argument from internal calls, and derive it from duration and elapsed when necessary
  • we end up not actually computing it at all when startAmount == endAmount, which should be a significant number of orders

yarn coverage of the relevant section (before and after shown below) shows gains of 130 to 160 gas for all fulfill* and match* calls.

TODO:

  • Update reference implementation as well

main:

······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  fulfillAdvancedOrder            ·     100495  ·     209321  ·      160454  ·          126  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  fulfillAvailableAdvancedOrders  ·     137853  ·     227269  ·      193361  ·           19  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  fulfillAvailableOrders          ·     171746  ·     226914  ·      205200  ·           13  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  fulfillBasicOrder               ·      92295  ·    1628323  ·      665700  ·          160  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  fulfillOrder                    ·     101861  ·     212811  ·      173831  ·          105  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  incrementCounter                ·          -  ·          -  ·       47029  ·            6  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  matchAdvancedOrders             ·     204305  ·     271088  ·      252862  ·           67  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  matchOrders                     ·     164525  ·     363989  ·      265784  ·          105  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                

patched:

······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  fulfillAdvancedOrder            ·     100247  ·     209063  ·      160207  ·          126  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  fulfillAvailableAdvancedOrders  ·     137767  ·     227033  ·      193234  ·           19  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  fulfillAvailableOrders          ·     171646  ·     226690  ·      205057  ·           13  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  fulfillBasicOrder               ·      92295  ·    1628323  ·      665700  ·          160  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  fulfillOrder                    ·     101613  ·     212553  ·      173575  ·          105  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  incrementCounter                ·          -  ·          -  ·       47029  ·            6  ·          -  │                
······························|··································|·············|·············|··············|···············|··············                
|  Seaport                    ·  matchAdvancedOrders             ·     204109  ·     270892  ·      252636  ·           67  ·          -  │                
······························|··································|·············|·············|··············|···············|··············

@@ -53,6 +51,9 @@ contract AmountDeriver is AmountDerivationErrors {
}
}

// Derive time remaining until order expires
uint256 remaining = duration - elapsed;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TODO: could this potentially be unchecked? Still need to investigate

Choose a reason for hiding this comment

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

Constraints that are true from c4 review:

  1. $startTime \ge block.timestamp_t$
  2. $endTime > block.timestamp_t$
  3. $endTime > startTime$ (1, 2)
  4. $duration = endTime - startTime$
  5. $duration > 0$ (3, 4)
  6. $elapsed = block.timestamp_w - startTime$
  7. $elapsed \ge 0$
  8. $block.timestamp = endTime \implies invalid(order)$
  9. $elapsed < duration$ (6, 8)

From this I think unchecked is fine.

@naps62
Copy link
Contributor Author

naps62 commented Jun 8, 2022

After a better look, this could even be further optimized, by sending directly startTime and endTime down, and let _locateCurrentAmount compute duration and elapsed as well

However, this would either require changing pure to view, or sending along block.timestamp as well. Usually I would consider this less readable and probably not worth it, but being such a hot code path, maybe it is?

@leonardoalt
Copy link

For ref, see https://github.com/ProjectOpenSea/seaport/pull/355/files which is doing similar things.

@naps62
Copy link
Contributor Author

naps62 commented Jun 8, 2022

Now defering computation of duration and elapsed as well, as per my previous comment

For now deferred the decision of adding unchecked, since it warrants more review, and already generated some comments on #355

uint256 duration = endTime - startTime;

// Derive time elapsed since the order started & place on stack.
uint256 elapsed = block.timestamp - startTime;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this ended up making the change much bigger than expected, since changing the function from pure to view and relying on current timestamp force me to switch around a lot of code in the test suite

a better compromise could have been to instead keep the function pure and send in an extra currentTime. Someone please let me know if that's preferable, and I'll make the change

Copy link
Contributor

Choose a reason for hiding this comment

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

I think deferring these calculations until necessary makes a lot of sense. If you add a section to the natspec here documenting the conditions asserted in _verifyTime you could also make this computation unchecked

Copy link
Contributor

@0age 0age left a comment

Choose a reason for hiding this comment

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

This is nice! Left some general cleanup notes.

uint256 duration = endTime - startTime;

// Derive time elapsed since the order started & place on stack.
uint256 elapsed = block.timestamp - startTime;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think deferring these calculations until necessary makes a lot of sense. If you add a section to the natspec here documenting the conditions asserted in _verifyTime you could also make this computation unchecked

@0age
Copy link
Contributor

0age commented Jun 8, 2022

We do need to work out why these forge tests are breaking now as well

@naps62
Copy link
Contributor Author

naps62 commented Jun 8, 2022

@0age fixed all pending comments, and included underflow noticed as requested (actually re-used the notice from #355)

as for the test suite, it was failing to compile due to a "stack too deep error", which I fixed with this. However, the forge suite is failing for me locally, for reasons I haven't yet understood. It fails on main as well though, so I don't think it's a problem here. can we try re-runing the action?

@naps62 naps62 requested a review from 0age June 8, 2022 15:15
@0age
Copy link
Contributor

0age commented Jun 8, 2022

Rerunning forge tests now!

@0age
Copy link
Contributor

0age commented Jun 8, 2022

While we're working out what's going on with the forge tests, just wanted to also flag an implication of this change: for orders with multiple ascending / descending amount items, these parameters will now be calculated multiple times. It does seem like most items won't have ascending or descending amounts, and the orders that do have an item with ascending or descending amounts usually won't have more than one, but just wanted to flag it and hear what you think.

@0age 0age changed the base branch from main to optimize-apply-fraction June 8, 2022 21:29
@0age 0age merged commit 084c9d0 into ProjectOpenSea:optimize-apply-fraction Jun 8, 2022
@naps62 naps62 mentioned this pull request Jun 13, 2022
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.

3 participants