[HN Gopher] Snappy UIs with WebAssembly and Web Workers
       ___________________________________________________________________
        
       Snappy UIs with WebAssembly and Web Workers
        
       Author : jaflo
       Score  : 113 points
       Date   : 2023-08-07 14:45 UTC (8 hours ago)
        
 (HTM) web link (mofi.loud.red)
 (TXT) w3m dump (mofi.loud.red)
        
       | eole666 wrote:
       | Interesting article, but it's lacking a performance comparison
       | between wasm and javascript running in workers.
       | 
       | JS can be largely fast enough for a lot of use cases: if I had
       | some WebAssembly to a project I need a good reason to do so.
        
         | postalrat wrote:
         | https://takahirox.github.io/WebAssembly-benchmark/
         | 
         | JS can be faster in enough cases where it's worthwhile to test.
         | 
         | WASM is mostly being used for code that has already been
         | written and is now being integrated into a web site. I wouldn't
         | suggest jumping right to WASM simply for performance.
        
       | afavour wrote:
       | I'm fascinated by WebAssembly and love that it exists but if
       | anyone tells you they need to use WebAssembly to make the UI
       | snappy I'd advise you interrogate that assertion thoroughly.
       | 
       | I don't want to speak to this example too deeply because I don't
       | know it (I see they're doing all sorts of stuff with audio so
       | maybe they do need WebAssembly) but modern JavaScript VMs are
       | very, very fast. 99% of webapps are absolutely fine using
       | JavaScript.
       | 
       | The far more important part of making snappy UIs is the Web
       | Worker aspect. To my mind it's one of the key reasons native apps
       | feel so much better than web ones: it's trivial to move an
       | operation off the main thread, do an expensive calculation and
       | then trivial to bring it back to the main thread again.
       | Unfortuately the API for doing so on the web is extremely clunky,
       | passing messages back and forth between a page context and a
       | worker context. I'd recommend anyone thinking about this stuff to
       | take a look at Comlink:
       | 
       | https://www.npmjs.com/package/comlink
       | 
       | it lets you wrap a lot of this complication up in simple
       | promises, though you still have to think hard about what code
       | lives inside a worker and what does not. In my ideal world all
       | the code would live in the same place and we'd be freely
       | exchanging stuff like you can in Swift. "Don't do it on the main
       | thread" feels like a mantra taught to every new native developer
       | as soon as it can be, the same discipline simply doesn't exist on
       | the web. But neither do the required higher level language
       | features/APIs.
        
         | eyelidlessness wrote:
         | I think they're vastly understating/oversimplifying _how_ they
         | use WASM. For audio analysis and operations on audio binary
         | /encoded data, it's quite possible that WASM is a very good
         | fit. _Maybe_ offloading that work to a JS worker would be
         | sufficient--you're right that JS VMs are extremely fast--but I
         | wouldn't necessarily discount this WASM use case either. And
         | I'm saying that having explored these kinds of optimizations
         | enough to _generally recommend against using WASM_ unless you
         | have strong reason to believe you'd benefit from it (ideally
         | with benchmarks to prove it).
         | 
         | I'd also caution that using Web Workers isn't always so obvious
         | either (and the same applies to server side threading eg on
         | Node). There's significant overhead (runtime) in spinning up a
         | worker, and in every communication between threads--enough to
         | negate their benefit for many use cases. Both WASM and workers
         | have roughly the same perf downsides, and both should generally
         | be justified by real measurement of their impact.
        
         | [deleted]
        
         | ch_sm wrote:
         | Those are good recommendations, and they confirm my own
         | findings and experience trying to improve performance for
         | Spectrogram and Waveform generation in a heavy audio focused
         | web app.
         | 
         | AssemblyScript and Rust/WASM implementation of these relatively
         | simple but computationally heavy algorithms didn't result in
         | any meaningful improvements of their JS counterparts. In the
         | end moving computations that took 10ms or more (some up to
         | 700ms) off main thread and using tooling to simplify the
         | WebWorker API was definitely more gainful, simpler and better
         | for code hygiene, and in most cases as fast or faster than the
         | WASM implementations.
        
         | ramesh31 wrote:
         | >I'm fascinated by WebAssembly and love that it exists but if
         | anyone tells you they need to use WebAssembly to make the UI
         | snappy I'd advise you interrogate that assertion thoroughly.
         | 
         | The Figma team would say differently. For 99% of CRUD app use
         | cases you are absolutely correct. But WASM has enabled
         | functionality on the web we could have only dreamed of 5 years
         | ago.
        
           | afavour wrote:
           | I'd argue that's part of the interrogation. "We should use
           | WebAssembly because Figma does and Figma is fast" is faulty
           | logic. Are _you_ making a product with the level of
           | complexity of Figma? OK but are you _really_? Are you _sure_
           | WebAssembly is what makes Figma fast? If so, what are they
           | using WebAssembly for? Is there an overlap with what you 're
           | doing?
           | 
           | > WASM has enabled functionality on the web we could have
           | only dreamed of 5 years ago
           | 
           | Like what? I ask the question genuinely. WebAssembly makes it
           | dramatically easier to do a lot of performance-sensitive
           | things but to my mind it doesn't actually enable a whole lot
           | of new functionality. I don't mean to write off making things
           | easier, it's a huge deal. But if anyone asserts that we
           | absolutely _must_ use WebAssembly for Project X I 'd really
           | want to dive into exactly why. "It's fast" is not a good
           | enough answer.
        
             | kettlecorn wrote:
             | WebAssembly doesn't provide any new functionality much in
             | the same way cars did not when compared to horse-drawn
             | carriages. You can argue that everything you can do with
             | Wasm _can_ be done with JavaScript, but often it involves
             | significantly greater effort.
             | 
             | Where Wasm is a massive improvement:
             | 
             | * Ability to use existing C++/C/Rust code without a
             | rewrite.
             | 
             | * Performance _consistency_ through languages with manual
             | memory-management and more straightforward performance
             | characteristics.
             | 
             | * Performance of working with multithreaded code by using
             | languages that can pass pointers and avoid message-passing
             | overhead.
             | 
             | * This last point is somewhat unproven as it's related to
             | my own personal work, so take it with a grain of salt! Wasm
             | has unique properties that allow it to be augmented to run
             | complex, seemingly unnetworked, code perfectly synchronized
             | between computers with very little latency in the UX. I'm
             | convinced it can eliminate a whole class of complex
             | networking / sync issues. I first demonstrated the concept
             | earlier this year with tanglesync.com, but I'm currently
             | working on a follow-up.
        
               | pjmlp wrote:
               | Already offered by PNaCL, and CrossBridge, in 2010, in
               | what concerns C and C++.
               | 
               | https://adobe-flash.github.io/crossbridge/
               | 
               | Still waiting for those wonderful WebAssembly + WebGL
               | games that can match Infinity Blade for iOS from 2010,
               | the game Apple used to show off iOS GL ES 3.0
               | capabilities.
               | 
               | Or something better that citadel demo, beyond the asm.js
               | port done by Mozzilla .
        
           | k__ wrote:
           | I've used quite some graphic editing web apps, and I wouldn't
           | count Figma to the snappy ones.
           | 
           | It's a bit like the Slack of graphic software.
        
           | jauntywundrkind wrote:
           | It's an impressive app but I as the parent said, I'd expect
           | JS would have worked just fine & let you do all the same
           | things, at essentially the same speed.
           | 
           | The key differentiation, as the parent says, is using Web
           | Workers to make sure you're not doing work on the main
           | thread.
        
             | karaterobot wrote:
             | > I'd expect JS would have worked just fine & let you do
             | all the same things, at essentially the same speed
             | 
             | I think you would be wrong about that. Certainly, it's not
             | just WASM that makes Figma fast, but it's an important
             | piece. Look at it this way: why would they have built out
             | the product with WASM five years ago if Javascript was just
             | as good for their use case? It's a huge investment in a new
             | technology with relatively few people you can hire for it,
             | versus quite possibly the most well-known, well-supported
             | language in the world. They identified some significant
             | advantage that made the cost-benefit analysis line up.
             | 
             | Normally, I don't like the argument that because somebody
             | did it, it must be the right choice. But, Figma made so
             | many correct decisions out of the gate that, in this case,
             | I'd give them the benefit of the doubt.
        
               | afavour wrote:
               | > They identified some significant advantage that made
               | the cost-benefit analysis line up.
               | 
               | No doubt. But we don't know what that advantage was. You
               | could assume it was for performance related reasons. But
               | maybe they wanted cross-platform compatibility, something
               | WASM allows far more than JS. Or maybe they hired a load
               | of developers experienced in making a graphic design tool
               | rather than making a webapp and decided to use WASM to
               | provide those developers with a more familiar
               | environment.
               | 
               | Anyway, without knowing any of this it's very difficult
               | to say "Figma did it so we should too".
        
               | crabmusket wrote:
               | Figma were already compiling C++ to asm.js, and then
               | switched over to WASM.
               | 
               | https://www.figma.com/blog/webassembly-cut-figmas-load-
               | time-...
        
               | treis wrote:
               | > They identified some significant advantage that made
               | the cost-benefit analysis line up.
               | 
               | I don't know if this is true or not but I don't rate the
               | fact that they used it as much evidence that they should
               | have used it. IME decisions like this are because some
               | early engineer wanted to use that particular bit of
               | technology.
        
           | kevingadd wrote:
           | asm.js was capable of most of the same stuff, wasm just takes
           | it further (with simd and native int64)
        
           | PrimeMcFly wrote:
           | > But WASM has enabled functionality on the web we could have
           | only dreamed of 5 years ago.
           | 
           | Like what?
        
         | jcelerier wrote:
         | > 99% of webapps are absolutely fine using JavaScript.
         | 
         | as a user, much less than 99% of apps are "absolutely fine" to
         | use.
        
           | renegade-otter wrote:
           | This is not a Javascript problem, this is a Javascript
           | ecosystem problem. A standard web page will load megabytes of
           | JS code that was packaged from hundreds of dependancies for
           | the reason that "this is how everyone does it".
           | 
           | I just finished a UI that requires a few HTTP requests to the
           | API and a bit of dynamic behavior (but not a _ton_ ), and
           | it's done inline with ES6, with no transpilers or minifiers,
           | using ArrowJS. It does the job, and it rips.
        
           | jayd16 wrote:
           | Having them in wasm won't always improve them so this is a
           | bit of a statistical fallacy.
        
           | afavour wrote:
           | You are arguing a very separate point, one that I tackle in
           | the next paragraph.
        
         | [deleted]
        
         | kettlecorn wrote:
         | > Unfortuately the API for doing so on the web is extremely
         | clunky, passing messages back and forth between a page context
         | and a worker context.
         | 
         | This is one of the advantages to using WebAssembly: you can use
         | a language like Rust that makes multithreaded code easier to
         | write and faster to run.
         | 
         | Out of the box with JavaScript if you want to share an object
         | with a worker you need to send a message (which internally
         | serializes / deserializes the object) or manually manage
         | serializing / deserializing bytes to a SharedArrayBuffer.
         | 
         | With Rust/Wasm + Web Workers you can use Rust's synchronization
         | primitives and just pass a pointer to the worker. You pay no
         | cost for serialization / deserialization.
         | 
         | > if anyone tells you they need to use WebAssembly to make the
         | UI snappy I'd advise you interrogate that assertion thoroughly.
         | 
         | As you pointed out the JavaScript VM is incredibly optimized
         | but where Wasm shines is _consistency_. JavaScript requires
         | some care to not produce garbage every frame, otherwise you may
         | unpredictably trigger a lengthy garbage collection. With most
         | Wasm languages you can easily avoid allocating any JavaScript
         | garbage.
         | 
         | The lower-level Wasm languages also _tend_ to be easier to
         | reason about performance. When assessing Rust code performance
         | you typically want to look for Big-O and excessive memory
         | allocations. If you do find a performance bottleneck typically
         | you know where to try to improve. With JavaScript you 'll
         | sometimes hit situations where you fall off the VM's golden
         | path, or where one JavaScript VM performs fine and another does
         | not.
        
         | rapnie wrote:
         | > if anyone tells you they need to use WebAssembly to make the
         | UI snappy I'd advise you interrogate that assertion thoroughly.
         | 
         | Get prepared to be blown away by Makepad [0]. I have no
         | affiliation with them, but just watched their most recent
         | conference presentation [1]. The slides were made with Makepad
         | itself and included, embedded, a full-blown IDE, a synthesizer
         | app, a Mandelbrot to zoom in endlessly, and more. All running
         | at 120fps. The presentation is for the most part live-coding
         | with this setup.
         | 
         | What they want to do is bring coders and designers closer
         | together, and while some code is in Rust they developed a DSL
         | for the GUI parts that is close to how Figma works. These GUI's
         | can run anywhere.
         | 
         | And I couldn't help thinking "Why would people have complicated
         | stacks to create Web 2.0 apps for the Google Web, when they
         | have this?", in other words an opportunity to break out of the
         | browser straitjacket.
         | 
         | Btw. WebAssembly/WebGL isn't the only way in which Makepad is
         | available. And while running well in the browser for a time,
         | there were issues to be solved here (addressed in the
         | presentation). And tbh this isn't a real answer to your
         | assertion. Greg Johnston, creator of Leptos, has made a video
         | with performance comparisons [2].
         | 
         | Edit: Adding a link to the synthesizer app I just found [3].
         | 
         | [0] https://github.com/makepad/makepad
         | 
         | [1] https://www.youtube.com/watch?v=rC4FCS-oMpg
         | 
         | [2] https://www.youtube.com/watch?v=4KtotxNAwME
         | 
         | [3] https://makepad.nl/makepad/examples/ironfish/src/index.html
        
           | derefr wrote:
           | > And I couldn't help thinking "Why would people have
           | complicated stacks to create Web 2.0 apps for the Google Web,
           | when they have this?", in other words an opportunity to break
           | out of the browser straitjacket.
           | 
           | Perhaps because they still believe in the promise of Web 1.0,
           | where their app is a graceful-enhancement over an initial
           | server-rendered _document_ , that can be easily worked with
           | at a DOM level by any HTML scraper, easily baked to a PDF and
           | printed, easily re-laid-out for better readability just by
           | changing the browser font size, easily text-to-speech'ed
           | (including ARIA roles, alt text, etc), easily re-styled with
           | a user-agent stylesheet, easily intermediated by browser
           | extensions, and so forth.
           | 
           | I've yet to see a WASM-driven web application that's any less
           | opaque to these technologies than a Flash or ActiveX applet
           | would be.
        
             | rapnie wrote:
             | All fair points. But there's no reason that these things,
             | like ARIA standard, aren't added. That is independent of
             | WebAssembly standard. I did not mention "Google Web" for no
             | reason. The "promise of Web 1.0" in terms of being open is
             | near dead with Google DRM's and the browser oligopoly and
             | all that jazz.
             | 
             | Also mentioned Web 2.0 for a reason. The "cram full-blown
             | app into browsers" web, that uses Rube Goldberg machines
             | behind the scenes. Joking aside, these (btw, also opaque)
             | dynamic applications can hugely benefit from a new
             | paradigm. While the Web itself can go back more to its
             | original 1.0 roots of hypermedia, and allowing more and
             | simpler browsers to wield its content.
        
           | evilduck wrote:
           | Rendering your UI entirely in a canvas tag isn't a new idea.
           | If you have ADA compliance requirements or just want to build
           | E2E tests, good luck.
        
             | rapnie wrote:
             | Saying "Oh, canvas tag. Been there, done that" doesn't do
             | this project justice. It is not new ideas that matter here.
             | It is the execution of them into workable solutions.
        
           | afavour wrote:
           | That demonstrates that you _can_ use WebAssembly to make a
           | snappy UI, it does not demonstrate that you _must_ use
           | WebAssembly.
           | 
           | It's nearly ten years old now but I remember being absolutely
           | blown away by React Canvas, a UI toolkit that leveraged React
           | but instead of rendering DOM nodes it rendered to a <canvas/>
           | tag. Beautiful, 60fps stuff. All written in JS. Unfortunately
           | all the demos seem to have disappeared since but here's a
           | blog post about it:
           | 
           | https://engineering.flipboard.com/2015/02/mobile-web
           | 
           | Point is, both Makepad and React Canvas have something in
           | common: they ditched the DOM. There are both advantages and
           | disadvantages to doing so but the relevant point is that you
           | don't need to use WebAssembly to do it.
        
             | rapnie wrote:
             | Fair point. I alluded to what you are saying in my last
             | sentence, and agree.
        
             | bob1029 wrote:
             | > they ditched the DOM
             | 
             | I strongly recommend NOT doing this. It sounds like a
             | fantastic idea in theory (who _wouldn 't_ want pixel-
             | perfect control over all client viewports?), but there are
             | so many caveats and edge cases that make it a nightmare in
             | practice. You don't even have to get into accessibility or
             | internationalization stuff to find the kinds of sharp edges
             | that scared me away. Simple things like HiDPI, resizing
             | viewports and drawing text.
             | 
             | I spent a solid 3-4 months of time on this path. I was so
             | tired of fighting platform/browser quirks that it made
             | sense. Now, I accept those quirks as battle-tested
             | _features_ and don 't try to fight them anymore.
             | 
             | The only reason you should actually ditch the DOM is if you
             | are doing something like Overwatch in the browser. Even
             | then, I'd argue you would be a complete dumbass if you skip
             | out on the power of CSS, etc. for purposes of handling your
             | Menu/HUD elements.
        
         | wim wrote:
         | We're building an "IDE for notes/tasks" [1], so as an editor of
         | sorts, UI snappiness matters a lot for us too. The approach
         | we're taking is to basically split up the app in two parts (we
         | refer to these parts as "frontend" and "backend", but they are
         | both on the client).
         | 
         | The frontend does all the rendering for the editor, which we
         | want to stay within the frame budget. That's why we offload all
         | data synchronization work (applying CRDT deltas,
         | encrypting/decrypting data to/from websockets, IndexedDB
         | caching, search, parsing JSON and so on) to the "backend"
         | thread.
         | 
         | I think for apps like this, splitting the UI and data part up
         | (kind of like a frontend and a backend in a classic
         | client/server web app) is very useful to prevent blocking the
         | main thread. In our case this "backend" is actually a
         | SharedWorker, which has the added benefit that it's very easy
         | to keep all state in sync with multiple open tabs/windows, and
         | we just multiplex all incoming websocket events to all
         | connected "frontends".
         | 
         | I agree passing messages is a bit clunky, but we've taken a
         | similar approach to comlink so we can simply "await" a function
         | call on the backend from the frontend. Besides setting that up
         | once (or just using something like comlink), it's not much
         | work. Okay except I found debugging on Chrome a bit clunky, as
         | I think Firefox allows you to see all SharedWorker console
         | output from within the main thread console for example (with
         | Chrome you can open the inspector for shared workers separely).
         | Plus we also have all kinds of other neat features with modern
         | browsers these days, like the SharedWorker, and zero-copy
         | communication using Transferable objects.
         | 
         | [1] https://thymer.com/
        
         | [deleted]
        
       | nwoli wrote:
       | With all this hype around full gpu UIs it rarely mentions the
       | high latency around that. I'm much more excited about wasm making
       | things snappier
        
       | mattlondon wrote:
       | > runs at speeds you would not be able to achieve with just
       | JavaScript
       | 
       | Is that true? Last I heard wasm was significantly slower than
       | vanilla JavaScript in synthetic benchmarks.
       | 
       | Has that changed recently?
        
         | tamimio wrote:
         | I'm interested too to see those benchmarks
        
       | brundolf wrote:
       | Even just JavaScript Web Workers can be really helpful for doing
       | heavy compute outside of the UI thread. JavaScript is pretty
       | fast, it just needs to be unblocked. I used them once to sort a
       | six-digit array of objects client side, while keeping the UI
       | snappy (it took a couple seconds to process, but the UI was
       | responsive the whole time)
       | 
       | Of course for some tasks you'll still need more than that, which
       | very well may have been true for the OP, but benchmarking is good
       | etc
        
       | samwillis wrote:
       | WASM and Web Workers - unless carefully used - won't magically
       | make your UI snappy.
       | 
       | There are three reasons (for the vast majority of apps) that a UI
       | feels sluggish:
       | 
       | 1. The network! Requesting data from a server is slow, by far the
       | slowest aspect of any app. As a start, prefetch and cache, use a
       | CDN, try edge platforms that move data and compute closer to the
       | user. However, if you can explore Local First
       | (http://localfirstweb.dev), for a web _app_ it is the way we
       | should be looking to build in future.
       | 
       | 2. They are doing work on the UI thread that takes longer than
       | 16ms. This is where Web Workers are perfect, the DX around them
       | isn't perfect, as another comment suggested Comlink helps, but
       | there is a lot of opportunity here to build abstractions that
       | help devs.
       | 
       | 3. Excessive animations that delay user interaction - there is so
       | much bad UX where animations have been added that only make
       | things slower. Good animations have a purpose, showing where
       | something came from or is going, and never get in the way.
       | 
       | Finally, we are well into diminishing returns with front end
       | frameworks optimising the way they do templating and update the
       | DOM. The key thing there now is DX, that is how you pick a
       | framework, benchmarks are almost always useless.
        
         | KRAKRISMOTT wrote:
         | > _Good animations have a purpose, showing where something came
         | from or is going, and never get in the way._
         | 
         | Good animations also help reduce _perceived_ delays
        
         | jalk wrote:
         | > Good animations have a purpose, showing where something came
         | from or is going
         | 
         | Many websites will slowly slide a newsletter sign up form up
         | from the bottom. We already know that it comes from the
         | underworld, so the animation absolutely pointless there.
        
       | austin-cheney wrote:
       | > Running Fast(er) With WebAssembly
       | 
       | How much faster?
       | 
       | I have to ask because people frequently make claims about perform
       | based upon unmeasured assumptions that are wrong more often than
       | not. Worse than that, many of these imagined claims about
       | performance tend to be off by one or more orders of magnitude.
        
         | whoomp12342 wrote:
         | I love the idea of blazor, but I have seen first hand that it
         | can be slows as balls. But that might not be WASMS fault, it
         | could be .net
        
       | amelius wrote:
       | Can we have structural sharing between web workers please?
       | Because serializing everything as a message stream is not the
       | most efficient way to go about many things. Also not the most
       | programmer-friendly.
        
         | titzer wrote:
         | You can share Wasm memories between workers. It's clunky and
         | low-level, but it's as fast as hardware can be, with no
         | serialization steps.
        
       | fenomas wrote:
       | Web Workers are a really great feature for performance. The way
       | they want the worker code to live in a separate file makes them
       | slightly annoying if you're using a bundler, but each bundler has
       | a loader or similar feature for this, so all is mostly well.
       | 
       | But the thing I haven't found a solution for is, the case where
       | you want to use web workers inside a library that other people
       | will be importing into their own project, and you don't know what
       | bundler they'll use (or transpiler, minifier, etc). I can think
       | of hairy ways to do it, that involve pre-building the worker JS
       | and storing that text in your library, piping it into a file blob
       | at runtime, or the like. But does anyone know a clean way of
       | handling this?
        
         | sickill wrote:
         | I've been looking for an answer for this exact problem as well.
         | There doesn't seem to be anything out there that doesn't
         | involve some awkward hackery...
        
       | kevingadd wrote:
       | I would caution anyone looking to make their UI "snappy": while
       | webassembly performance for things like raw compute - especially
       | with simd - is superior, any time you need to interface with
       | browser APIs like the DOM to update your UI you're now going to
       | pay a bunch of interop costs that aren't present in native
       | JavaScript. For some workloads this will make wasm meaningfully
       | slower - especially ones that use strings.
        
         | eyelidlessness wrote:
         | Or really just anything "chatty" with the main thread. Doesn't
         | have to be strings, or DOM, just heavy cross-VM/cross-thread
         | interaction.
        
       | logankeenan wrote:
       | I have experimented with something similar by embedding an entire
       | Rust Axum server in a service worker.
       | 
       | I wrote a blog about it recently.
       | 
       | https://logankeenan.com/posts/client-side-server-with-rust-a...
        
       ___________________________________________________________________
       (page generated 2023-08-07 23:01 UTC)