在过去的一年中,自我分析工作组一直在构建用于分析 rustc
的工具,因为我们经常收到关于了解编译时间花费在哪里的请求。这对于优化编译器非常有用,这是编译器团队为提高编译时间而不断努力的方向之一,但对于希望重构其 crate 以便更快编译的用户也很有用。我们一直在开发一项新功能来帮助解决这个问题,这篇博客文章预览了它的工作原理。不过请注意:它仍然是实验性的,我们预计接口会随着时间的推移而变化。Rust Unstable Book 提供了此功能的文档,我们将保持更新,以便您可以随时在那里找到最新的说明。
在这篇文章中,我们将看看当前可用的工具,并使用它们来分析 rustc
在编译示例 crate 时的行为。
设置
首先,我们将从 measureme
存储库安装用于分析自我分析跟踪数据的工具。
$ cargo install --git https://github.com/rust-lang/measureme crox flamegraph summarize
现在我们有了工具,让我们下载一个示例 crate 来分析它的构建。
$ cd ..
$ git clone https://github.com/rust-lang/regex.git
$ cd regex
我们需要使用最新的 nightly 编译器来访问不稳定的 -Z
标志。
$ rustup override set nightly
如果您之前没有安装过 nightly 编译器,这将为您下载 nightly 编译器。如果您已经安装过,请更新它以确保您使用的是最新版本。
$ rustup update nightly
分析编译器
现在我们可以构建它并告诉 rustc
分析 regex
crate 的构建。这将导致在工作目录中创建三个新文件,其中包含分析数据。
$ cargo rustc -- -Zself-profile
$ ls
CHANGELOG.md LICENSE-APACHE UNICODE.md regex-17088.string_data regex-syntax target
Cargo.lock LICENSE-MIT bench regex-17088.string_index rustfmt.toml test
Cargo.toml PERFORMANCE.md examples regex-capi scripts tests
HACKING.md README.md regex-17088.events regex-debug src
新文件遵循格式 {crate 名称}-{rustc 进程 ID}.{events,string_data,string_index}
。
我们将使用三个主要工具中的每一个来分析分析数据
summarize
顾名思义,此工具会汇总跟踪文件中找到的数据。此外,summarize
还可以显示两个跟踪文件之间的“差异”,但我们不会使用此模式。
让我们运行该工具,仅传递跟踪的文件名(但不包括扩展名)
$ summarize summarize regex-17088
+-----------------------------------------------+-----------+-----------------+----------+------------+
| Item | Self time | % of total time | Time | Item count |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| LLVM_module_codegen_emit_obj | 4.89s | 42.752 | 4.89s | 159 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| codegen_module | 1.25s | 10.967 | 1.37s | 159 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| LLVM_module_optimize_module_passes | 1.15s | 10.022 | 1.15s | 159 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| LLVM_module_codegen_make_bitcode | 786.56ms | 6.875 | 960.73ms | 159 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| typeck_tables_of | 565.18ms | 4.940 | 689.39ms | 848 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| LLVM_module_codegen | 408.01ms | 3.566 | 6.26s | 159 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| mir_borrowck | 224.03ms | 1.958 | 543.33ms | 848 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| LLVM_module_codegen_emit_compressed_bitcode | 174.17ms | 1.522 | 174.17ms | 159 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| optimized_mir | 157.91ms | 1.380 | 205.29ms | 1996 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| evaluate_obligation | 146.50ms | 1.281 | 184.17ms | 8304 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| codegen_crate | 139.48ms | 1.219 | 1.58s | 1 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| mir_built | 123.88ms | 1.083 | 168.01ms | 848 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| metadata_decode_entry | 88.36ms | 0.772 | 117.77ms | 55642 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| incr_comp_copy_cgu_workproducts | 64.21ms | 0.561 | 64.21ms | 1 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| monomorphization_collector_graph_walk | 54.11ms | 0.473 | 344.00ms | 1 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| link_rlib | 43.21ms | 0.378 | 43.21ms | 1 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| check_impl_item_well_formed | 41.36ms | 0.362 | 77.14ms | 736 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| codegen_fulfill_obligation | 40.36ms | 0.353 | 51.56ms | 1759 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| expand_crate | 37.24ms | 0.326 | 48.52ms | 1 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| symbol_name | 36.31ms | 0.317 | 39.06ms | 5513 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
| free_global_ctxt | 34.34ms | 0.300 | 34.34ms | 1 |
+-----------------------------------------------+-----------+-----------------+----------+------------+
...
Total cpu time: 11.440758871s
输出按自身时间(在查询或活动中花费的时间,但不包括自身调用的其他查询或活动)排序。如您所见,大部分编译时间都花费在 LLVM 为可执行文件生成二进制代码上。
flamegraph
正如您可能已经猜到的,flamegraph
将生成分析数据的 火焰图。要运行该工具,我们将像 summarize
一样仅传递不带文件扩展名的文件名
$ flamegraph regex-17088
这将在工作目录中创建一个名为 rustc.svg
的文件
单击此处尝试交互式 svg。
crox
此工具将自我分析数据处理为 Chromium 分析器可以理解的 JSON 格式。您可以使用它来创建图形时间线,精确显示各种跟踪事件发生的时间。
在本节中,我们将介绍 crox
可以运行的几种不同模式,例如分析整个 crate 编译(包括依赖项)和过滤掉小型事件。让我们从基础知识开始!
基本用法
要运行该工具,我们将像以前一样仅传递不带文件扩展名的文件名
$ crox regex-17088
这会在工作目录中创建一个名为 chrome_profiler.json
的文件。要打开它,我们将使用您可能已经熟悉的常规 Chromium 性能工具
- 打开 Chrome
- 通过按
Ctrl
+Shift
+i
(Windows/Linux) 或Cmd
+Option
+i
(macOS) 打开开发者工具控制台 - 单击控制台顶部的“性能”选项卡。
- 单击“加载配置文件”按钮,该按钮看起来像一个向上箭头。
- 选择我们创建的
chrome_profiler.json
文件。
您现在应该看到类似以下的内容
您可以使用鼠标上的滚轮或触摸板上的相应手势来放大或缩小时间线。
过滤短事件
如果 chrome_profiler.json
文件过大,则常规 Chromium 性能工具在打开文件时会出现问题。一种简单的解决方法是告诉 crox
删除短于所选持续时间的事件
$ crox --minimum-duration 2 regex-17088
过滤掉小于 2 微秒的事件会将我们的 chrome_profiler.js
文件从 27mb 缩小到 11mb。
捕获事件参数
可以将自我分析器配置为在编译期间记录事件参数。例如,查询将包括其查询键。默认情况下,此功能处于关闭状态,因为它会增加自我分析器的开销。
要启用此功能,我们需要记录一个新的编译,向 rustc
传递一个额外的参数
$ cargo clean
$ cargo rustc -- -Zself-profile -Zself-profile-events=default,args
然后处理新的输出文件
$ crox regex-23649
现在,在 Chromium 分析器中,如果您单击一个节点,您可以在屏幕底部看到有关许多事件的附加数据
其中显示此 optimized_mir
查询正在处理 regex::compile::{{impl}}::new
函数体。
分析整个 crate 图
通过使用 RUSTFLAGS
环境变量,我们可以分析每个 rustc
调用,而不仅仅是最终 crate 的。然后,crox
可以将所有配置文件合并到一个输出文件中。由于这将创建大量文件,我们将告诉 rustc
创建一个文件夹来放置所有跟踪。
$ rm regex-17088.* regex-23649.* # clean up the old trace files since we're done with them
$ cargo clean
$ RUSTFLAGS="-Zself-profile=$(pwd)/profiles -Zself-profile-events=default,args" cargo build
这会在工作目录中创建相当多的跟踪文件。现在,我们将告诉 crox
将当前目录中的所有跟踪文件组合在一起
$ crox --dir profiles
打开此文件会显示编译的所有 crate
单击一个 crate 将展开它以显示其中的线程和事件数据
感谢您的阅读!
在过去的几个月中,我们一直在大量使用这些工具,它们极大地帮助我们了解了编译器花费时间的地方。将来,我们将添加更多功能,并且我们将致力于使该工具更易于使用。如果您有任何疑问或想参与自我分析工作组,请查看 measureme 存储库或访问我们的 Zulip 流。