Hacker News new | ask | show | jobs
by andyp-kw 1151 days ago
My understanding that WASM has a heavier load time, however actual benchmarks after the initial load are more impressive.
2 comments

It’s not WASMs fault. Rust produces large binaries for whatever reason and people like to write their WASM in Rust. I’ve ported Rust to equivalent C and it was 25% of the size and similarly for loading times.
it is rather simple, JS tooling cares A LOT about the size of their dependency tree. Statically compiled languages do not (except if they focus on embedded programming). Having a binary be 2mb vs 30mb is not a big deal for a desktop application

Just for reference I was testing this the other day and compiled some simple C++ to WASM and adding:

std::cout << "some text";

to the code increased the binary size by like 5mb. Turns out std:cout pulls ALL currency-formating, date-formating and i18n code in the c++ standard library into your binary

ansi C printf does not meaningfully increase your WASM binary size

If you want your code to be able to be loaded on-the-fly and fast you need bundling tools, just like JS does. Bundling is a really hard problem, game devs struggle a lot with it as well (although their problem is usually bundling assets, not code itself)

Exactly, WASM was designed to be very very lightweight... you can put a lot of logic into a very small amount of WASM, but you need a good compiler to do that, or write WASM by hand to really feel the benefit. If you just compile Go to WASM, with its GC, runtime and stdlib included in the binary, yeah it's going to be pretty heavy... Rust doesn't have a runtime but as you said, for some reason, produces relatively large binaries (not the case only in WASM by the way). Probably, the best ways to create small WASM binaries is to compile from C or from a WASM-native language like AssemblySCript (https://www.assemblyscript.org).
Rust doesn't have to output more code than a C compiler. But it tends to because most rust programs are stuffed full of bounds checks. And bounds checks aren't small. As well as the conditional itself, every bounds check also includes:

- A custom panic message (so you know which line of code crashed)

- Some monomorphized formatting code to output that message

- The infrastructure to generate a stack trace after a panic

- Logic to free all the allocated objects all the way up the stack

If you compile this 1 line function:

    pub fn read_arr(arr: &[usize], i: usize) -> usize {
        arr[i] // (equivalent to 'return arr[i];')
    }
... You produce 20 hairy lines of assembler: https://rust.godbolt.org/z/dhz34KEvj

In contrast, the equivalent C function is this rust code:

    pub fn read_arr_unchecked(arr: &[usize], i: usize) -> usize {
        unsafe { *arr.get_unchecked(i) }
    }
And predictably, the result is this gem - identical to what the C compiler outputs:

    example::read_arr_unchecked:
        mov     rax, qword ptr [rdi + 8*rdx]
        ret
But nobody writes rust code like that (for good reason). You can get a lot of the way there by leaning heavily using rust's iterator types and such. But its really difficult to learn what patterns will make the rust compiler lose its mind. There's no feedback on this at compile time, at all.
I'd appreciate any more tips or resources you might have about reducing Rust code bloat. I want my library [1] to be acceptable to the most strident anti-bloat curmudgeons, so they'll make their UIs accessible.

[1]: https://github.com/AccessKit/accesskit

I don’t know many good resources to learn this stuff unfortunately.

The things I reach for in practice are godbolt and cargo asm[1] - which can show me the actual generated assembler for functions in my codebase. And twiggy[2], which can tell you which functions are the biggest in your compiled binary and point out where monomorphization is expensive.

When I’m developing, I regularly run a script which compiles my code to wasm and tells me how the wasm file size has changed since the last time I compiled it.

Some tips:

Try to avoid array lookups with an index when you can. When looping, use slice iterators and when making custom iterators, wrap the slice iterator rather than storing a usize index yourself.

Be careful of monomorphization. If you’re optimising for size, it can be better to take a dyn Trait rather than making a function generic.

And play around with your wasm API surface area. It takes a lot more code to pass complex objects & strings back and forth to javascript than other types.

But otherwise, good luck! Love the project.

[1] https://github.com/gnzlbg/cargo-asm

[2] https://github.com/rustwasm/twiggy

Great project - this is important! Love your clear motivation statement at the top. I do wish there were some code/data examples within the README, but clearly that's not holding people back from using it.
Rust can be the same size if you put the same code into the binary, sometimes even smaller.

The problem is that it's real easy to just add a bunch of crates to an application, similar to the nodejs/Python approach.

Most people slso don't seem to turn off many parts of the standard library they don't, even for platforms like WASM. Maybe it's useful to have a stack unrolling panic handler during debug but in release you can just abort and save up to megabytes of space.

There's also a lot to be gained by tweaking the compiler optimisers. By default the optimizer is multithreaded, which makes compiles quite a lot faster, but reduce that to a single thread and suddenly a lot of optimizations can happen that wouldn't happen by default.

I wouldn't write code like described here in C, but I imagine Go and C# are better choices here. Maybe even that Java library the name of which I can never remember, or that Kotlin project that compiles Kotlin to Javascript with super easy interaction between frontend and backend.

I love Rust but if you're going to pick a systems programming language for your frontend, just make desktop supplications. Web is a nice fallback but if it's your primary target, there are so many better options out there.

And since everyone likes to talk about how great and magic the WASM sandbox happens to be, who cares if C is being used.
When you ship javascript, the Browser already has a lot of the bigger libraries built in. When you ship rust/wasm, you need to ship all of the basic types like Strings, Vecs, + a lot of the std