WASM ファイルのサイズを小さくするには（Rust編）
Tags: webassembly, rust
Published: 2020-03-30 01:14:09

##TL;DR; 

[wasm-pack](https://rustwasm.github.io/wasm-pack/)を使いましょう。

## はじめに

[こちらの記事で、Rustでwasm32のバイナリを出力できるようになりました](https://qiita.com/chikoski/items/6c9f24dc4a825c437e78)。こちらの記事で作成された`hello-world.wasm`のサイズは875KBでした。これを小さくしていくのが、この記事の目的です。

ここに書いたことの大半は、[wasm-pack](https://rustwasm.github.io/wasm-pack/)が面倒を見てくれます。手でいろいろやりたい、という人向けの記事となっています。

## releaseビルド

Rustのビルドには、debugビルドとreleaseビルドがあります。

debugは名前の通り、デバッグ用のビルドです。最適化はあまりされておらず、デバッグ用の情報も追加されています。`cargo`コマンドでビルドした際のデフォルトは、debugビルドです。

一方releaseビルドは、最適化がなされ、デバッグ用の情報も追加されていません（通常は）。`--release` オプションを指定してビルドすると、releaseビルドされたバイナリを作成できます。

~~~sh
% ls target/wasm32-unknown-unknown/release
build			examples		hello-world.wasm	native
deps			hello-world.d		incremental
~~~

で、releaseビルドをでは、どれくらいサイズが小さくなってるのかというと、実はあんまり小さくなっていません。

| ビルド       | サイズ |
|:-----------------|:-----------------|
|debug|875K|
|release|866K|

より小さくするために、最適化オプションを変更してみることにします。

## lto オプションと最適化レベルの指定

`Cargo.toml`には最適化レベルを指定できます。何が書けるかは[The Rust Programming Language BookのThe manifest formatの節](https://doc.rust-lang.org/cargo/reference/manifest.html)に説明があります。

このファイルに次の3行を追加します。

~~~toml
[profile.release]
lto = true
opt-level = "z"
~~~

ltoオプションに`true`を指定することで、ライブラリのリンク時も最適化を行います。また`opt-level`は最適化レベルを表します。`z`は、より積極的にバイナリサイズを小さくするように最適化を行います。

結果は次のようになりました。

| ビルド       | サイズ |
|:-----------------|:-----------------|
|debug|875K|
|release|866K|
|lto + -Oz|273K|

だいぶ減りました。もう少し減らすために、何が原因かを調査します。

## コードサイズプロファイラ

コードサイズを増やしている原因を調査するために、コードサイズプロファイラと呼ばれるツールを利用することにします。今回は[twiggy](https://github.com/rustwasm/twiggy)と呼ばれるツールを利用します。

tiwggyは、次のように`cargo`コマンドを使ってインストールできます。

~~~sh
% cargo install twiggy
~~~

twiggyはいくつかのサブコマンドがありますが、`top`を使うと次のように、どの項目が、どれだけのサイズなのかをランキングで表示できます。

|Shallow Bytes|Shallow %|Item|
|--:|--:|:---|
|         82059 |    29.37% | `custom section '.debug_info'`|
|         76288 |    27.30% | `custom section '.debug_line'`|
|         46774 |    16.74% | `custom section '.debug_str'`|
|         25201 |     9.02% | `custom section '.debug_ranges'`|
|          7738 |     2.77% | "function names" subsection|
|          7595 |     2.72% | `data[0]`|
|          3315 |     1.19% | `dlmalloc::dlmalloc::Dlmalloc::malloc::h8a37a103d2401d9d`|
|          1656 |     0.59% | `main`|
|          1514 |     0.54% | `std::io::Write::write_all::hfd4c5b414ccb1671`|
|          1399 |     0.50% | `<str as core::fmt::Debug>::fmt::hda75deb2d8ed9e81`|
|         23210 |     8.31% | `... and 251 more.`|
|        276749 |    99.04% | Σ [261 Total Rows]|

なおこの結果は、次のコマンドは実行して得ました。`top`はサイズのランキングを出すサブコマンドす。`-n`オプションを加えることで、何位までを出力するかを決められます。

~~~sh
% twiggy top -n 10 target/wasm32-unknown-unknown/release/hello-world.wasm
~~~

## デバッグ情報の削除

上記の結果を見ると、デバッグ情報が大きなサイズを取っていることがわかりました。これを削除します。削除には、[wasm-snip](https://github.com/rustwasm/wasm-snip)というツールを利用しました。

### wasm-snipのインストールと、デバッグ情報の削除

Cargoコマンドでインストールできます。

~~~sh
% cargo install wasm-snip
~~~

不要なコードの削除は、次のように行います。`-o`オプションには、不要な情報が削除されたwasmファイルのパスを指定します。

~~~sh
% wasm-snip -o hello-world-snipped.wasm target/wasm32-unkown-unknown/release/hello-world.wasm
~~~

結果は、次のようになりました。もとの20分の1程度には小さくなりました。

| ビルド       | サイズ |
|:-----------------|:-----------------|
|debug|875K|
|release|866K|
|lto + -Oz|273K|
|wasm-snip|45K|

### どこにデバッグ情報があるのか？

WebAssebmlyには[Custom Section](https://webassembly.github.io/spec/core/appendix/custom.html)あります。このセクションは、モジュールの振る舞いには影響は与えないが、処理系にとって有用な情報を書く場所として定義されています。

デバッグに必要な情報も、振る舞いには影響を与えないが処理系にとって有用な情報なので、ここに書かれます。

## まとめ

* Cargo.tomlでLTOを有効にすること
* コンパイラの最適化オプションを変えて、`z`にすること
* [wasm-snip](https://github.com/rustwasm/wasm-snip)をつかって、不要なコードを削除すること

以上が、この記事のまとめです。

はじめにの節にも書きましたが、ここに書かれていることの大半はwasm-packが面倒を見てくれます。ただ、LTSや最適化オプションあたりは自分で設定することもできます。

またこれ以上のバイナリサイズの削減も、きっとできるとは思いますが、それは別の記事としようと思っています。