我们能把 x86_64 Linux Rust 二进制程序做多小?它能与纯汇编程序相媲美吗?让我们一探究竟!在此过程中,我们将了解 Linux 下的程序加载方式,并领略 Rust 的灵活性。

起点:3.6MB

创建一个项目:cargo new --vcs=none smallrs

替换 src/main.rs 为最简单的 Rust 程序:

fn main() {
		std::process::exit(42)
}

你可能会说这不是最简单的 Rust 程序:fn main() { } 会更简单。你是对的,但这只是因为 Rust 运行时为你做了什么。

通过从 Kerrisk 学习(译注:书 The Linux Programming Interface 的作者),你会知道程序只有两种终止方式:

如果你不退出,CPU 就会尝试在程序结束后运行指令,碰到无效指令并杀死你的程序。

为什么在 Rust 中不需要这样做呢?fn main() {} 的默认返回类型或任何不提供返回类型的函数的默认返回类型是 (). Termination 特性被实现为 (),这使得你的 main 函数默认返回 SUCCESS。

Rust 运行时会获取 SUCCESS 返回值。然后必须使用该值调用 exit syscall,可以是 rustc,也可以是 LLVM。反正到我们完成时,所有这些机器都会消失。

编译该程序,确认其正常运行,并检查其大小:

$ cargo build --release
   Compiling smallrs v0.1.0 (/home/graham/src/smallrs)
    Finished release [optimized] target(s) in 0.35s

$ ./target/release/smallrs
$ echo $?
42

$ ls -alh target/release/smallrs
-rwxr-xr-x 2 graham graham 3.6M Jul  1 09:10 target/release/smallrs

3.5 兆字节!这可一点都不好。让我们做得更好。

最大的收益是 strip:300 KiB

最简单也是最大的收益就是从二进制中剥离(strip)符号。你可以手动这样做:

$ ls -alh target/release/smallrs
-rwxr-xr-x 2 graham graham 3.6M Jul  1 09:10 target/release/smallrs

$ strip target/release/smallrs

$ ls -alh target/release/smallrs
-rwxr-xr-x 2 graham graham 303K Jul  1 09:50 target/release/smallrs

或者 cargo 可以代劳(这样更好)。在 Cargo.toml:

[profile.release]
strip = true