Minimizing WASM binaries
- 3 minutes read - 463 wordsI’ve spent time recently playing around with WebAssembly (WASM) and waPC. Rust and WASM were born at Mozilla and there’s a natural affinity with writing WASM binaries in Rust. In the WASM examples I’ve been using for WASM Transparency, waPC and MsgPack and waPC and Protobufs.
I’ve created 3 WASM binaries: complex.wasm
, simplex.wasm
and fabcar.wasm
and each is about 2.5MB when:
cargo build --target=wasm32-unknown-unknown --release
The Rust and WebAssembly book has an excellent section titled Shrinking .wasm.
Code Size. So, let’s see what help that provides.
For complex.wasm
, the starting file size using the above command is: 2,473,898.
Adding Link-Time Optimizations
:
[profile.release]
lto = true
Reduces the file size to: 1,112,965 (~50%)
Then, aggressively optimizing for size:
[profile.release]
lto = true
opt-level = 'z'
Reduces the file size to: 1,053,485 (~43% of original)
Let’s use twiggy to see what’s remaining:
twiggy top -n 10 ./target/wasm32-unknown-unknown/release/complex.wasm
Shallow Bytes │ Shallow % │ Item
───────────────┼───────────┼────────────────────────────────────────────────────────
296065 ┊ 28.10% ┊ "function names" subsection
85876 ┊ 8.15% ┊ custom section '.debug_str'
75273 ┊ 7.15% ┊ data[0]
56076 ┊ 5.32% ┊ custom section '.debug_info'
47403 ┊ 4.50% ┊ custom section '.debug_line'
25480 ┊ 2.42% ┊ custom section '.debug_ranges'
25067 ┊ 2.38% ┊ custom section '.debug_pubnames'
5859 ┊ 0.56% ┊ dlmalloc::dlmalloc::Dlmalloc::malloc::h86eb72a884c31be6
5829 ┊ 0.55% ┊ <protobuf::descriptor::DescriptorProto
4678 ┊ 0.44% ┊ <protobuf::descriptor::FileDescriptorProto
425879 ┊ 40.43% ┊ ... and 2951 more.
1053485 ┊ 100.00% ┊ Σ [2961 Total Rows]
Now, let’s use binaryren’s wasm-opt
tool.
wasm-opt \
--strip-debug \
-o complex.no_debug.wasm \
./target/wasm32-unknown-unknown/release/complex.wasm
Then:
twiggy top -n 10 complex.nodebug.wasm
Shallow Bytes │ Shallow % │ Item
───────────────┼───────────┼─────────────────────
75273 ┊ 16.18% ┊ data[0]
5498 ┊ 1.18% ┊ code[1410]
5478 ┊ 1.18% ┊ code[424]
4409 ┊ 0.95% ┊ code[1369]
4348 ┊ 0.93% ┊ elem[0]
4214 ┊ 0.91% ┊ code[383]
4069 ┊ 0.87% ┊ code[340]
3459 ┊ 0.74% ┊ code[385]
2892 ┊ 0.62% ┊ code[1382]
2707 ┊ 0.58% ┊ code[1552]
352801 ┊ 75.85% ┊ ... and 2933 more.
465148 ┊ 100.00% ┊ Σ [2943 Total Rows]
Reduces the fize size to: 465,148 (~20% of original)
And, lastly, optimized aggressively for size (using wasm-opt
this time):
wasm-opt \
-Oz \
-o complex.size.wasm \
complex.no_debug.wasm
Then:
twiggy top -n 10 output.wasm
Shallow Bytes │ Shallow % │ Item
───────────────┼───────────┼─────────────────────
46380 ┊ 11.90% ┊ data[36]
6072 ┊ 1.56% ┊ data[58]
5843 ┊ 1.50% ┊ data[14]
5412 ┊ 1.39% ┊ code[332]
5071 ┊ 1.30% ┊ code[978]
4168 ┊ 1.07% ┊ code[942]
4014 ┊ 1.03% ┊ elem[0]
3824 ┊ 0.98% ┊ code[291]
3547 ┊ 0.91% ┊ code[259]
3311 ┊ 0.85% ┊ data[10]
302236 ┊ 77.52% ┊ ... and 1874 more.
389878 ┊ 100.00% ┊ Σ [1884 Total Rows]
Reduces the fize size to: 389,878 (~16 of original)
There are even more optimisations described in the book but, I think ~16% is a sufficiently dramatic improvement to call it quits.