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

Recycle VM components in between host calls #827

Closed
jayz22 opened this issue May 31, 2023 · 13 comments
Closed

Recycle VM components in between host calls #827

jayz22 opened this issue May 31, 2023 · 13 comments
Assignees
Labels
enhancement New feature or request

Comments

@jayz22
Copy link
Contributor

jayz22 commented May 31, 2023

The VmInstantiation cost is by far the largest component in the host, due to the underlying cost of parsing the WASM contract and instantiating the VM. This will present bottleneck for contracts that make many cross-contract calls (e.g see #825 (comment)).

We could perform optimization such as caching parsed contracts between invocation/tx, or having the host own some shared VM components (e.g. Engine, maybe a version of Store) for all Vm instantiations to start from.

@jayz22
Copy link
Contributor Author

jayz22 commented Jun 6, 2023

I did a quick exploration on possibly sharing VM components between contracts to improve performance. There doesn't seems to be any low-hanging fruits there.

Even Engine can not be shared currently due to wasmi-labs/wasmi#631. The engine owns EngineResources for performance reasons and is locked when accessed from a Wasm contract. Calling another contract within a contract will attempt to access the same engine which results in a deadlock.

@jayz22
Copy link
Contributor Author

jayz22 commented Jun 6, 2023

Also, for the same reason caching parsed Wasmi module won't work either, because the module is coupled with the engine (which led to the creation of the wasmi-labs/wasmi#631).

@MonsieurNicolas
Copy link
Contributor

Caching is probably doable in the context of a single transaction, but crossing transaction boundary is probably fairly challenging:
as cpuInstructionCount is a non refundable resource, we'd have to come up with some scheme that allows to reason at the tx set level (to also divide up fairly the cost of instantiation between transactions).

If we were to do it at the transaction level only, I am not sure the benefits would be there.

@graydon
Copy link
Contributor

graydon commented Jun 12, 2023

We got some feedback today that some contracts want to call other contracts repeatedly (eg. to inquire about the balance of multiple accounts) and so in this context a "first cross contract call to X is charged instantiation, subsequent calls are charged only a cheaper no-module-parsing / cached-instantiation" model makes sense (it's not sensitive to transaction order or anything).

@graydon graydon added the enhancement New feature or request label Jun 20, 2023
@graydon
Copy link
Contributor

graydon commented Jun 20, 2023

Fwiw I think we should do this before final, but perhaps a little after we have instrumentation sufficient to quantify the win, so sometime over the summer. It'll be an XDR change (to add a new cost type for cached instantiations) but otherwise the only user visible effect should be things get cheaper.

@graydon
Copy link
Contributor

graydon commented Jun 20, 2023

(I've just set this to preview 10 and assigned to @jayz22 but that is just for the XDR change -- we can do the rest later)

@anupsdf
Copy link
Contributor

anupsdf commented Oct 12, 2023

FYI, the XDR change for a new ContractCostType::VmCachedInstantiation has been done. The remaining work in env is non-protocol breaking, so can be done after v20 stable release package is out.

@jayz22
Copy link
Contributor Author

jayz22 commented Oct 13, 2023

As per discussion today, we will be adding the necessary data structure for storing contract IDs and control flow for charging VmCachedInstantiation for calls to a known contract. This will give us freedom to implement the caching logic later, without breaking the protocol.
One of the main risks of this work is surprises from Wasmi side, e.g. not allowing certain things to be cached (see above). The risk of stale/polluted data is lower.

@jayz22
Copy link
Contributor Author

jayz22 commented Oct 16, 2023

Bumping prio to P0 for adding in the charging control flow. Once done will reduce it back for the actual caching implementation.

@namankumar
Copy link

What is our latest thinking on what to cache? Trying to determine the low hanging fruits while maximizing benefit to the user.

Got ecosystem feedback today:

Shared server cache for recently used contracts. So, for example, the first user pays a full fee of contract loading, while all subsequent calls from other users are much cheaper while the contract remains in cache (ideally, at least for several minutes).

@roman-karpovich
Copy link

It would be big improvement to cache contracts at least within single operation as first step. There are use-cases when we need to invoke reasonably limited amount of sub-contracts deployed using single wasm code.

@graydon
Copy link
Contributor

graydon commented Feb 29, 2024

concerning the deadlock above, I think the most likely-to-work (and not be horribly ugly) solution to this is to pre-parse all modules in the footprint of a given transaction, before we begin executing any. on the one hand it means there's a possibility of "charging someone for work they don't ultimately use" (eg. if they mention a module in their footprint but then, due to some conditional logic in the contract, ultimately never call that module); on the other hand it's a simple model to reason about, and I expect the great majority of cases will benefit from the caching and not be punished by the eager parse (i.e. they will only mention contracts in their footprint that they're actually going to call).

github-merge-queue bot pushed a commit that referenced this issue Mar 26, 2024
This is a sketch of the first step in the plan for addressing #1292 and
eventually #827 and #1313, namely:

- add a bunch of cost types that decompose the current "worst case" VM
instantiation cost type
  - continue to charge the worst case on initial contract upload
- _store_ the decomposed cost-type inputs in the ledger, since we can
observe them after the initial upload parse
- _use_ those decomposed cost-type inputs when doing runtime
instantiation
  - add a module cache
  - populate the module cache with all modules on host startup
  - use cached modules during instantiation

This PR has accompanying changes in XDR and wasmi:

  - stellar/stellar-xdr#177
  - stellar/rs-stellar-xdr#346

Remaining to do:

  - [x] determine what the correct set of decomposed cost types even is
  - [ ] ~add more code to wasmi to enable observing more inputs~
- [x] add cost-type runners / calibrations for all the decomposed
cost-types
  - [x] protocol-gate this new behaviour
- [x] make the linker-loop do less work to match the tighter cost model
(i.e. complete #1292)
- [x] possibly _duplicate_ the set of cost types added here, so we have
a cached and uncached version of each, and implement a solution to #827
- [ ] possibly decompose the set sufficiently to model what will happen
when we take wasmi 0.32 and enable lazy translation (i.e. complete
#1313)
@graydon
Copy link
Contributor

graydon commented Apr 8, 2024

Fixed by #1359 and #1375

@graydon graydon closed this as completed Apr 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

7 participants
@graydon @namankumar @MonsieurNicolas @roman-karpovich @jayz22 @anupsdf and others