Stream: git-wasmtime

Topic: wasmtime / PR #13822 Gc performance improvements, in part...


view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 14:14):

tschneidereit requested fitzgen for a review on PR #13822.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 14:14):

tschneidereit requested wasmtime-core-reviewers for a review on PR #13822.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 14:14):

tschneidereit opened PR #13822 from tschneidereit:gc-perf-improvements to bytecodealliance:main:

This is a set of commits which, combined, substantially improve GC performance, at least as measured for one mid-sized Kotlin component I've been testing. (Which I unfortunately can't share.)

While single-threaded performance improves substantially with the first commit, the other two only make a real difference when running multiple instances in parallel. In total, performance for 20 concurrent requests improves from 168 RPS to 17680 RPS:

rev concurrency=1 concurrency=20
main 239 168
initial-heap-size=2MB 1549 1809
batched-trace-info 1565 4050
cached-subtype-checks 1610 17681

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 14:15):

tschneidereit commented on PR #13822:

One thing I'm wondering is if we should choose a different default for the initial GC heap size allocation. Perhaps we should set that to at least one wasm page?

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 14:17):

tschneidereit updated PR #13822.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 14:27):

tschneidereit commented on PR #13822:

One thing I'm wondering is if we should choose a different default for the initial GC heap size allocation. Perhaps we should set that to at least one wasm page?

Testing this just now, it doesn't seem to really make much of a difference. Which I guess makes some sense: setting the initial size to 64kb will only ever remove the first, cheapest growth cycle.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 14:48):

tschneidereit updated PR #13822.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 16:43):

:memo: alexcrichton submitted PR review:

Overall this looks pretty reasonable to me, and are definitely levers that were expected to be slow points about the GC implementation (primarily all the locking).

Personally I'd like to ideally consider completely redesigning how TraceInfo is managed (the second commit here). To me this seems like something we should precompute at module-creation-time and not require any locks at all during instantiation/runtime. As-is that's not going to be an easy refactor, however.

I also think that this is something where we're going to need to bust out unsafe and raw pointers. For example what I'm thinking is that Modules (and RegisteredTypes) should have TraceInfo precomputed along with a map from VMSharedTypeIndex to this information. When instantiating within a store this shouldn't require cloning TraceInfo because that'll just slow down instantiation, especially when there are a lot of types. The module/RegisteredType is already strong-held by the store as well, so using more Arcs is also extra overhead (e.g. cloning a contended Arc is not the cheapest).

Basically the second commit here I'd like to ideally separate out and take some time to figure out what we "really want to do" w.r.t. performance there.

The third commit I think is another in this category, but the true fix is something we know about and is also pretty gnarly. As a temporary fix for performance the hash-map-cache seems reasonable.

So, tl;dr; I think it's fine to land the 1st/3rd commits here with light modifications, but I'd prefer to take more time to figure out what to do about the 2nd commit.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 16:43):

:speech_balloon: alexcrichton created PR review comment:

Would it be possible to model ensure_trace_info as a wrapper around this function instead of the other way around? That way implementations are guaranteed to, by default, implement the bulk registration.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 16:43):

:speech_balloon: alexcrichton created PR review comment:

I think this'd be best stored in Tunables so gc_heap_memory_type() could also be updated as-is to return a memory with the appropriate minimum set. Implementation/feature-wise this all looks fine though, so just shuffling around where this is defined.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 16:43):

:speech_balloon: alexcrichton created PR review comment:

Could this use NopHasher we have elsewhere perhaps?

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 17:25):

:memo: fitzgen submitted PR review:

Thanks! A few requested tweaks.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 17:25):

:speech_balloon: fitzgen created PR review comment:

Can we store this in the tunables, where we store the other similar knobs for memories and the GC heap?

Also, what happens if the gc_heap_initial_size is larger than the gc_heap_reservation? It seems like maybe we actually just want to always make this be round_down(gc_heap_reservation, DEFAULT_WASM_PAGE_SIZE) and adjust the Tunables::gc_heap_memory_type method to have its initial/min size reflect that reservation size. This would, I think, give us the wins this commit is aiming for without adding a new / partially redundant config knob.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 17:25):

:speech_balloon: fitzgen created PR review comment:

As an additional potential follow up: we could probably make this register even fewer trace infos by making it so that if the collector keeps small trace infos inline in the GC header (which the copying collector does) then we don't actually register that type in the TraceInfos registry. Would need a little API design work since different collectors do/don't store tracing data inline the GC header, and theoretically they could also have different capacity for the size of trace info data that can be stored inline in the header. But it is worth looking into.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 17:25):

:speech_balloon: fitzgen created PR review comment:

Could we factor this cache out into its own type to try and encapsulate some of the details from the rest of the StoreOpaque (which is massive, and I don't want to keep making it more massive), something like this:

// is_subtype_cache.rs

pub(crate) struct IsSubtypeCache {
    cache: crate::hash_map::HashMap<u64, bool, GcTypeCacheHasher>,
}

impl IsSubtypeCache {
    pub(crate) fn is_subtype(&mut self, engine: &Engine, sub: VMSharedTypeIndex, sup: VMSharedTypeIndex) -> bool {
         // ...
    }
}

// store.rs

impl StoreOpaque {
    pub fn is_subtype(&mut self, sub: VMSharedTypeIndex, sup: VMSharedTypeIndex) -> bool {
        self.is_subtype_cache.is_subtype(&self.engine, sub, sup)
    }
}

Also, do we want this to cache's size to grow unbounded? Maybe we want to use an LRU cache like https://docs.rs/associative-cache/latest/associative_cache/ instead?

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 17:25):

:speech_balloon: fitzgen created PR review comment:

Also, if we do end up keeping this new config knob, we want to also hook it up to our fuzz config in crates/fuzzing/src/generators/config.rs

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 18:22):

github-actions[bot] added the label wasmtime:api on PR #13822.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 18:22):

github-actions[bot] added the label wasmtime:config on PR #13822.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 18:22):

github-actions[bot] added the label wasmtime:ref-types on PR #13822.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 18:23):

github-actions[bot] commented on PR #13822:

Subscribe to Label Action

cc @fitzgen

<details>
This issue or pull request has been labeled: "wasmtime:api", "wasmtime:config", "wasmtime:ref-types"

Thus the following users have been cc'd because of the following labels:

To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.

Learn more.
</details>

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 19:18):

github-actions[bot] commented on PR #13822:

Label Messager: wasmtime:config

It looks like you are changing Wasmtime's configuration options. Make sure to
complete this check list:

[fuzzing-config]: https://github.com/bytecodealliance/wasmtime/blob/ca0e8d0a1d8cefc0496dba2f77a670571d8fdcab/crates/fuzzing/src/generators.rs#L182-L194
[fuzzing-docs]: https://docs.wasmtime.dev/contributing-fuzzing.html


<details>

To modify this label's message, edit the <code>.github/label-messager/wasmtime-config.md</code> file.

To add new label messages or remove existing label messages, edit the
<code>.github/label-messager.json</code> configuration file.

Learn more.

</details>

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 21:15):

fitzgen commented on PR #13822:

Also I'd be curious whether you saw/see any perf improvements on Sightglass's GC benchmarks:

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 21:22):

:memo: tschneidereit submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 21:22):

:speech_balloon: tschneidereit created PR review comment:

The reason I didn't make it a tunable is that it doesn't affect compilation, and can be tweaked at runtime if so desired. I agree that it's in a bit of a gnarly location as-is, though.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 21:23):

:memo: tschneidereit submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 21:23):

:speech_balloon: tschneidereit created PR review comment:

but I do like the idea of round_down(gc_heap_reservation, DEFAULT_WASM_PAGE_SIZE), so maybe that's just the answer and the option can go away entirely.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 21:24):

:memo: tschneidereit submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 21:24):

:speech_balloon: tschneidereit created PR review comment:

See the conversation over here

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 21:24):

:memo: tschneidereit submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 21:24):

:speech_balloon: tschneidereit created PR review comment:

err, wrong thread

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 21:25):

:memo: tschneidereit submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 21:25):

:speech_balloon: tschneidereit created PR review comment:

See the conversation over here

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 21:26):

tschneidereit commented on PR #13822:

Personally I'd like to ideally consider completely redesigning how TraceInfo is managed (the second commit here). To me this seems like something we should precompute at module-creation-time and not require any locks at all during instantiation/runtime. As-is that's not going to be an easy refactor, however.

I should've mentioned that I looked at that and then decided that there's no way we'd backport it, so focused on something much smaller ... and then forgot to note that the real fix should happen as follow-up work.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 07 2026 at 16:38):

:memo: alexcrichton submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 07 2026 at 16:38):

:speech_balloon: alexcrichton created PR review comment:

I've moved this part of this PR over to https://github.com/bytecodealliance/wasmtime/pull/13841 where I've hooked up fuzzing and configured a tunable. FWIW though this setting can affect codegen because a static offset known to be beneath the threshold of the minimum size of a linear memory can avoid the bounds check entirely for example.

Also, for the initial value, VM-wise there's no consequence to setting this equal to gc_heap_reservation, but store-resource wise there's still consequence. For example the resource limiter will think that an allocation of 4GiB is being made if gc_heap_initial_size matches gc_heap_reservation. Effectively by increasing the initial size the embedder loses hooks into growth to limit resource usage.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 07 2026 at 21:17):

alexcrichton commented on PR #13822:

Basically the second commit here I'd like to ideally separate out and take some time to figure out what we "really want to do" w.r.t. performance there.

I've created https://github.com/bytecodealliance/wasmtime/pull/13843 for what I was thinking here. Notably instead of batching lock acquisitions I want to eliminate the lock acquisition entirely as I think it's tractable to do so.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 08 2026 at 15:29):

:memo: alexcrichton submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 08 2026 at 15:29):

:speech_balloon: alexcrichton created PR review comment:

@fitzgen I'd personally prefer to not pick up a whole new dependency for this, and given that the set of types is probably fixed within a store and the max size of this cache is O(n^2) of the size of types, WDYT about leaving it as-is with just a hash map?

view this post on Zulip Wasmtime GitHub notifications bot (Jul 10 2026 at 15:50):

:memo: fitzgen submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 10 2026 at 15:50):

:speech_balloon: fitzgen created PR review comment:

FWIW, that crate is my own, not just a random crate.

$O(n^2)$ is maybe fine, but maybe not great? I don't know. Have you looked at how many types Kotlin programs are generating? It can be quite a few. Sightglass's kotlin-richards has 87 and its a pretty small program. The benchmark program that the Kotlin folks have been filing issues about recently has 7908 types. That's up to 62.5M entries in this cache...

The limit on types is 1M, so up to a trillion cache entries, and we don't have any backpressure/limits on the cache's growth -- given a malicious guest, that sounds like a guest-controlled resource growth CVE waiting to happen, no?

The more I think this through, the more I think we should probably not have an unbounded cache here.

We could fix this by simply capping the cache's size at some nearly-always-large-enough max capacity, and refusing to add new entries after that, but at that point why not just use an actual LRU cache? We can probably make the LRU cache's capacity much smaller while still getting 99% of the caching benefits.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 10 2026 at 16:38):

:memo: alexcrichton submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 10 2026 at 16:38):

:speech_balloon: alexcrichton created PR review comment:

Ok yeah makes sense to me. I've extracted this functionality to https://github.com/bytecodealliance/wasmtime/pull/13860 although I've stayed on HashMap for now because we'd need some work to get the associative-cache crate integrated, notably getting it working for #![no_std]. I'm also a bit hesitant to invest too much time into this as we know the "real solution", https://github.com/bytecodealliance/wasmtime/issues/13484, has nothing to do with a store-local map/cache. I think that a fixed-size map is probably good enough for most reasonable cases for now for performance and I'm not too worried about the maintenance overhead (just an extra field in a GC-specificfile already)

view this post on Zulip Wasmtime GitHub notifications bot (Jul 10 2026 at 16:39):

:cross_mark: alexcrichton closed without merge PR #13822.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 10 2026 at 16:39):

alexcrichton commented on PR #13822:

This is now extracted as a combination of:

I'm going to close this as a result, and once the final one lands I'll work on backports.


Last updated: Jul 29 2026 at 05:03 UTC