adamrk opened PR #14189 from adamrk:cache-component-funcref to bytecodealliance:main:
A core
Funccaches a raw pointer to itsVMFuncRef, but a
component::Funcrederives the pointer on ever call and this
contributes to host -> wasm component function calls having
significantly higher overhead than core function calls (even concurrency
support disabled).This PR caches the
VMFuncRefforcomponent::Funcin the same it is
currently done for coreFunc. TheVMFuncReffor the associated
post_returncall is also cached along with Some additional metadata.These are my benchmark results for the impact on nop calls with no
arguments or return values:Before change:
| Call type | Latency | ----------------------------------------------- | core | 35 ns | | component (concurrency disabled) | 300 ns | | component (concurrency enabled) | 800 ns |After change:
| Call type | Latency | ----------------------------------------------- | core | 35 ns | | component (concurrency disabled) | 140 ns | | component (concurrency enabled) | 600 ns |The bencmarks run are:
cargo bench --bench call -- --exact "sync/no-hook/core - host-to-wasm - typed - nop" cargo bench --bench call -- --exact "no-concurrent/sync/no-hook/component - host-to-wasm - typed - nop" cargo bench --bench call -- --exact "concurrent/sync/no-hook/component - host-to-wasm - typed - nop"A separate commit also modifies the
callbenchmark to allow running component calls without concurrency support.
adamrk requested pchickey for a review on PR #14189.
adamrk requested wasmtime-core-reviewers for a review on PR #14189.
adamrk requested alexcrichton for a review on PR #14189.
adamrk requested wasmtime-default-reviewers for a review on PR #14189.
github-actions[bot] added the label wasmtime:api on PR #14189.
github-actions[bot] added the label wasmtime:c-api on PR #14189.
:memo: adamrk submitted PR review.
:speech_balloon: adamrk created PR review comment:
Note: This change to the benchmark was required to bench component calls with
concurrency_supportdisabled. The previous setup would only run them with it enabled.
:speech_balloon: adamrk created PR review comment:
Note: I think we could also save a lookup here by caching
CanonicalOptionsas well, but I didn't do it because that type has a bunch of fields and variants which I think would require either
- Making the C type messy and changing it whenever new canon opts are made. Or
- Boxing
CanonicalOptionsbefore storing it inFunc.
alexcrichton unassigned pchickey from PR #14189 wasmtime: Cache VMFuncRef in component::Func.
:memo: alexcrichton submitted PR review:
Nice wins! Do you have a relative breakdown of where the wins are coming from? For example which lookup is the most expensive?
I'm a bit wary to inflate
component::Functoo too much to avoid duplicating information thoughout the runtime, so if some of the field movements are pretty minor in wins and one field predominantly dominates that might be a way to cut down on duplication. Naively for example I'd expect thatExportIndexwould be relatively quick to lookup the type/options, and then the presence of post-return and async-ness in theory not too much more expensive when looking up the options themselves. Basically I'd expect that theunsafe_func_reffield to be the majority of the win here, but before actually changing anything here I'd want to confirm about where the performance wins come from
adamrk commented on PR #14189:
Do you have a relative breakdown of where the wins are coming from? For example which lookup is the most expensive?
I didn't do a breakdown, but I can get one. Also I'm now realizing that
abi_infogets called a few times, so maybe even if we don't include other fields inFuncwe can at least get some improvement by just callingabi_infoonce and reusing the result.
adamrk commented on PR #14189:
For the case of concurrency disabled, with just the
unsafe_func_reffield I'm seeing a time of180ns(from the300nsoriginal). So all the other fields together are an additional ~40ns. It's a bit hard to divide up the remaining amount between specific other fields because I'm seeing 10-20ns swings between runs anyway.I also realized we could mostly reuse the results of
abi_infoin the non-concurrent case without caching them inFuncand that seems to give us most of the benefits anyway. With the existing changes I'm now seeing140nswith concurrency disabled (same as the original change) and730nswith concurrency enabled (not as big as the original change).I guess this means we could just use the
unsafe_func_reffield and there's probably opportunity to improve the concurrent case as a follow up by reusing the results ofabi_infomore.
adamrk updated PR #14189.
:thumbs_up: alexcrichton submitted PR review:
Sounds reasonable to me, and changes look good!
alexcrichton added PR #14189 wasmtime: Cache VMFuncRef in component::Func to the merge queue
:check: alexcrichton merged PR #14189.
alexcrichton removed PR #14189 wasmtime: Cache VMFuncRef in component::Func from the merge queue
Last updated: Aug 30 2026 at 09:07 UTC