记一次 Rust 编译体积优化
最近在用 Rust 写一个 Alfred 工作流,写完后发现体积有点大,于是查找了一些减少编译体积的方法,在此记录一下。
编译器版本:
1 | > rustc --version |
1.使用 Release 构建
Cargo 默认使用 debug 模式编译,这个模式下没有进行任何优化,并且附加了大量的调试信息,所以程序体积比较大。如果采用 release 模式下会对程序进行优化,也不会包含调试信息,详情可以参考 cargo book 或者 rust book.
我的程序也不是很大,在 debug 模式大概有 2.7MB,使用 release
模式下能够减少到 2.1 M,大概减少了 22%. 因为我的程序原本就不大,所以 20% 的减少量还是挺可观的。如果程序比较大,这个体积的减少量会更大。
2. 修改 Profiles
2.1 调整优化等级
Cargo 中提供了 profiles 用来修改一些编译设置,其中可以使用 opt-level
来调整优化等级。下面是一些可选的值,具体可以查看 cargo book
0
: no optimizations1
: basic optimizations2
: some optimizations3
: all optimizations"s"
: optimize for binary size"z"
: optimize for binary size, but also turn off loop vectorization.
优化等级越高,程序体积就越小,但是运行得越慢。因为我的程序只是一个 Alfred workflow,不需要他运行得多快,所以我将优化等级设置为了 ‘z’。
1 | [profile.release] |
调整优化等级后我的程序体积减少到了 1.4 M,相比之前减少了大概 33%,很 nice。
2.2. 开启 LTO
LTO(Link Time Optimization),它控制了 rustc
的 -C lto
, -C linker-plugin-lto
和 -C embed-bitcode
选项,即控制了 LLVM 的链接时优化,从而消除大量冗余代码,但是代价是更长的链接时间。下面是一些有效的值,集体可以参考 cargo book
false
: 只对本地的 crate 进行优化true
/fat
: 尝试对依赖的所有的 crates 进行优化thin
: 与fat
相似,但是运行时间大量减少off
: 禁止 LTO
这里采用 true
,不过尴尬的是几乎没有什么提升,大概减少了 5%
2.3. Panic 时立刻终止
panic
选项可以控制 -C panic
flag,控制了使用的 panic 策略。panic
有下面两种可能的值,具体可以参考 cargo book
"unwind"
: panic 时进行栈展开"abort"
: panic 是立即终止
使用 abort
不利于修改 bug,不过我的程序很简单,于是直接采用 abort
策略了。
1 | [profile.release] |
程序体积减小到了 1.3MB,感觉还行
2.4. 调整 codegen-units
codegen-utils
控制着 -C codegen-units
flag, 控制着一个 crate 执行代码生成时使用的线程数量, codegen-utils
越大,就意味着编译时间越少,但是可能产生的代码运行得更慢(因为会妨碍某些优化). 这里我们采用 1,体积大概减少了 0.03% 😂
2.5 使用 strip
strip
控制 -C strip
flag,即控制是否在二进制文件中保留符号链接、调试等信息,可选值为 none
/ false
, debuginfo
, symbols
/ true
。详情可以查看 cargo book
这里我是用 symbols
,程序体积减少了大概 10 %,较少到了 1.2 MB
感觉这里和使用 strip
命令是一样的效果
1 | strip target/release/alfred-zed |
3. 减少依赖
可以使用 cargo deps | dot -Tpng > dep.png
来查看依赖图,不过这只能看到依赖图。如果想要知道某个 crate 占据了多大体积,可以是使用 cargo-bloat
我的程序使用到的依赖如下:
1 | [dependencies] |
1 | > cargo bloat --release --crates |
可以看到,使用 cargo-bloat
是体积增大了,不过没有关系,我们只是想要知道 crates 占据的大致大小和比例。
从输出结果可以看到, [Unknown]
占据了绝大多数的体积,我们可以使用 --unknown
选项查看
1 | > cargo bloat --release --unknown |
这里看到 unknown 很多,不过大致可以猜到是和 sqlite 相关,所以我们把问题定位到 rusqlite
。(而且从依赖图中也能看到它依赖了很多东西。)
于是我决定用 sqlite
替换 rusqlite
,程序体积减少到了 326KB,非常 nice,体积减少了 75% 左右。
禁用不必要的 feature
这一点我的程序没有用到,不过我之前写的另一个工作流用到了。我但是是使用的 regex
库,禁用了他的默认 features
1 | regex = { version = "1.10", default-features = false, features = ["std"] } |
4. libstd 优化
Rust 的工具链自带了预编译的标准库(libstd),即每次编译 Rust 程序时直接把 libstd 静态链接进去,但是这样开发者就没的选了
xargo
提供了一个功能,允许我们自定义 std
。我们只需要新建一个 Xargo.toml
文件,然后写入想要优化的依赖就行
1 | # Xargo.toml |
然后编译时使用下面的命令:
1 | > xargo build --target x86_64-apple-darwin --release |
最终编译后的程序大小为 132KB
总结
总体来说,大部分的优化在 cargo book 中都有描述,有时间要看一下 cargo book. 然后就是编译时间、运行速度和编译体积之间大小的取舍,这还是要需要一定的经验的,需要慢慢积累
参考资料: