wasm2c: Save memory base locally as a perf optimization - #2804
Conversation
f9f0f77 to
1a4b701
Compare
|
Can you update the PR description now that part 1 has landed? |
|
Did some cleanup to simplify the code and reduce the code-diff and updated the PR description. |
|
In your benchmark results I guess +37.6% means 37% overhead compared to native? i.e. bigger is worse? |
| #if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_MEMORY | ||
| // We can only use Segue for this module if it uses a single unshared, | ||
| // default-page, 32-bit imported or exported memory. | ||
| #if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_DEFAULT32_MEMORY |
There was a problem hiding this comment.
How about splitting out this change?
There was a problem hiding this comment.
I think this potentially belongs in this PR.
Reason: Segue doesn't need this change. This change is needed because this current PR (aka local-base optimization) has the same constrains as segue + some additional constraints. Rather than segue and local-base doing 2 different sets of constraints checks, I've changed things so segue and the local-base optimization use a superset of constraints. So I see this as part of the local-base optimization change.
Details
The local base optimization works only when the memory is not movable -- i.e., linear memory is allocated with mmap. However, wasm2c does not use mmap for memory types whose page size is a custom. So the full set of constraints are
- Single memory
- Unshared
- Default32
Segue actually can support custom page size memories too, as it supports movable memories as long as the memory growth happens within the module and thus it only requires
- Single memory
- Unshared
But writing code to separately check segue's constraints and linear base's constraints adds more complexity than needed for a use case that presumably get's very little use. Hence this approach which just has segue and local-base check for Single memory, Unshared, Default32
For Wasm modules with a single unshared wasm-32 memory, and when using the MMap based memory allocation (which guarantees that the base won't move over the Wasm instance lifetime), this change caches the memory base in a local variable and uses that instead of fetching the value each time from the instance pointer.
While this seems like it really shouldn't do much, it unlocks a bunch of optimizations in C compilers, as they don't seem to be able to reason that the base pointer hasn't changed after most function calls with the Wasm code. The end result is some dramatic improvements.
As a reference, this approach is able to claw back a lot of the same performance overheads of segue (i.e., using the segment register) in wasm2c except without segment registers, meaning this will work on all platforms. This makes some intuitive sense, as I expect that segue is more important to classic SFI tools (like LFI) rather than a Wasm like system. (But there are other benchmarks that segue still is a huge win --- on average, segue still remains the best option if supported on the target platform)
Anyway, here are the performance numbers from two use cases in Firefox: Expat XML parsing, Graphite font rendering that use Wasm sandboxes. The number is the overhead over native code (so bigger is worse)
Note the "local base" row is the new row below
Expat XML parsing overhead
Baseline: native
Wasm2c (Mmap + guard pages, no segue): +37.6%
Wasm2c (Mmap + guard pages, segue): +13.7%
Wasm2c (Mmap + guard pages, local base): +18.7%
Wasm2c (Mmap + bounds checks, no segue): +50.7%
Wasm2c (Mmap + bounds checks, segue): +37.4%
Wasm2c (Mmap + bounds checks, local base): +32.8%
Graphite font overhead
Baseline: native
Wasm2c (Mmap + guard pages, no segue): +41.2%
Wasm2c (Mmap + guard pages, segue): +18.2%
Wasm2c (Mmap + guard pages, local base): +14.1%
Wasm2c (Mmap + bounds checks, no segue): +60.0%
Wasm2c (Mmap + bounds checks, segue): +43.3%
Wasm2c (Mmap + bounds checks, local base): +41.8%
I intend to enable this optimization in Firefox's builds asap once it is landed here.