Skip to content
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

Fixing wasm-bindgen benchmark #589

Merged
merged 1 commit into from
Jun 28, 2019
Merged

Conversation

Pauan
Copy link
Contributor

@Pauan Pauan commented Jun 24, 2019

No description provided.

@krausest krausest merged commit b625b02 into krausest:master Jun 28, 2019
@krausest
Copy link
Owner

Thanks. Results have been updated.
I changed package.json script to match the README.

@Pauan Pauan deleted the fixing-wasm-bindgen branch June 29, 2019 02:06
@deklanw
Copy link

deklanw commented Jul 15, 2019

I'm surprised this implementation is slightly slower than the VanillaJS.

@Pauan do you have any insight into why that may be? It seems to me that theoretically this should be faster. Am I wrong?

@Pauan
Copy link
Contributor Author

Pauan commented Jul 15, 2019

@deklanw No, it is expected that it should be slower.

There is a lot of overhead when communicating between Wasm<->JS. Some of that overhead will be fixed in the near future, but some of it is unavoidable.

As an example of overhead, wasm-bindgen creates a simple heap in JS. Whenever you send a JS object to Rust, it stores that JS object in the JS heap, and then sends the index to Rust.

This is necessary because Wasm cannot access JS objects, it can only access integers. So we have to use a JS heap to "convert" a JS object into an integer index.

Then whenever Rust needs to do something to that object (call a method, call a function, etc.) it has to send that index integer back to JS, then JS looks up the object in the heap, and then calls the method.

And finally, when Rust is done using the JS object, it then calls a special JS function which removes the JS object from the heap, which then allows the JS engine to garbage collect the object.

As you can imagine, this adds some extra cost every time you do anything involving JS objects (including the DOM). The cost is extremely small, but when you're dealing with thousands of objects it adds up.

As a more serious example of overhead, in the case of strings it has to do a full O(n) copy and encoding from UTF-8 to UTF-16 every single time a string is sent from Rust to JS (or from JS to Rust). This cost is significant.

Wasm is much faster than JS as long as you stay entirely within Wasm. But the more that you communicate between Wasm and JS, the slower it will be. So a web app where 99+% of the time is spent calling web APIs will of course be slower than pure JS.

However, it is not hopeless, because I am working on a pull request which adds in string interning to wasm-bindgen. That means it can completely avoid the O(n) copy and encoding of strings in many cases.

And soon browsers will have support for anyref, which allows Wasm to access JS objects directly (and thus we don't need a JS heap anymore).

And within the next couple years we should get WebIDL bindings, which will allow Wasm to call web APIs (such as the DOM) directly, without needing to use JS at all. Because it is statically checked and doesn't go through JS, this should allow Wasm to call web APIs faster than JS.

I'm actually quite impressed that wasm-bindgen manages to get almost the same performance as vanilla JS, even though it has a lot of Wasm<->JS overhead and string copying + encoding.

@deklanw
Copy link

deklanw commented Jul 15, 2019

@Pauan Ah, that explains it. Thanks. I wasn't even sure if this was calling the web APIs directly or not. That it isn't explains a lot.

Once those direct bindings are available Rust can take potentially take over the frontend >:)

Seriously though, how do you see Rust + WASM's current role on the frontend? Best used for intensive calculations? Worth using now for other stuff? Something else?

@Pauan
Copy link
Contributor Author

Pauan commented Jul 15, 2019

Once those direct bindings are available Rust can take potentially take over the frontend >:)

Well, there's not much stopping it from taking over the frontend right now.

Sure the performance isn't as good as JS when calling web APIs, but it's much better whenever you're not calling web APIs. Overall the performance is still very good (and will only get better over time).

And you get to use an excellent language with static typing, nice features, good documentation, good tooling, etc. Overall using Rust is a lot nicer than JS.

Plus, if your server is written in Rust (which it should be), then sharing Rust code between the client and server makes a lot of sense.

Seriously though, how do you see Rust + WASM's current role on the frontend? Best used for intensive calculations? Worth using now for other stuff? Something else?

Of course Rust will be best at things which are self-contained, like encoders/decoders, CPU crunching algorithms, cryptographic algorithms, etc.

But it still works quite well in areas where it wasn't intended. For example, I'm currently using Rust + Wasm to create Firefox / Chrome extensions (which are normally written in JS).

I'm quite happy with the end result: it's far more maintainable than using JS, and the performance is generally very good: unlike js-framework-benchmark, real apps aren't spending 99+% of their time calling web APIs, so you get to take advantage of the speed benefits of Wasm.

For example, in one of my extensions it was using the standard JS JSON.parse and JSON.stringify, but they were taking several seconds to parse some large JSON. I switched to serde_json and now it takes just a couple hundred milliseconds.

I'm also using Rust + Wasm for a web game I'm working on. So it can be used for pretty much anything. I personally use Rust for all my personal projects, I see no reason to switch back to JS.

@deklanw
Copy link

deklanw commented Jul 16, 2019

Thanks for the involved responses!

Plus, if your server is written in Rust (which it should be), then sharing Rust code between the client and server makes a lot of sense.

For my new toy project I'm using Svelte on the frontend and actix-web on the backend. When the Rustaceans conquer I'll be ready :p

I must ask, though, what code sharing do you find to be useful? I've heard this argument before for various stacks but when I think about it I can't think of much. Form validation rules, maybe? GraphQL et al cover typing APIs.

I'm still not convinced about using a lot of Rust for web dev, front or backend. It seems like the mental and syntactical overhead of avoiding GC might not be worth it. It certainly seems to me that few JS developers will convert. Most frontend sites I can think of are usually not going to be CPU-bound.

Are there any Rust frameworks in the works which give you some nice higher-level abstractions, terse code? I'm genuinely interested in trying some out.

And you get to use an excellent language with static typing, nice features, good documentation, good tooling, etc. Overall using Rust is a lot nicer than JS.

To be fair, there are other languages that do this already with a similar or better type system (without needing to think about memory management). Elm, ReasonML, PureScript. Hell, even TypeScript is pretty good on strict mode. Elm/TypeScript have very good performance options, ReasonML you have React bindings with its performance, and PureScript is........ not concerned with performance.

real apps aren't spending 99+% of their time calling web APIs, so you get to take advantage of the speed benefits of Wasm.

What are the most common apps which would noticeably benefit from Rust on the frontend, but which aren't making heavy amounts of DOM API calls?

@Pauan
Copy link
Contributor Author

Pauan commented Jul 16, 2019

I must ask, though, what code sharing do you find to be useful? I've heard this argument before for various stacks but when I think about it I can't think of much.

What I use it for is sharing serialization. All you have to do is slap a #[derive(Serialize, Deserialize)] and you can effortlessly share data between the client and server, and it's guaranteed that the serialization format will be the same.

And any methods defined on those types can often be shared between client and server as well.

Also, libraries can be shared, tools can be shared, documentation can be shared, your mental model can be shared, and any helper utilities you create can be shared.

I haven't done much server-wise, so that's all I can say about that.

It seems like the mental and syntactical overhead of avoiding GC might not be worth it. It certainly seems to me that few JS developers will convert.

It's not actually a big deal. You just create a top-level State struct and then wrap it in Rc<RefCell<State>> and you're good to go. Once you learn a few tricks it becomes quite easy.

I agree that few JS developers will convert, but that's fine, they don't need to.

To be fair, there are other languages that do this already with a similar or better type system (without needing to think about memory management).

I had looked at essentially every compile-to-web language (all of those you listed and a lot more), and none of them were viable (for my desires), until I found Rust.

I'm probably the only person in the world who desperately looked for a compile-to-web language before deciding that obviously the best option would be a systems programming language without a GC. Obviously.

Even though I had never used a systems programming language before, and even though every language I had used before had a GC. As you can see, I'm very good at decision making.

I even started learning Rust back when wasm32-unknown-unknown was incredibly unstable, barely worked at all, and broke all the time, but I still put up with it. That's how much I like Rust.

Hell, even TypeScript is pretty good on strict mode.

Yeah, I use TypeScript at work. It's... better than JS, but not very good. Too many quirks, unsound type system, lack of useful features (typeclasses, nice ADTs, etc.), poor syntax (inherited from JS).

PureScript is........ not concerned with performance.

I used to use PureScript, then I switched to Rust because PureScript is indeed very slow (understatement), with no intention of improving.

What are the most common apps which would noticeably benefit from Rust on the frontend, but which aren't making heavy amounts of DOM API calls?

Well, I'm not the person to ask that, since I would say "rewrite All The Things in Rust".

But I think a better way of looking at it is this: you can seamlessly blend Rust and JS together in the same code base.

So you can put things like business logic in Rust, and put the DOM code in JS (using whatever framework you want). Then the JS code can call Rust functions and vice versa.

So you can surgically replace some hot JS functions with Rust code without rewriting everything in Rust. I think that's going to be a big selling point of Rust: the ability to replace bits and pieces of code piecemeal with Rust, while keeping the rest of the app the same.

The reason why Rust specifically is so good at this is because other languages have a GC and runtime. Adding a whole GC and runtime just to improve the efficiency of a hot function isn't great. And you also have to deal with things like GC cycles between JS and the other language.

Rust doesn't have any of those problems, since it doesn't have a GC or runtime, so it's very lightweight and has no GC issues when interacting with JS. This makes Rust the ideal language for embedding within an app.

Are there any Rust frameworks in the works which give you some nice higher-level abstractions, terse code? I'm genuinely interested in trying some out.

I know of a few, but since I'm biased I'm going to shill my own framework dominator. It has a full featured TodoMVC example (or the much simpler counter example) and I'm using it for Real Things(tm).

We should probably move this discussion over there, so we don't clutter up this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants