cargo-bloatでRustのバイナリサイズの原因を特定する

最近Redditでちょっと話題になっていた[cargo-bloat][github]を紹介。

# Cargo-bloat
どの関数が重いかがざっくりわかる[Cargo Subcommand][cargo-subcommand]。
こんな感じになります。

```
$ cargo bloat --release -n 10
    Finished ...

  File  .text     Size Name
 39.1%  72.8%   1.5MiB [3469 Others]
  3.5%   6.4% 132.1KiB clap::app::parser::Parser::add_defaults
  2.3%   4.3%  88.7KiB clap::app::parser::Parser::add_env
  1.5%   2.8%  56.9KiB <regex::exec::ExecNoSync<'c> as regex::re_trait::Regu...
  1.3%   2.4%  50.0KiB clap::app::parser::Parser::get_matches_with
  1.3%   2.4%  49.8KiB clap::app::parser::Parser::parse_long_arg
  1.3%   2.4%  49.1KiB clap::app::parser::Parser::parse_short_arg
  1.1%   2.0%  40.2KiB rg::args::ArgMatches::to_args
  0.9%   1.7%  34.5KiB regex_syntax::parser::Parser::parse_expr
  0.9%   1.6%  33.4KiB clap::app::validator::Validator::validate_matched_args
  0.7%   1.3%  25.8KiB encoding_rs::variant::VariantDecoder::decode_to_utf8_raw
 53.8% 100.0%   2.0MiB .text section size, the file size is 3.7MiB
```

# How to Use
### Install

まだcrates.ioには置いていないのでgitを介してinstallする感じになる。

```bash
cargo install --force --git https://github.com/RazrFalcon/cargo-bloat.git
```

2017/1/12追記: Publishされました

```bash
cargo install cargo-bloat
```

### Command

```
$ cargo bloat -V
cargo-bloat 0.1.0
```

```
$ cargo bloat -h
Find out what takes most of the space in your executable

Usage: cargo bloat [options]

Options:
    -h, --help              Print this message
    -V, --version           Print version info and exit
    --features FEATURES     Space-separated list of features to also build
    --all-features          Build all available features
    --no-default-features   Do not build the `default` feature
    --manifest-path PATH    Path to the manifest to analyze
    --release               Build artifacts in release mode, with optimizations
    --example NAME          Build only the specified example
    --crates                Per crate bloatedness
    --filter CRATE          Filter functions by crate
    --split-std             Split the 'std' crate to original crates like core, alloc, etc.
    --full-fn               Print full function name with hash values
    -n NUM                  Number of lines to show, 0 to show all [default: 20]
    -w, --wide              Do not trim long function names
    -v, --verbose           Use verbose output
    -q, --quiet             No output printed to stdout
    --color WHEN            Coloring: auto, always, never
    --frozen                Require Cargo.lock and cache are up to date
    --locked                Require Cargo.lock is up to date
    -Z FLAG ...             Unstable (nightly-only) flags to Cargo
```

見慣れないオプションがいくつかある。

```
--crates                Per crate bloatedness
--filter CRATE          Filter functions by crate
--split-std             Split the 'std' crate to original crates like core, alloc, etc.
--full-fn               Print full function name with hash values
-n NUM                  Number of lines to show, 0 to show all [default: 20]
-w, --wide              Do not trim long function names
```

# 使用例
お世話になっている[ripgrep]に使ってみる。

```
$ git clone https://github.com/BurntSushi/ripgrep
$ cd ripgrep
$ cargo bloat --release
    Compiling ...
    Finished ...

  File  .text     Size Name
 34.5%  64.1%   1.3MiB [3459 Others]
  3.5%   6.4% 132.1KiB clap::app::parser::Parser::add_defaults
  2.3%   4.3%  88.7KiB clap::app::parser::Parser::add_env
  1.5%   2.8%  56.9KiB <regex::exec::ExecNoSync<'c> as regex::re_trait::Regu...
  1.3%   2.4%  50.0KiB clap::app::parser::Parser::get_matches_with
  1.3%   2.4%  49.8KiB clap::app::parser::Parser::parse_long_arg
  1.3%   2.4%  49.1KiB clap::app::parser::Parser::parse_short_arg
  1.1%   2.0%  40.2KiB rg::args::ArgMatches::to_args
  0.9%   1.7%  34.5KiB regex_syntax::parser::Parser::parse_expr
  0.9%   1.6%  33.4KiB clap::app::validator::Validator::validate_matched_args
  0.7%   1.3%  25.8KiB encoding_rs::variant::VariantDecoder::decode_to_utf8_raw
  0.7%   1.2%  25.5KiB globset::GlobSet::new
  0.6%   1.2%  23.7KiB rg::run
  0.5%   0.9%  18.6KiB regex::re_bytes::Regex::find_at
  0.5%   0.9%  18.5KiB <regex::re_trait::Matches<'t, R> as core::iter::itera...
  0.5%   0.9%  17.9KiB ignore::walk::Worker::run
  0.4%   0.8%  15.6KiB rg::app::app
  0.4%   0.7%  15.1KiB clap::app::help::Help::write_arg
  0.4%   0.7%  15.0KiB rg::run_one_thread
  0.4%   0.7%  14.9KiB clap::app::usage::get_required_usage_from
  0.4%   0.7%  13.8KiB clap::app::validator::Validator::validate
 53.8% 100.0%   2.0MiB .text section size, the file size is 3.7MiB
```
普通に打つとこんな感じ。デフォルトではトップ20が表示され、その他は一つにまとめられる。コマンドラインパーサの[clap]が目立つ。
Sizeは[.textセクション][.text]のもの。%はファイル全体に対してと.textセクションの合計に対しての両方を出してくれるようになった。

20は多いので`-n 10`でトップ10だけ表示することにする。これが上に載せた例。

```
$ cargo bloat --release -n 10
    Finished ...

  File  .text     Size Name
 39.1%  72.8%   1.5MiB [3469 Others]
  3.5%   6.4% 132.1KiB clap::app::parser::Parser::add_defaults
  2.3%   4.3%  88.7KiB clap::app::parser::Parser::add_env
  1.5%   2.8%  56.9KiB <regex::exec::ExecNoSync<'c> as regex::re_trait::Regu...
  1.3%   2.4%  50.0KiB clap::app::parser::Parser::get_matches_with
  1.3%   2.4%  49.8KiB clap::app::parser::Parser::parse_long_arg
  1.3%   2.4%  49.1KiB clap::app::parser::Parser::parse_short_arg
  1.1%   2.0%  40.2KiB rg::args::ArgMatches::to_args
  0.9%   1.7%  34.5KiB regex_syntax::parser::Parser::parse_expr
  0.9%   1.6%  33.4KiB clap::app::validator::Validator::validate_matched_args
  0.7%   1.3%  25.8KiB encoding_rs::variant::VariantDecoder::decode_to_utf8_raw
 53.8% 100.0%   2.0MiB .text section size, the file size is 3.7MiB
```

[clap]が重いのは明らかだが、`--crates`オプションを渡して確認する。

```
$ cargo bloat --release --crates
    Finished ...

  File  .text     Size Name
 15.3%  28.4% 584.0KiB clap
 11.1%  20.7% 426.1KiB [Unknown]
 10.7%  20.0% 410.5KiB std
  6.2%  11.5% 236.5KiB regex
  3.4%   6.4% 131.1KiB regex_syntax
  2.5%   4.7%  96.7KiB ignore
  1.3%   2.5%  50.8KiB globset
  0.8%   1.4%  29.3KiB encoding_rs
  0.5%   0.9%  19.0KiB grep
  0.4%   0.8%  17.1KiB walkdir
  0.3%   0.6%  11.8KiB aho_corasick
  0.2%   0.4%   8.0KiB crossbeam
  0.2%   0.3%   6.0KiB env_logger
  0.2%   0.3%   6.0KiB thread_local
  0.1%   0.2%   5.1KiB termcolor
  0.1%   0.2%   3.2KiB ansi_term
  0.1%   0.1%   2.8KiB vec_map
  0.1%   0.1%   2.1KiB utf8_ranges
  0.1%   0.1%   2.1KiB memchr
  0.1%   0.1%   2.0KiB strsim
 53.8% 100.0%   2.0MiB .text section size, the file size is 3.7MiB
```
28%を[clap]が占めているという状態。高機能なだけになかなか重い。

### 軽量版clapを使う
ここで[clap]の`v2.29.1`を使うように`cargo update`してからもう一度実行してみる。

```
$ cargo bloat --release --crates
    Finished ...

  File  .text     Size Name
 12.2%  24.8% 423.5KiB [Unknown]
 11.7%  23.8% 406.8KiB std
  7.0%  14.3% 244.0KiB clap
  6.8%  13.8% 236.7KiB regex
  3.8%   7.7% 131.1KiB regex_syntax
  2.8%   5.7%  96.7KiB ignore
  1.5%   3.0%  50.8KiB globset
  0.8%   1.7%  29.3KiB encoding_rs
  0.5%   1.1%  19.0KiB grep
  0.5%   1.0%  17.1KiB walkdir
  0.3%   0.7%  11.8KiB aho_corasick
  0.2%   0.5%   8.0KiB crossbeam
  0.2%   0.4%   6.0KiB env_logger
  0.2%   0.3%   6.0KiB thread_local
  0.1%   0.3%   5.1KiB termcolor
  0.1%   0.2%   3.2KiB ansi_term
  0.1%   0.2%   2.8KiB vec_map
  0.1%   0.1%   2.1KiB utf8_ranges
  0.1%   0.1%   2.1KiB memchr
  0.1%   0.1%   2.0KiB strsim
 49.2% 100.0%   1.7MiB .text section size, the file size is 3.4MiB
```
300Kほど軽くなっている。
実は、[clap]の開発者が[cargo-bloat][github]の結果に触発されて[軽量化を行った][weight-loss]らしい。開発途中だけど、こんな感じで参考になりそう。

### 2017/01/12追加: stdライブラリを分割して表示
1/11夜にオプションがいくつか追加されたので`--split-std`を使ってみた。`--filter`とかは自明なので割愛。

```
$ cargo bloat --release --crates --split-std
    Finished ...

  File  .text     Size Name
 12.2%  24.8% 423.5KiB [Unknown]
  7.0%  14.3% 244.0KiB clap
  6.8%  13.8% 236.7KiB regex
  5.5%  11.2% 191.5KiB core
  3.8%   7.7% 131.1KiB regex_syntax
  3.4%   7.0% 119.6KiB std
  2.8%   5.7%  96.7KiB ignore
  2.6%   5.3%  91.4KiB alloc
  1.5%   3.0%  50.8KiB globset
  0.8%   1.7%  29.3KiB encoding_rs
  0.5%   1.1%  19.0KiB grep
  0.5%   1.0%  17.1KiB walkdir
  0.3%   0.7%  11.8KiB aho_corasick
  0.2%   0.5%   8.0KiB crossbeam
  0.2%   0.4%   6.0KiB env_logger
  0.2%   0.3%   6.0KiB thread_local
  0.1%   0.3%   5.1KiB termcolor
  0.1%   0.2%   3.2KiB std_unicode
  0.1%   0.2%   3.2KiB ansi_term
  0.1%   0.2%   2.8KiB vec_map
 49.2% 100.0%   1.7MiB .text section size, the file size is 3.4MiB
```
stdライブラリが`core`や`alloc`などのcrateに分割されて表示されているのがわかる。


### 注意点

* RustではなくCで書かれたライブラリ等は`[Unknown]`にまとめられる
* Windows未対応 

[github]: https://github.com/RazrFalcon/cargo-bloat
[cargo-subcommand]: https://github.com/rust-lang/cargo/wiki/Third-party-cargo-subcommands
[ripgrep]: https://github.com/BurntSushi/ripgrep
[clap]: https://github.com/kbknapp/clap-rs
[.text]: http://www.ertl.jp/~takayuki/readings/info/no02.html
[weight-loss]: https://clap.rs/2018/01/09/new-years-weight-loss/