New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Interning internal enum representations and field names #3468
Comments
That's the solution I came up with immediately as well. It doesn't sound too excessive to be honest, considering applications that use WebGPU are probably not small. I'm not sure if there were better proposals in the past, but we could of course change the way bindings like this are generated to be in JS instead of using |
I was thinking we'd apply the solution here across all WebIDL bindings instead of just WebGPU, or at least be able to opt-in to it per-WebIDL file or something. I think the string intern cache could be pretty big depending on where we apply it |
I don't really think that's an option, it has to be opt-in for users in my opinion. I did a bit more research on this and I'm not sure why exactly it was decided to implement dictionaries like this, it doesn't seem like a good trade-off to me. I would absolutely be in favor of implementing dictionaries without |
Looking at the original implementation #702 it wasn't discussed. Probably the best solution forward would be to just implement dictionaries with the On that note, nothing is really stopping you from doing this already in Enums are a bit more involved. This probably requires some adjustments in the All of that should be possible without a breaking change. Happy to provide guidance to anybody wanting to work on this. |
Right, this is why I was thinking it might be excessive if it's only useful for high frequency APIs.
What are the benefits of going through the macro vs. interning the strings (e.g. wrapping field names and enum values in
Some projects already use the web-sys types directly and they're part of wgpu's public API, so we can't really do this in a practical way without slightly breaking the existing ecosystem.
Could you outline the rough steps here a bit? I've changed the WebIDL generator before but not sure how involved this change would be. |
Going through the macro will generate JS bindings, so instead of using
Currently the WebIDL generator converts this: dictionary Foo {
float bar;
}; ... roughly into this: #[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = Object)]
type Foo;
}
impl Foo {
fn bar(&mut self, val: f32) -> &mut Self {
Reflect::set(&self, "bar".into(), val.into());
self
}
} What I'm proposing is to let the #[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = Object)]
type Foo;
// don't expose this method
#[wasm_bindgen(method, getter, js_name = bar)]
fn bar_internal(this: &Foo, val: f32);
}
// here we wrap the method above to keep the interface the same; no breaking changes after all
impl Foo {
fn bar(&mut self, val: f32) -> &mut Self {
self.bar_internal(val);
self
}
} Though as I said above, I would like to get the input of another maintainer first. |
This all sounds reasonable to me 👍 Also, I looked into the string cache a bit and it would be able to work with the current design if not for the fact that You don't need to call |
@grovesNL I think we crystallized a couple of solutions here for you:
I think solution 1. and 2. are easy enough that any Rust contributor can tackle them, 3. is much more involved, but definitely not impossible for somebody new to |
@daxpedda ok great! In that case solution 3 sounds like the proper fix in the long term. Could you provide any implementation guidance in this thread? I'd be happy to start looking into it. |
Solution 1. is probably what you should start with. Solution 3. only replaces solution 2. -- Solution 1.Should be fairly easy, you just need to change what the WebIDL generator outputs in the case of dictionaries. So this looks like it is already everything you need to change. For details on what you should actually let the WebIDL generator generate, look at #3468 (comment). Solution 3.Is more complicated and involves two parts.
Probably what you want to end up with is just passing through an integer. The enum should generate a JS function that turns this integer into the proper string, which is then called. To generate the enum JS function, you will have to encode the variant strings into the "description". The second part is just again, like in solution 1., hooking it up into the WebIDL generator to use the feature you just implemented. If you get blocked on anything please feel free to just contact me on Matrix. |
wasm-bindgen/crates/backend/src/codegen.rs Lines 917 to 1029 in f569fdd
Everything else you said is right, it's just that the existing implementation needs to be modified rather than making a new one. |
So my thinking was that we might want to leave whatever After all they do represent two different things, export vs import. On the other hand this might be pretty useless, as JS isn't typesafe in this way and enums don't actually exist like this per se. WDYT? |
While Applying |
I was not aware of this behavior ... |
Is this currently being worked on? If no, would the info in this issue be enough for a wasm bindgen noob to work on it? I wouldn't mind giving it a shot. It's the currently the biggest perf bottleneck in my engine's web target. |
See #3468 (comment) for a summary of what needs to be done. The 1st solution outlined there should be pretty easy to implement. |
Motivation
We use a lot of enums for WebGPU at high frequency, e.g. https://docs.rs/web-sys/0.3.63/web_sys/struct.GpuRenderPassDepthStencilAttachment.html#method.stencil_load_op
These enums are represented as strings in the WebIDL, e.g.
wasm-bindgen/crates/web-sys/webidls/unstable/WebGPU.webidl
Lines 1016 to 1019 in 5f10d6f
This is problematic because we end up going through
JsValue::from_str
and creating a significant amount of temporary string allocations for both string enum values and field names.Proposed Solution
Values like
load
andclear
in the example above could be internally interned (i.e., cached on the JS side) so we don't create temporary strings for these.I don't know how we'd expose this option, but ideally we'd want to be able to control whether string enum values and field names are interned somehow. This might need to be fine-grained so that only high-frequency enums/field names are interned.
We could consider lazy-loading all string enum values and field names into a cache for interning but that might be excessive.
Alternatives
We could use wasm-bindgen's existing
enable-interning
feature somehow but we can't use theintern
function on enums or field names since they're generated internally in the WebIDL bindings.We could also consider internally representing these as something other than a string (e.g., remapping to
u32
and mapping it back to a string on the JS side).Additional Context
This adds significant overhead in real use cases so it's not a micro-optimization. I raised this to the WebGPU specification in the early days (hoping we could use
u32
instead to avoid the string overhead). We're seeing the performance problems show up in projects now since wasm host bindings aren't available yet.I'd guess this affects a lot of other generated bindings that are called at high frequencies too (e.g., DOM access, audio) so this could improve performance for a lot of projects, especially if they're sensitive to GC hitching (like most WebGPU applications).
The text was updated successfully, but these errors were encountered: