Stream: git-wasmtime

Topic: wasmtime / issue #3319 Optimize `Func::call` and its C API


view this post on Zulip Wasmtime GitHub notifications bot (Sep 08 2021 at 21:45):

github-actions[bot] commented on issue #3319:

Subscribe to Label Action

cc @fitzgen, @kubkon, @peterhuene

<details>
This issue or pull request has been labeled: "fuzzing", "wasi", "wasmtime:api", "wasmtime:c-api"

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 (Sep 09 2021 at 16:07):

alexcrichton commented on issue #3319:

To show an idea of the numbers I'm looking at, here's some information. I've got a C program that shows the following timings on various wasmtime versions. Timings are shown with using wasm.h and wasmtime.h. Times are all in nanoseconds.

What's being benchmarked:

nop

host calls a wasm function that does nothing

version wasm.h wasmtime.h
0.26.1 45
0.27.0 104
0.28.0 115 113
0.29.0 117 117
this PR 43 51

i64

host calls a wasm function. Wasm function calls a host function returning i64. Wasm then returns that i64.

version wasm.h wasmtime.h
0.26.1 181
0.27.0 247
0.28.0 258 240
0.29.0 258 245
this PR 130 104

many

host calls a wasm function. Wasm function calls a host function with 5 i32 params and one i32 return. Wasm discards result and returns.

version wasm.h wasmtime.h
0.26.1 223
0.27.0 275
0.28.0 295 258
0.29.0 284 261
this PR 173 138

This is all a far cry away from what Rust can do though, so while this PR certainly improves the state of affairs Rust far outstrips it. Rust has the option of using "typed" and "untyped" functions, effectively using Func or TypedFunc to call wasm functions or Func::wrap/Func::new to define host functions. The table below shows timings (in nanoseconds) of calling the functions in a typed/untyped fashion:

when imports are defined with Func::wrap

func typed untyped
nop 20 34
i64 21 41
many 23 37

when imports are defined with Func::new

func typed untyped
i64 43 62
many 85 100

The basic summary here is that when we have "typed" versions of everything basically any combination of arguments/returns is roughly 20ns of call-time overhead. That's the minimum threshold for calling a wasm function. Once "untyped" things are used then there starts to be per-argument and per-result overhead. This creeps up in both calling wasm functions and calling host functions, and can be seen how "many" is slower than "i64" which is slower than "nop". Finally the C API imposes about a 40ns blanket overhead on top of the wasmtime "untyped" Rust versions, mostly because wasmtime_val_t needs to be converted to Val and back.

Basically at this point I think that the "untyped" variants, which C currently is forced to use, are as optimal as they're gonna get. There's still inherent overhead with translation between wasmtime_val_t and Val, but at some point that can only be but so optimal. I think the only way forward to improve C bindings is to somehow get a "typed" variant in there which opens up opportunities to remove type translations and type checks entirely.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 13 2021 at 18:14):

alexcrichton commented on issue #3319:

I'll note that I think we still want this even in the face of https://github.com/bytecodealliance/wasmtime/pull/3345. I personally like the idea of taking &mut [Val] instead of returning Box<[Val]> for Func::call, and otherwise all the optimizations here are internal implementation details we can fiddle with over time.


Last updated: Nov 22 2024 at 17:03 UTC