7MB *.wasm files?

  1. I’m working on a Rust/wasm32 app.

  2. I’m now at the point where ls -l deploy/*.wasm (release mode, non debug mode) is hitting 7MB (not for entire directory, for the single wasm file).

  3. Besides manually removing creates one by one, trying to make the code recompile, is there anyway to figure out what crates are causing the wasm to balloon in size?

  4. There are no non-trivial include-string/include-bytes.

There’s a couple of sections in the Rust WASM book about shrinking binary size - one in the tutorial, and one in the reference. If you’ve not tried the steps from there, that’d be a good start.

2 Likes

*.wasm binary size went from 7MB to 1MB. Thanks!

1 Like

Side note: If you care about deploying this on a website, make sure to compare the gzipped sizes!

2 Likes

Is there a way to get ‘cargo web deploy’ to automatically handle this for me? If not, what is a recommended guide? (This notion of gzipping wasm is new to me.)

Typically you don’t gzip it yourself – webservers will gzip it, often on the fly, to deliver it to clients. So the client may download a lot less than 1MB.

1 Like

I’ve been playing around with making very small wasm binaries recently, and I have a few other suggestions on how to investigate your binary size.

You likely know this already, but for people who find this thread from a search engine: Rust wasm binaries are not inherently large. My smallest useful deployed one is about 200 bytes. I gather some other toolchains targeting wasm right now produce big binaries; Rust isn’t one of them.

So, once you have a wasm module, how do you tell why it’s large? The two things I’d look for are:

  1. Large initialized data sections.
  2. Suspiciously large clusters of functions for a single crate.

The wasm binutils (wabt) don’t provide great support for the second use case, but the first one is okay:

$ wasm-objdump -x -j data your_wasm_module.wasm | less

The init= at the top of each chunk shows you the size in bytes. If the size of any section, or all the sections together, approaches your problematic module size, then you’ve found your culprit. Tying them back to their crates of origin is harder; squint at the data and see if anything jumps out, like crate names in str literals.

For identifying large code, here’s a script that generates a simple report. Use the script like this:

$ wasm-objdump -x your_wasm_module.wasm | ruby report.rb

The report will attempt to summarize usage by both function name and crate name. (I say “attempt” because the symbol parser is kind of a hack, but it works for the modules I’ve tried.) Example output:

# By func name
... many lines omitted ...
      1403  <str as core::fmt::Debug>::fmt::hda75deb2d8ed9e81
      1487  wasm_game_of_life::conway::step::h0e8b342676c7bf62
      3303  dlmalloc::dlmalloc::Dlmalloc::malloc::h8a37a103d2401d9d
# By crate
         4  js_sys
       410  console_error_panic_hook
       892  wasm_bindgen
      2067  alloc
      2526  wasm_game_of_life
      4237  std
      4902  unknown
      6697  dlmalloc
     11643  core

Script (in Ruby, because I’m guessing you have Ruby installed):

#!/usr/bin/env ruby

func_names = {}
func_crates = {}

size_by_name = {}
size_by_crate = {}

STDIN.each_line { |line|
  line.strip!
  if line =~ /func\[([0-9]+)\] sig=[0-9]+ <(.*)>$/
    idx = $1.to_i
    name = $2
    if name =~ /^([a-zA-Z0-9_]+)::/
      crate = $1
    elsif name =~ /^<([a-zA-Z0-9_]+)::.* as /
      crate = $1
    else
      crate = 'unknown'
    end
    func_names[idx] = name
    func_crates[idx] = crate
  elsif line =~ /func\[([0-9]+)\] size=([0-9]+)/
    idx = $1.to_i
    size = $2.to_i
    size_by_name[func_names[idx]] = size

    size_by_crate[func_crates[idx]] ||= 0
    size_by_crate[func_crates[idx]] += size
  end
}

puts "# By func name"
size_by_name.each.sort { |a, b| a[1] <=> b[1] }.each { |n, size|
  printf("% 10d  %s\n", size, n)
}

puts "# By crate"
size_by_crate.each.sort { |a, b| a[1] <=> b[1] }.each { |n, size|
  printf("% 10d  %s\n", size, n)
}
2 Likes