andrewl6097 opened PR #14753 from andrewl6097:guest-profiler-cache-symbols to bytecodealliance:main:
Closes #14752
GuestProfiler::newrebuilds and re-demangles each module's symbol table on
every call, which costs milliseconds for large modules. This builds the table
the first time a profiler includes a module, keeps it on theModule, and
shares it through anArcwith every later profiler.Changes:
ModuleInnergets aprofiling_symbols: OnceLock<Option<Arc<SymbolTable>>>
behindcfg(feature = "profiling"), with apub(crate)accessor. There is no
public API change.
module_symbolsreads the table from that cache. The table-building code is
moved unchanged intomodule_symbol_table.
GuestProfiler::newadds the library without its table and attaches the
shared table withProfile::set_lib_symbol_table.Profile::add_libkeys
libraries by value, so a table carried insideLibraryInfowould be hashed,
every name included, on each new profile.A unit test profiles the same module twice. It checks that the second
profiler reuses the first one's table (Arc::ptr_eq) and that both profiles
name the sampled frame, demangled. Without the cache the test fails.Measurements, release build, x86_64 Linux, using the reproduction program from
the issue (median of 1000):
Functions Before After, first profiler After, later profilers 1,500 672 µs 695 µs 12.8 µs 10,000 4.60 ms 4.56 ms 79.9 µs On a real 1.7 MB Rust module with about 1,500 functions, a later
new()drops
from 2.36 ms to 16.3 µs (measured with this change applied to wasmtime 43.0.2).The remaining per-profile cost still grows with the function count, about 8 ns
per function. I haven't looked into where it goes, and it isn't addressed here.Checked locally:
cargo test -p wasmtime --lib profiling;cargo fmt --check;
cargo clippy -p wasmtime --all-targetswith no warnings;cargo check -p wasmtime --no-default-featureswith and withoutprofiling, with and without
runtime. I couldn't run the CLI test suite (tests/all) locally.
andrewl6097 requested alexcrichton for a review on PR #14753.
andrewl6097 requested wasmtime-core-reviewers for a review on PR #14753.
andrewl6097 updated PR #14753.
:memo: alexcrichton submitted PR review:
Thanks! Could the
profiling_symbolscache be stored on the profiler itself rather than theModulethough? Also for this I think it's ok to skip tests since it's just an internal refactor
andrewl6097 commented on PR #14753:
I think that the issue there would be that Profilers are pretty short-lived things, AFAICT - there's one per time that you profile things, and so different invocations of the guest all get their own Profiler.
wasmtime serve, for example, currently makes a new guest profiler on every instance that it creates. So absent a good deal more plumbing, like "create a new profiler using this old one" or "re-use a profiler", both of which then require API changes from consumers to get the benefit.Happy to drop the test in either case.
andrewl6097 updated PR #14753.
andrewl6097 edited PR #14753:
Closes #14752
GuestProfiler::newrebuilds and re-demangles each module's symbol table on
every call, which costs milliseconds for large modules. This builds the table
the first time a profiler includes a module, keeps it on theModule, and
shares it through anArcwith every later profiler.Changes:
ModuleInnergets aprofiling_symbols: OnceLock<Option<Arc<SymbolTable>>>
behindcfg(feature = "profiling"), with apub(crate)accessor. There is no
public API change.
module_symbolsreads the table from that cache. The table-building code is
moved unchanged intomodule_symbol_table.
GuestProfiler::newadds the library without its table and attaches the
shared table withProfile::set_lib_symbol_table.Profile::add_libkeys
libraries by value, so a table carried insideLibraryInfowould be hashed,
every name included, on each new profile.Measurements, release build, x86_64 Linux, using the reproduction program from
the issue (median of 1000):
Functions Before After, first profiler After, later profilers 1,500 672 µs 695 µs 12.8 µs 10,000 4.60 ms 4.56 ms 79.9 µs On a real 1.7 MB Rust module with about 1,500 functions, a later
new()drops
from 2.36 ms to 16.3 µs (measured with this change applied to wasmtime 43.0.2).The remaining per-profile cost still grows with the function count, about 8 ns
per function. I haven't looked into where it goes, and it isn't addressed here.Checked locally: the reproduction program from the issue;
cargo fmt --check;
cargo clippy -p wasmtime --all-targetswith no warnings;cargo check -p wasmtime --no-default-featureswith and withoutprofiling, with and without
runtime. I couldn't run the CLI test suite (tests/all) locally.
github-actions[bot] added the label wasmtime:api on PR #14753.
alexcrichton commented on PR #14753:
Ah that's a good point yeah. I'm personally a bit hesitant though to have the dependencies here worm their way into the data representation of modules because in theory profiling is a pretty orthogonal feature. The way we've solve this in the past is with a slightly different API design though -- could the string/module iterator passed to
GuestProfiler::newbe wrapped up in a new abstraction perhaps? Something likeGuestProfilerModulesand that can be the pre-computation/cache for the symbol table which is shared amongst a set ofGuestProfilers?
andrewl6097 commented on PR #14753:
Ok - that's fair guidance. Let me noodle around on it for a bit here.
andrewl6097 updated PR #14753.
andrewl6097 requested alexcrichton for a review on PR #14753.
andrewl6097 requested wasmtime-default-reviewers for a review on PR #14753.
andrewl6097 updated PR #14753.
Last updated: Oct 31 2026 at 23:03 UTC