11 ms·
Migrating a JavaScript Library from JavaScript to WebAssembly
- gigel82 6y agoThis is a very nice and thorough write-up. Frankly, I would've dropped IE support altogether, that browser has been dead a long long time. No need to futz with gzip and base64, just fetch the wasm, engines optimize for the common path.
- devlopr 6y agoThat's like dropping support for firefox or edge or safari Chrome 69.28% Edge 7.75% Firefox 7.48% Internet Explorer 5.21% Safari 3.73% QQ 1.96% Sogou Explorer 1.73% Opera 1.12% Yandex 0.90% UC Browser 0.37%
- hombre_fatal 6y agoIE isn't even in the top 6 in any of the regions you can click on this website: https://gs.statcounter.com/browser-market-share/all/europe https://gs.statcounter.com/browser-market-share/all/europe. I feel sorry for the people who continue working on IE because they think it's just as important as Edge/Safari/Firefox. :(
- devlopr 6y agoI have it at #4 in the desktop category basically on par with edge / firefox and almost double safari. https://netmarketshare.com/browser-market-share.aspx?options=%7B"filter"%3A%7B"%24and"%3A%5B%7B"deviceType"%3A%7B"%24in"%3A%5B"Desktop%2Flaptop"%5D%7D%7D%5D%7D%2C"dateLabel"%3A"Trend"%2C"attributes"%3A"share"%2C"group"%3A"browser"%2C"sort"%3A%7B"share"%3A-1%7D%2C"id"%3A"browsersDesktop"%2C"dateInterval"%3A"Monthly"%2C"dateStart"%3A"2019-11"%2C"dateEnd"%3A"2020-10"%2C"segments"%3A"-1000"%7D https://netmarketshare.com/browser-market-share.aspx?options...
- forgotmypw17 6y agoWheelchair users are not even close to 1% of the population. Would you tell a person in a wheelchair to just use their legs when at your establishment?
- dmt0 6y agoImportant or not, if your big corporate customers are willing to pay you big bucks to support it, how can you refuse? But yes, it does feel very counterproductive, and even more so over time, when you see that more and more of the popular libs drop support for ES5.
- collinmanderson 6y agoInternet Explorer 11 will probably be around for 25+ years, knowing Microsoft's track record in Windows. Though I'm surprised the author continued to support IE 10. IE 10 and below are quite dead. https://www.w3counter.com/trends https://www.w3counter.com/trends https://analytics.usa.gov/ https://analytics.usa.gov/ https://analytics.usa.gov/data/live/ie.json https://analytics.usa.gov/data/live/ie.json Edit: Amazing to see there's still IE5 usage on US Gov analytics :)
- micrio 6y agoYeah, I still encounter IE10 and older sometimes -- looking at the wide audiences Micrio projects are used for, this ranges from grandparent's Windows XP machines to OS-locked government IE10-PCs. Can't be helped, so an automatic fallback to the previous Micrio version is the least I can do. I do really hope that there could be a _final_ update from MS to IE10 to at least support ES6. That would also make such a big difference. One can wish..
- forgotmypw17 6y agoIt's not dead, it's alive and well on millions of machines still perfectly functional and usable. There are many developers, and thus services, who are not too lazy to support it. I've been able to achieve compatibility down to IE3, and hoping to go down all the way to 1.0, as a lone developer writing a relatively complex project. Retro-computing is growing at remarkable rates. But aside from that, there are also people out there using older devices not for the retro-computing cool, but because that's what they have. Telling them to upgrade is like telling a wheelchair user that they need to upgrade to legs. After all, wheelchair users are probably less than 1% of the population, right? If I were you, as a developer, I would be just a little bit ashamed and embarrassed of the cop-out attitude displayed in your comment.
- wishinghand 6y agoComparing IE usage to wheelchair users is disingenuous and gross. No one is physically unable to move on from Internet explorer 10. Times change, and standards do too.
- forgotmypw17 6y ago>No one is physically unable to move on from Internet explorer 10. Please rethink what you wrote here. You made an ableist and ignorant statement. There are many, many people out there who are trying to access information resources who, for one reason or another, have no control over what browser they are using. Corporate users. People with older devices they cannot upgrade. People who don't want to upgrade. People using public computers in libraries, shelters, and other assistance centers. People borrowing someone else's device or with a hand-me-down. People with devices they are emotionally attached to for whatever reason. The list goes on and on. I don't know about you, but as a developer who likes to think of himself as conscientious, as a developer trying to conquer laziness and over-complexity, as a developer who thinks about users and greater good, I'm not going to consciously write all these users off just because Internet Explorer presents some challenges in writing compatible code. If you are going down that path, please go to the bathroom, if you are privileged enough to access one, and have a long hard look at yourself in the mirror. Is that really the best you can do?
- jjkaczor 6y agoIE11 usage will probably tailor off quite dramatically this fall, after Microsoft drops support for it when using their 365 product suite... https://techcommunity.microsoft.com/t5/microsoft-365-blog/microsoft-365-apps-say-farewell-to-internet-explorer-11-and/ba-p/1591666 https://techcommunity.microsoft.com/t5/microsoft-365-blog/mi...
- WorldMaker 6y agoI think the bigger sandbag is the single file requirement. I understand why for this type of project and the way it is often deployed by its users that seems like a necessity, but at the same point, we're about to the point that you can assume that ES2015+ browsers also support HTTP/2+, individual file requests are no longer quite the same bottleneck they have been in HTTP/1.x. In which case you could get away with a "loader" possibly as simple as one <script type="module" src="path/to/modern-lib"></script> and one <script>/* older browser fallback */</script>.
- titzer 6y agoImpressive work, and a very nice speedup. Just a PSA, if you including a .wasm binary in your app, especially if it is large, be sure to request it separately as a .wasm (with MIME type "application/wasm", as your browser will cache the compiled code along with the original bytes in its HTTP cache, so you'll get lightning fast startup.
- nitsky 6y agoPairing that with the use of ‘instantiateStreaming’, browsers will even start interpreting and JITing the wasm as it downloads.
- wwww4all 6y agoGreat write up of the issues in migration to wasm. It should give insights into anyone contemplating thoughts about replacing JavaScript with X technology. JavaScript is the most popular and deployed full stack platform. It simply works with web workflows and development patterns. All the issues with JavaScript are trivial and new features and frameworks are available to overcome most issues.
- nitsky 6y ago> All the issues with JavaScript are trivial If the problems with JavaScript are trivial, why are the solutions such as webpack, babel, and typescript so complicated. Many companies have whole teams of people dedicated to maintaining solutions these problems. > and new features and frameworks are available to overcome most issues. Producing JavaScript apps that are both fast to run and easy to maintain does not feel like a problem that has been solved yet.
- speed_spread 6y ago> Producing JavaScript apps that are both fast to run and easy to maintain does not feel like a problem that has been solved yet. If the language is not part of the solution, maybe it's part of the problem?
- hombre_fatal 6y ago> If the problems with JavaScript are trivial, why are the solutions such as webpack, babel, and typescript so complicated. Many companies have whole teams of people dedicated to maintaining solutions these problems. Because building user-facing clients that run on a bunch of moving-target user machines instead of one big server has fundamental differences and challenges that afflicts all client development, no matter which language you're using nor the platform you're targeting, nor have centralized proprietary platforms that work on a single line of phones (Android, iOS) fared much better than the opposite, disjointed, organic approach of volunteers (the web). Why? Because it's hard, and hard for everyone. > Producing JavaScript apps that are both fast to run and easy to maintain does not feel like a problem that has been solved yet. You say that like you think anyone has solved it. How to build UIs in a world of constantly changing consumer tech is still an open question because it's hard, and it will always be. Apple is still straddling spaghetti code KVO systems from the 70s, and their new-fangled SwiftUI platform is still so buggy that the last three places I held an iOS contract with were clinging to UIKit.
- tantalor 6y ago> the Wasm-functions you call will return immediately That can't be right. Doesn't JS block on the call, like any old function? The wasm function returns a value, not a promise/thenable. So if the wasm function takes a long time/forever, it will not "return immediately".
- brundolf 6y agoI think by "immediately" he specifically meant "synchronously". He contrasted with web workers which run in separate threads, which means that they send messages back in an asynchronous promise.
- hombre_fatal 6y agoThey're expressing surprise that, unlike their experience with WebWorkers, you get to interface with Wasm/JS directly rather than through an async boundary.
- 1strepublicusr 6y agoNice article. Lots of useful ideas. It would be nice to know how much WebAssembly actually optimized here vs just writing WebGL in JavaScript. There are too many changes here to know if the speed ups and size gains are related to wasm or just to dumping three.js, and switching all the previous canvas 2D rendering to webgl rendering. In fact (maybe I missed it), if they're using AssemblyScript is it possible to just run it through TypeScript and check?
- micrio 6y agoThank you! Someone else in this post asked the same question and I answered briefly there. Indeed the engine rewrite itself from general purpose to specific usage probably made the biggest difference performance-wise. However, I do use matrix4 calculations for the 360 viewer, for which Wasm definitely is better. Interesting idea to do a followup benchmark using the AssemblyScript as TS! I might actually have to do that :D
- moron4hire 6y agoI'm curious to know more about their statement that loading textures in a WebWorker caused additional performance overhead versus loading them in the main thread. I have my own WebWorker based texture loading system and it is significantly better than not using it. At the risk of stating the obvious, as OP seems pretty well on top of the Web API game, did they remember to mark the texture data ArrayBuffer or ImageBitmap as transferable? Though, I suppose my metric is "number of dropped frames", not "total time to load" and I haven't actually measured the latter. My project is a WebXR project, so the user is wearing a VR headset, wherin dropped frames are the devil.
- micrio 6y agoI have to admit that I didn't dive 100% into what made this difference exactly. It just worked better without webworkers. I just dived into the git history, and it turns out I didn't use the buffers as transferable. Perhaps that was it! I'll definitely check that out later, thank you for pointing this out!
- XCSme 6y ago> it turns out I didn't use the buffers as transferable Not using transferable has HUGE performance hits, it can take even seconds to transfer larger buffers.
- micrio 6y agoA follow-up on this one. Now textures are downloaded by WebWorkers using the transferable buffers. Over multiple benchmark runs this results in another 32% CPU performance gain (WebWorkers vs single thread texture downloads)! Thank you for point this out to me!
- moron4hire 6y agoNo problem. I just happen to be working on a somewhat similar project. It's strictly 360 photos and it's built around our company's foregin language instruction services, but I've had to deal with/am currently dealing with several similar issues. Indeed, the next major tech change I had planned was to also implement my own WebGL code and get rid of Three.js. I'd like to see if I could get the WebGL code running in a WebWorker with OffscreenCanvas, but I'm as-yet unsure if that will work with WebXR.
- jariel 6y agoIt'd be interesting to get a true measure of the performance opportunities from WASM. Every app has performance opportunities, but they usually not quite related to the raw performance of the language, usually, it's loading and backend latency. People don't realize that V8 is really, really fast and always getting faster, and that WASM was never really super fast. As V8 gets faster the delta opportunity for WASM is reduced at least in terms of raw, algorithmic performance.
- titzer 6y agoI worked on both V8, Wasm, and Wasm in V8. The two have separate goals. V8's priority for JavaScript is to run the trillions of lines of JavaScript code found in the wild well and to support the language's evolution. It is no longer primarily about top-level throughput performance on computational kernels. Wasm has the goal of high, predictable performance for numeric-heavy, legacy-, and low-level code. Wasm is also focused on bringing more languages to the web that could reasonably be accomodated by a compile-to-JavaScript approach.
- jariel 6y ago"predictable performance for numeric-heavy, legacy" You mean all that 'legacy' C++ meant for the web? Yes, I see what someone might mean, i.e Autocad able to re-use a lot of code, but those are small use cases. And as for 'predictability' ... is it really predictable in application, given the amount of quasi-compilation that has to happen to get to WASM, and, more important, does it matter if it's predictable if it's slow, or rather, V8 is 'fast enough' in comparison? The fact is, WASM is a Zombie Technology. It's odd that it continues to exist, like Bitcoin it has a few vested interests, and the 'dream' seems real, but there are just very few material uses cases, and the practical reality of doing anything functional with WASM is very small. It's an intellectual concept, created without any specific customers/users in mind, and as such has very little adoption. Because V8 is 'so good' and 'fit to purpose' and increasingly so (whatever stated objectives are), it means the real opportunity for WASM just fades. Porting old C++ is a narrow case, and writing new C++ with a janky toolchain, and very limited ways to interact with networking, and UI ... all for what reason again?
- z3t4 6y agoThis article is different to other WebAssembly articles in that it also talks about the gotchas. I was going to comment "use webgl" but that also seem to be covered.
- la_fayette 6y agoWow this is so awesome. I would be highly interested how the energy consumption is dropped by using web assembly. If you consider that 5 billion people use smartphones and browse the web, if you could reach just 1% less cpu time on average that would have a significant impact on worldwide energy consumption...
- tacoe 6y agoSeconded: I had a blast reading this article. And I'd also be very curious for stats about power profile instead of raw speed (I'd expect them to be more or less correlated, but that might be a flawed assumption)
- micrio 6y agoThank you very much! Really interesting question. Whereas I don't have power-specific benchmarks, the general rule of thumb for power consumption is indeed the amount of CPU + GPU usage. It's an eternal battle to stay feature rich and keep smooth texture downloads, but use as little power as possible. And ofcourse don't draw any unnecessary frames. What I managed to do with the entire Wasm rewrite operation, is definitely get the number of CPU cycles down by a lot, and shortening the pathways between raw CPU and GPU operations. Since this article I was already able to do a lot more optimizations, because of the resulting new code architecture being so much clearer than before. Funny how more minimal structures allow for better optimizing.
- mattdesl 6y agoSo this is great but it might be worth pointing out that the library went from Canvas2D (slow) and ThreeJS (very general purpose) to pure WebGL calls tailored to the application, which alone probably would have been the significant driver behind the performance improvements. It’s hard to see exactly how much WASM has helped on top of that, and I wonder if a pure JS+WebGL version would perform about as well at the fraction of file size, complexity and parse speed. Usually I wouldn’t recommend going WASM unless you have very hot CPU code paths, like physics across thousands of 3D bodies and collisions that is eating up your frame time. In the case of an image viewer I’m not sure that code is ‘hot’ enough in CPU terms to really warrant all this. (I’d love to be proved wrong with benchmarks and find more general uses of wasm tho !) Just my 2c. Really great write up and work nonetheless!
- micrio 6y agoThanks for the comment! I agree that the change to a custom tailored engine probably made the biggest difference performance wise. At the end of the article I also briefly mention this. However, having a monolithic compiled Wasm module which contains all of (and only) the rendering logic is really nice on a codebase-level. Also, the Wasm part of Micrio is being used for a lot of realtime matrix4 calculations. While I didn't use this specific library, here is a bench page which shows that Wasm really kills JS performance here: https://maierfelix.github.io/glmw/mat4/ https://maierfelix.github.io/glmw/mat4/ . So it definitely makes a difference. If I had the time I would port the Wasm engine back to JS and do more benchmarking. But that's a big if :)
- mattdesl 6y agoI hadn't seen glmw, thanks for sharing. I can see how WASM definitely improves performance in those benchmarks (where we're talking about thousands of matrix operations). But I imagine your per-frame camera & matrix math is probably not taking up much time (within a 16ms budget), so a lot of these WASM optimizations may not have any real influence on the rendering performance. But, they could introduce more upfront parsing/loading time for your library (i.e. base64 decoding, WASM instantiation), and obviously a bring with them a lot more complexity (in authoring the module and supporting legacy devices). Anyways, it's pretty cool that you can call GL functions directly from WASM, I hadn't realized that before and it probably would make for an epic game engine to have almost everything called from WASM land.
- mrtnkl 6y agoTrue fun fact: the author, Marcel Duin, got married yesterday!
- julenx 6y agoNicely written article and very interesting read! > To be sure that there was as little background noise as possible, I ran all tests after a fresh reboot, making sure no unnecessary background processes, such as Windows Update, were running. This made me laugh =) In a more relevant note, probably worth pointing out to use production builds and running the browser in safe mode or with a clean profile (i.e. without any interfering extensions).
- petercooper 6y agoI apologise for making a meta point, but this is a fantastic example of how a story can take a while to "click" on HN. This is its sixth submission in 4 months (none by me, before you ask) and the first to actually get anywhere :-) There is so much gold flowing through HN /newest and I strongly encourage more people to check it out if possible.
- apatheticonion 6y agoI'm looking forward to the day when WebAssembly modules have DOM access and we can import them via script tags
- dandare 6y agoTangential question, what does the !! operator do in: const supported = !! self.WebAssembly;
- test1235 6y agocoerces the object to a boolean
- dandare 6y agoOf course! :)
- deleted 6y ago[deleted]
- simlevesque 6y ago! Is the operator but it is used twice.
- XCSme 6y agoIt's just the not "!" operator twice. The first NOT operator (from right to left) casts the following operand to the boolean primitve type and negates the value. The second NOT operator reverses the truth value of the boolean, in order to regain the original truthiness. !self.WebAssembly; -> false if self.WebAssembly is truthy (eg. object exists), true otherwise !!self.WebAssembly; = !(!self.WebAssembly;) = !(false|true) -> true if WebAssembly is defined, false otherwise.
- jek0 6y agoGreat achievement. One remark though, it sounds like the author does a lot of assumptions that something is better without actually benchmarking before and after. On those kind of projects, one should measure the impact of each step. Maybe the new version is only faster because it uses WebGL, maybe the WASM code is actually slower... Or is it the opposite? In my youth, I did a lot of x86 assembly programming. It's very easy to end up with a code slower that compiled high level languages. Here's an example: aligning memory buffers made a piece of code 50% faster (the bottleneck was memory bandwidth). That's a sort of optimization a compiler might (or might not) do for you. With ASM languages you have the control, so you're responsible for doing it. Michael Abrash's Black Book is a bible in term of approach to software optimization. It's old but a nice read. Out of print, a free ebook is maintained here: https://github.com/jagregory/abrash-black-book https://github.com/jagregory/abrash-black-book
- XCSme 6y agoI agree, a lot of assumptions to why things are faster. As far as I know WASM is not necessarily faster than native JS if you write the JS code properly (typed arrays, don't generate garbage, object pooling, etc.).
- XCSme 6y agoAmazing write-up, thank you! For the initial benchmark: > Over the 2 minute measured timespan, there was only 14% less CPU usage than with 2.9, tested over a number of trials. > Also, the red dots in the timeline at the top indicate blocked frames, or frame skips. In terms of power usage, 14% less CPU while instead using GPU for rendering probably leads to an actual higher power usage. Also, if the frame was skipped/blocked while waiting for the GPU, this can also explain the less CPU usage (CPU was idle as it was waiting for the GPU). My point is, this was not necessarily an improvement, especially considering that frame skips are the worst that can happen. I think a more fair comparison would have been to compare WebGL in JS vs WebGL through AssemblyScript, not Canvas2D vs WebGL in AssemblyScript, as now parts of the improvements come from moving computation from CPU to GPU, not necessarily from using WebAssembly. This is mentioned in the conclusion: "Are the performance gains of the new version fully attributable to WebAssembly? Most likely not.". > 65% less CPU used than in 2.9 This is amazing. It would be good to also check the GPU usage delta. > Don't use unnecessary WebWorkers The thing about WebWorkers is that you have to be really careful with memory transfer, and use transferable objects only to avoid memory copying. It would be also interested to see if using OffscreenCanvas can lead to better performance for this high-res image use-case. > I could gzip them before base64-encoding them This was a clever solution but doesn't the extra JS parsing (for the new library) and CPU usage (for decoding the file) take more time than the original solution?
- micrio 6y agoThank you for your points! As I said in the article about the benchmarking-- I definitely did it the "quick and dirty" way, testing the whole application 2.9 vs 3.0 on just my device-- not testing specifically for difference in power usage, GPU usage, etc. I would love to have time/resources enough to more microtests as you describe. Micrio used OffscreenCanvas for a long time. Turns out (apart from occasional flickering screens in Safari) while it did great for Canvas2D operations, it didn't seem to make a difference for WebGL. It actually adds more rendering steps, since you're basically rendering to a framebuffer first. As someone else pointed out below, with loading the textures using WebWorkers, I indeed didn't use the transferable objects, so it was basically copying a lot of buffers, explaining the performance hits. I'll definitely be experimenting with that. About the gzipping solution; I've just run a benchmark. It takes 14ms to gunzip & parse the JS base64 to ES6 string, and 59ms to run it inside the `new Function()`-parser. Comparing that to 65% CPU saved per frame drawn (not to mention the general V8 ES6 optimizations compared to the previous ES5 JS-version), I think the cost is worth it :)