Hi there! I wanted to see how wasm-bindgen performs compared to vanilla JS so I made some benchmarks for UI bench: https://localvoid.github.io/uibench/
wasm-bindgen code: https://github.com/sagan-software/uibench-wasm-bindgen/blob/master/lib.rswasm-bindgen benchmarks by copying/pasting the URL above into the "Custom URL" input and clicking "Open"
So far the results are okay, but vanilla JS is consistently out-performing my wasm-bindgen implementation. Is there anything obvious I could change to speed it up? Thanks!
Have you tried profiling the wasm application in your browser's dev tools to see which bits are taking the most time?
If not - that would be a great start!
If so - what were the results?
https://rustwasm.github.io/book/reference/time-profiling.html is a good place to start for reasoning about performance. If you can come back with some more specific questions and/or highlight where time is being spent, that would be very useful.
Thanks @chinedufn @fitzgen . I did some profiling and it looks like deserializing data with Serde was taking a decent amount of time, so I refactored my structs into a bunch of externs: https://github.com/sagan-software/uibench-wasm-bindgen/blob/master/lib.rs
Using externs has helped some but it's still consistently slower than vanilla JS. Also it isn't very ergonomic but that's okay, right now I'm just trying to determine if there's any way Rust/WASM can beat vanilla JS in DOM manipulation speed. I know the answer is probably no, but I want to see how close I can get, and I know host bindings will help with this later on.
Here are the top results from profiling this page now. Honestly I'm not sure exactly how to read this:

So it seems that attaching/executing the onclick event listener on line 220 is taking a good amount of time? Is there some way to do that more efficiently? The benchmark requires that every .TableCell element has an onclick event listener that logs to the console, otherwise I would just use a single click event on the document.
The benchmark requires that every .TableCell element has an onclick event listener that logs to the console, otherwise I would just use a single click event on the document.
Is this a hard requirement? I know React (and some others) does DOM event delegation, so it only attaches a single event listener, so wouldn't that requirement disqualify React?
I did some profiling and it looks like deserializing data with Serde was taking a decent amount of time, so I refactored my
structs into a bunch ofexterns
Yeah, serializing and deserializing data is relatively expensive. Generally much faster to pass a 32-bit number value (pointer) instead.
I'm just trying to determine if there's any way Rust/WASM can beat vanilla JS in DOM manipulation speed.
At this moment in time, fairly doubtful if all that is happening is DOM manipulation. If time is spent in DOM methods, it doesn't matter if JS or wasm calls those DOM methods. At the limit, JS will be a teensy bit faster since there is one less function activation on the stack.
Once host bindings are a thing, then the story changes.
In general, this case isn't that interesting to us, since it is browsers' responsibilities, and we can't really do anything about the perf here regardless if we are writing JS or wasm, other than make fewer DOM calls.
However, if there is time that is spent in JS or wasm, and not in DOM calls, then we can get into some interesting discussion of algorithms and data layout and etc...
All that said, I can take a deeper look into this particular test case tomorrow and give it a once over.
Is this a hard requirement? I know React (and some others) does DOM event delegation, so it only attaches a single event listener, so wouldn't that requirement disqualify React?
Good catch! I don't know why I assumed each .TableCell needed an onclick handler. Turns out one on the container element works fine, although it does feel like cheating a little bit since the same trick could be applied to the Vanilla JS version. Anyway I updated the code to just use 1 event handler and performance is inching closer to Vanilla JS.
However, if there is time that is spent in JS or wasm, and not in DOM calls, then we can get into some interesting discussion of algorithms and data layout and etc...
Well the one thing that gave me hope is that most of the code is just string manipulation, building up a big ball of HTML and setting it with one set_inner_html call, so my (possibly naive) thinking was maybe that could be faster in WASM than JS? Since I would assume all the string functions would stay in Rust-land.
Here are the top results from profiling this page now. Honestly I'm not sure exactly how to read this:
@liamcurry sweet thanks for sharing!
self time is time spent inside of that function (not including all of the functions that this function might call
total time is the time in this function AND all of the functions that it calls.
I took a screenshot of your screenshot, focusing in on the functions that took the most total time:

It looks like at least a few of those are all things that wasm_bindgen is doing. The others are hard to tell without clicking into them further.
So if you click the arrows next to them and keep drilling down you'll find the function(s) that are taking up that total time (they'll have a higher self time.
From there you can look at those functions in wasm_bindgen and see if there are any ways to improve them.
Note that I am not familiar with wasm_bindgen's internals so for all I know things have already been optimized down as far as possible. But regardless at the very least you'll still get to see what's actually going on in there.
Well the one thing that gave me hope is that most of the code is just string manipulation, building up a big ball of HTML and setting it with one set_inner_html call, so my (possibly naive) thinking was maybe that could be faster in WASM than JS? Since I would assume all the string functions would stay in Rust-land.
That's rather unlikely, because when you pass a string from Rust<->JS it has to do a full O(n) copy, and while it's doing the copy it has to transcode every byte from UTF-8 to UTF-16.
Strings in JS are not slow. Maybe Rust strings are a bit faster. But if you pass strings between the Rust<->JS barrier then that completely obliterates any potential Rust speed gains.
Host bindings might make string passing significantly faster, but we'll have to wait and see.
I'm seeing most time spent in browser C++ code during this benchmark:

@Pauan's analysis of strings and passing them over the wasm<-->js abi boundary is spot on as well.
Reading this section of the book can help give a better idea of how to design programs to work well in wasm: https://rustwasm.github.io/book/game-of-life/implementing.html#interfacing-rust-and-javascript
I think we can close this issue now?
That's rather unlikely, because when you pass a string from Rust<->JS it has to do a full O(n) copy, and while it's doing the copy it has to transcode every byte from UTF-8 to UTF-16.
Thanks @Pauan that makes a lot of sense.
So instead of generating a big String of HTML, it sounds like building up elements using web_sys API calls could be faster...but there will still be a lot of string passing to create elements, set attributes, etc.
I will have to update the benchmark to test this out, but it seems like WASM can't beat vanilla JS in this area until host bindings arrive, which is what I expected.
I think we can close this issue now?
Yep. Thanks everyone!
Most helpful comment
Yeah, serializing and deserializing data is relatively expensive. Generally much faster to pass a 32-bit number value (pointer) instead.
At this moment in time, fairly doubtful if all that is happening is DOM manipulation. If time is spent in DOM methods, it doesn't matter if JS or wasm calls those DOM methods. At the limit, JS will be a teensy bit faster since there is one less function activation on the stack.
Once host bindings are a thing, then the story changes.
In general, this case isn't that interesting to us, since it is browsers' responsibilities, and we can't really do anything about the perf here regardless if we are writing JS or wasm, other than make fewer DOM calls.
However, if there is time that is spent in JS or wasm, and not in DOM calls, then we can get into some interesting discussion of algorithms and data layout and etc...
All that said, I can take a deeper look into this particular test case tomorrow and give it a once over.