【Rust 漫画】揭开 Hello World 二进制文件巨大之谜
前言
创意:张汉东 绘画:ChatGPT DALL•E3
创意来源:本人学习 Rust 过程中的一些踩坑、成长与思考。
如果大家喜欢,欢迎点赞、转发和赞赏,每一位读者对认可是我持续创作的动力。
漫画赏析
漫画解析
当 Rust 新人写下第一个 Hello World 程序时:
fn main(){
println!( "hello world, Rust");
}
在编译以后,看了二进制文件大小,多半会非常惊讶!你可能看到二进制文件大小高达 4 MB ,而 cpp 的 Hello world 二进制文件才 170kb,相比之下,Rust 二进制文件大小就太大了。
在 2015 年 Rust 1.0 发布之后的两三年内,这个问题曾在社区中引起了很大争议。
为什么 hello world 二进制文件如此巨大?
Rust语言在默认情况下产生较大的“Hello, World!”二进制文件的原因主要与其编译和链接策略有关。
静态链接:
默认情况下,Rust 产生的二进制是静态链接的,这意味着所有用到的库和函数都被直接包含在生成的文件中,而不是在运行时动态地加载。 这一策略有其好处:生成的二进制文件在不同的系统上更具有独立性和可移植性,因为它不依赖于外部的库文件。但这也导致了较大的文件大小。 即便是最简单的Rust程序,也会链接到Rust的标准库,也被称为 std
。这个库提供了许多基础的功能,如IO操作、线程管理、数据结构等。这些功能虽然在“Hello, World!”程序中可能未直接使用,但它们被包括在了编译的输出中。
其实 Rust的编译器(特别是其链接器)实际上是智能的,并采用了一个称为“树摇(tree shaking)”或“死代码消除(dead code elimination)”的过程,让它只会链接那些你的程序真正用到的库代码部分。
死代码消除:
Rust编译器会分析代码,确定哪些函数、变量和其他结构是未使用的。在编译和链接的过程中,所有未使用的代码(死代码)都不会出现在最终的二进制文件中。 Rust的标准库是模块化的。当你使用某个特定的模块或功能时,只有那部分代码会被拉入最终的二进制。例如,如果你的代码从标准库中只使用了 Vec
和println!
,那么只有与这些功能相关的代码部分会被包括进来。静态链接的影响:尽管 Rust 进行了死代码消除,但静态链接仍然可能会导致较大的二进制文件,因为所有必要的代码都被包含在单个文件中。这与动态链接相反,其中二进制文件依赖于外部的共享库。 其他因素:除了标准库和你的代码,还有其他因素可能影响编译输出的大小,例如调试信息、优化等级等。
那么该如何优化编译大小呢?
发布模式:通过使用
cargo build --release
,你可以告诉Rust进行更多的优化,并去除调试信息。这通常会显著减小生成的二进制文件大小。去除标准库:对于某些特定应用,如嵌入式系统编程,你可能不需要整个标准库。在这种情况下,你可以考虑使用
#![no_std]
属性来禁用标准库。使用
strip
命令:strip
是一个可以移除二进制文件中符号信息的工具,进一步减小文件大小。其他优化工具和策略:例如,使用
upx
可以进一步压缩生成的二进制文件。还有其他的Cargo插件和工具,如cargo-bloat
,可以帮助你识别和减小二进制文件大小。
社区也有人总结了如何优化编译文件大小:min-sized-rust[1]
其中也提供了一些优化配置:
[package]
name = "min-sized-rust"
version = "0.1.0"
authors = ["johnthagen <johnthagen@gmail.com>"]
license-file = "LICENSE.txt"
[dependencies]
[profile.release]
opt-level = "z" # Optimize for size.
lto = true # Enable Link Time Optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = "abort" # Abort on panic
strip = true # Automatically strip symbols from the binary.
最终效果还是可以的。
后记
Rust 编译时间、编译文件大小和性能优化,也一个不可能三角,需要根据实际情况和需求进行权衡。
参考资料
min-sized-rust: https://github.com/johnthagen/min-sized-rust