WASM Performance

697 views
Skip to first unread message

stephen.t....@gmail.com

unread,
Sep 3, 2021, 4:19:07 AM9/3/21
to golang-nuts
Hello,

I have a moderately large Go project that performs fine on my development machine. However when compiled for WASM the performance drops off significantly. I'm hoping someone can help me understand why.

For comparison purposes, on my hardware, the emulator will run uncapped at a 110-120fps when compiled for linux/amd64 but the wasm/js binary runs at maybe 10fps.

This is the project on Github


The only direct module requirement is the parent emulator project. The indirect requirements listed in go.mod aren't being used.

I've only done the barest minimum to get the WASM version working and in fact know very little about WASM. It's more a proof of concept that anything else, but it would be nice to get it performing well.

Regards

Stephen Illingworth

Brian Candler

unread,
Sep 3, 2021, 5:14:48 AM9/3/21
to golang-nuts
Could you explain a bit more about what you're comparing?

- Is the wasm version running in a browser? If so, which one? Or have you got a way to run wasm directly on the host (in which case, what is it)?
- How is the linux/amd64 version running, if it's not talking to a DOM-type environment?  If the native version is still using syscall/js, then how is it doing so?  Or is the native version in a different repo?
- By "the parent emulator project" do you just mean web2600 itself?

Stephen Illingworth

unread,
Sep 3, 2021, 5:41:37 AM9/3/21
to golang-nuts
On Fri, Sep 3, 2021 at 10:15 AM Brian Candler <b.ca...@pobox.com> wrote:
Could you explain a bit more about what you're comparing?

- Is the wasm version running in a browser? If so, which one? Or have you got a way to run wasm directly on the host (in which case, what is it)?

Running it in Firefox (78.13.0esr) and Chromium (92.0.4515.159)
 
- How is the linux/amd64 version running, if it's not talking to a DOM-type environment?  If the native version is still using syscall/js, then how is it doing so?  Or is the native version in a different repo?
- By "the parent emulator project" do you just mean web2600 itself?

Web2600 is using the emulation core of the parent project


The parent project runs on the desktop. It currently uses SDL and OpenGL etc. but it is designed to allow different methods of presentation.

Web2600 is using the core of the emulator (ie. the non-presentation parts) and so doesn't use SDL or OpenGL.

For presentation, the television package in the core emulation allows you to register "PixelRenderers". So Web2600 adds itself as a pixel renderer. The implemented SetPixels(), NewFrame() (etc.) functions will then talk to the DOM as appropriate.

The web version works but is just exceedingly slow by comparison.


Stephen Illingworth

unread,
Sep 3, 2021, 6:20:26 AM9/3/21
to golang-nuts
To follow up on this I should clarify what my questions are:

1) How much of a performance drop (when compared to AMD64 for example) should I expect when compiling to the WASM target?

2) Is there anything obvious I can do to counter any performance drops?

And I suppose this is a non-Go question, but:

3) I know nothing about WASM beyond the bare minimum. How can I profile and understand the compiled WASM binary? Is it possible to use the pprof tool in some way?

Stephen Illingworth

unread,
Sep 4, 2021, 6:12:17 AM9/4/21
to golang-nuts
I don't think I'm going to be able to make any progress with this. I chopped away enough code so that it compiles with TinyGo. It works but it's even slower.

I was hoping to find a way of profiling the WASM code but I see DWARF support for WASM binaries is still a work in progress. https://github.com/golang/go/issues/33503 I don't know enough about WASM to be able to contribute to that issue unfortunately.

Thanks to anyone who looked at this.

Arnaud Delobelle

unread,
Sep 4, 2021, 6:34:08 AM9/4/21
to Stephen Illingworth, golang-nuts
Hi Stephen,

I haven't really looked at your code in detail but one obvious
difference between your version and the original is the rendering
code. Are you certain that the slowness is not confined to your
rendering code (I have no reason to believe it is btw)?

Here is a suggestion. I have had a good experience using a 2d library
called ebiten (https://github.com/hajimehoshi/ebiten). It abstracts
the rendering and can target native or browsers via wasm. In my
experience performance when targeting the browser has been acceptable,
so you could write an implementation of television.PixelRenderer
backed by ebiten. You could then compile both to native and wasm and
see if there is still a big performance difference?

HTH

Arnaud

On Sat, 4 Sept 2021 at 11:12, Stephen Illingworth
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/6bb486eb-7481-4356-94cd-29c365c02416n%40googlegroups.com.

Stephen Illingworth

unread,
Sep 4, 2021, 6:58:54 AM9/4/21
to golang-nuts
I was hoping it was the rendering code that was the problem but I'm almost 100% certain that it isn't. If I just allow the emulation to run without drawing anything to the HTML canvas (and just writing a frame count to the console) the performance is still the same.

Good tip about Ebiten though. I don't think it'll help with my immediate problem but it shouldn't be too difficult to add it as an alternative presentation method. The main reason I opted for SDL was because I was familiar with it. Ebiten might be a good alternative.

Message has been deleted

da...@suarezhouse.net

unread,
Sep 5, 2021, 8:28:44 AM9/5/21
to golang-nuts
I had read an article that may be useful (format was different so may not be the same) -->  https://github.com/diekmann/wasm-fizzbuzz  (goes from basic WASM to Doom)

The key thing in the Doom port that I recall was needed was to change the perspective of the owning thread (now the browser) so needed to ensure it was never blocked/ responded quickly.  When you read through you may find your answer or something that gives you an idea to start searching through in your code.  

Hope it helps, David

Stephen Illingworth

unread,
Sep 5, 2021, 10:13:32 AM9/5/21
to golang-nuts
Thanks for that, it was interesting reading. The problem he was describing in the Doom case seems to be have been caused by the WASM program taking up all the CPU time, meaning the browser itself is unresponsive. I've solved that by using a Web Worker. From what I understand requestAnimationFrame() is a different solution to the same problem. Somebody correct me if I'm wrong.

What is interesting though is the profile differences between his Doom port and my 2600 emulator. The top image is from the Doom port:


And this is from Web2600, over a similar time period:


We can see a lot more gaps in the second case than the first, which would account for the performance drop I think.

Does this bring me closer to a solution?

On Sunday, 5 September 2021 at 13:28:44

Stephen Illingworth

unread,
Sep 5, 2021, 10:18:02 AM9/5/21
to golang-nuts

ma...@eliasnaur.com

unread,
Sep 7, 2021, 4:31:41 AM9/7/21
to golang-nuts
In my experience (Gio projects), WASM is very slow compared to native code; my investigations are part of #32591. You may find https://github.com/golang/go/issues/32591#issuecomment-517835565 relevant, because I cut out rendering to eliminate the JS<=>Go crossing overhead. It was a ~10x difference in 2019 and I don't think anything major has changed since then.

Elias

Stephen Illingworth

unread,
Sep 7, 2021, 8:34:38 AM9/7/21
to golang-nuts
Yes. I'm seeing a 10x difference in speed too. So at least I know I'm not doing anything fundamentally wrong. It's a general problem at the moment.

Thanks.

David Suarez

unread,
Sep 7, 2021, 10:47:42 PM9/7/21
to Stephen Illingworth, golang-nuts
Did moving rendering to the browser side (just have the other side prep the data to be rendered) solve for the difference?  how much?  Did he do the same in the Doom article to get it to OK?
--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/N10hzvkDA1A/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/6893845f-894b-4546-bb3e-f811e61c01ben%40googlegroups.com.

Sincerely,

David Suarez

Gallup Strengths Finder:  Achiever * Strategic * Relator * Ideation * Learner

https://www.linkedin.com/in/davidjsuarez/ 

David Suarez

unread,
Sep 7, 2021, 10:50:54 PM9/7/21
to Stephen Illingworth, golang-nuts
reading emails in reverse, apologies, may be similar to what I just sent.  Is it easy enough to try the request animation frame approach?  may help rule out an error in web worker code or approach if you emulate his flow to start, then start optimizing back to web worker if it solves to avoid whatever may have caused the delta.  Just a thought
--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/N10hzvkDA1A/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/aa9b40d9-b154-48dd-bb52-62f139e3bceen%40googlegroups.com.
blob.png
blob.png

Stephen Illingworth

unread,
Sep 9, 2021, 4:19:21 AM9/9/21
to golang-nuts
Even when I remove all rendering and just run the Go WASM in a web worker, like this:

    const go = new Go();
    WebAssembly.instantiateStreaming(fetch("web2600.wasm"), go.importObject).then((result) => {
        go.run(result.instance);
    });

The binary is still performing a long way below native performance. I'm writing to the web console with println() to indicate progress but apart from that there is no communication between Go and the browser.

I haven't looked into RequestAnimationFrames yet but I did try the Ebiten option. The profile looks similar to the Doom profile (with RequestAnimationFrames) but even then the emulation is running slower than native.

Worth emphasising that in the case of my Ebiten version, exactly the same Go code is being compiled for native CPU and WASM.

My next step (which won't be for a few days at least) is to learn a bit more about WebAssembly and to take a look at the code that's being generated.
Reply all
Reply to author
Forward
0 new messages