[英]Why are Rust executables so huge?
剛剛找到 Rust 並閱讀了文檔的前兩章,我發現他們定義語言的方法和方式特別有趣。 所以我決定親自動手,從“Hello, world.”開始。
順便說一句,我是在 Windows 7 x64 上這樣做的。
fn main() {
println!("Hello, world!");
}
發布cargo build
並在targets\debug
中查看結果,我發現生成的.exe
為 3MB。 經過一些搜索(cargo 命令行標志的文檔很難找到......)我找到了--release
選項並創建了發布版本。 令我驚訝的是,.exe 的大小只變小了一個微不足道的量:2.99MB 而不是 3MB。
因此,承認我是 Rust 及其生態系統的新手,我的期望是系統編程語言會產生一些緊湊的東西。
任何人都可以詳細說明 Rust 正在編譯什么,它怎么可能從 3 行程序生成如此巨大的圖像? 它是編譯到虛擬機嗎? 是否有我錯過的 strip 命令(發布版本中的調試信息?)? 還有什么可以讓我們理解正在發生的事情嗎?
Rust 使用靜態鏈接來編譯它的程序,這意味着即使是最簡單的Hello world!
程序將被編譯成您的可執行文件。 這也包括 Rust 運行時。
要強制 Rust 動態鏈接程序,請使用命令行參數-C prefer-dynamic
; 這將導致文件大小小得多,但也需要 Rust 庫(包括其運行時)在運行時可供您的程序使用。 這基本上意味着你將需要為他們提供如果計算機沒有他們,占用了比你原來的靜態鏈接程序占用更多的空間。
為了可移植性,如果您要將程序分發給其他人,我建議您按照您一直在做的方式靜態鏈接 Rust 庫和運行時。
我沒有任何 Windows 系統可以嘗試,但在 Linux 上,靜態編譯的 Rust hello world 實際上比等效的 C 小。如果您看到大小差異很大,那可能是因為您正在鏈接 Rust 可執行文件靜態和 C 一個動態。
使用動態鏈接,您還需要考慮所有動態庫的大小,而不僅僅是可執行文件。
因此,如果您想將蘋果與蘋果進行比較,您需要確保兩者都是動態的,或者都是靜態的。 不同的編譯器會有不同的默認值,所以你不能僅僅依賴編譯器的默認值來產生相同的結果。
如果你有興趣,這是我的結果:
-rw-r--r-- 1 aij aij 63 Apr 5 14:26 printf.c -rwxr-xr-x 1 aij aij 6696 Apr 5 14:27 printf.dyn -rwxr-xr-x 1 aij aij 829344 Apr 5 14:27 printf.static -rw-r--r-- 1 aij aij 59 Apr 5 14:26 puts.c -rwxr-xr-x 1 aij aij 6696 Apr 5 14:27 puts.dyn -rwxr-xr-x 1 aij aij 829344 Apr 5 14:27 puts.static -rwxr-xr-x 1 aij aij 8712 Apr 5 14:28 rust.dyn -rw-r--r-- 1 aij aij 46 Apr 5 14:09 rust.rs -rwxr-xr-x 1 aij aij 661496 Apr 5 14:28 rust.static
這些是用 gcc (Debian 4.9.2-10) 4.9.2 和 rustc 1.0.0-nightly (d17d6e7f1 2015-04-02) (built 2015-04-03) 編譯的,都有默認選項和-static
for gcc和-C prefer-dynamic
rustc -C prefer-dynamic
。
我有兩個版本的 C hello world,因為我認為使用puts()
可能會鏈接更少的編譯單元。
如果你想嘗試在 Windows 上重現它,這里是我使用的來源:
printf.c:
#include <stdio.h>
int main() {
printf("Hello, world!\n");
}
puts.c:
#include <stdio.h>
int main() {
puts("Hello, world!");
}
rust.rs
fn main() {
println!("Hello, world!");
}
此外,請記住,不同數量的調試信息或不同的優化級別也會有所不同。 但是我希望如果您看到巨大的差異,那是由於靜態鏈接與動態鏈接。
有關減小 Rust 二進制文件大小的所有方法的概述,請參閱min-sized-rust
存儲庫。
當前減少二進制大小的高級步驟是:
jemalloc
)Cargo.toml
[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
cargo build --release
在發布模式下cargo build --release
strip
。 使用nightly
Rust 可以做更多事情,但我會將這些信息留在min-sized-rust
因為它會隨着時間的推移而變化,因為使用了不穩定的功能。
您還可以使用#![no_std]
刪除 Rust 的libstd
。 有關詳細信息,請參閱min-sized-rust
。
使用 Cargo 編譯時,可以使用動態鏈接:
cargo rustc --release -- -C prefer-dynamic
這將大大減少二進制文件的大小,因為它現在是動態鏈接的。
至少在 Linux 上,您還可以使用strip
命令剝離二進制符號:
strip target/release/<binary>
這將使大多數二進制文件的大小大約減半。
每晚安裝rust - rustup toolchain install nightly
, rustup default nightly
rustup toolchain install nightly
現在,在項目中的所有Cargo.toml 文件中進行這些更改。
在Cargo.toml頂部的[package]
之前添加cargo-features = ["strip"]
在底部,或者在[dependencies]
和[package]
添加,
[profile.release]
# strip = true # Automatically strip symbols from the binary.
opt-level = "z" # Optimize for size.
lto = true # Enable link time optimization
codegen-units = 1 # Reduce parallel code generation units
現在使用RUSTFLAGS='-C link-arg=-s' cargo build --release
我發現這些鏈接很有用 - https://collabora.com/news-and-blog/blog/2020/04/28/reducing-size-rust-gstreamer-plugin/和https://github.com/johnthagen/min -size-rust和https://arusahni.net/blog/2020/03/optimizing-rust-binary-size.html
可執行文件可能很大的原因之一是 Rust 編譯器處理泛型的方式。 Rust 不使用多態性,而是使用單態化。 這意味着編譯器為每個具體類型的每個泛型函數創建一個單獨的代碼副本(根據需要)。開發指南在這里概述了它。
例如,如果我使用像Vec
這樣的泛型類型,並且在我的代碼中有多個實例,如Vec<i32>
、 Vec<bool>
和Vec<String>
,那么編譯器將為上述泛型生成生成的代碼的 3 個單獨副本。 一個用於Vec<i32>
,一個用於Vec<bool>
,等等......
這種方法的缺點之一是編譯時間增加並且二進制文件更大。 從好的方面來說,編譯后的代碼應該運行得更快,因為在運行時不需要推斷任何東西,並且每個具體類型都有(應該有?)針對該具體類型的優化實現。
剛剛找到 Rust 並閱讀了文檔的前兩章,我發現他們定義語言的方法和方式特別有趣。 所以我決定弄濕我的手指並開始使用 Hello world ...
我是在 Windows 7 x64 上這樣做的,順便說一句。
fn main() {
println!("Hello, world!");
}
發出cargo build
並查看targets\\debug
中的結果,我發現生成的.exe
為 3MB。 經過一番搜索(很難找到有關貨物命令行標志的文檔...),我找到了--release
選項並創建了發布版本。 令我驚訝的是,.exe 的大小只變小了一點點:2.99MB 而不是 3MB。
所以,承認我是 Rust 及其生態系統的新手,我的期望是系統編程語言會產生一些緊湊的東西。
任何人都可以詳細說明 Rust 正在編譯的內容,它如何從 3 行程序生成如此巨大的圖像? 是編譯成虛擬機嗎? 是否有我錯過的 strip 命令(發布版本中的調試信息?)? 還有什么可能讓您了解正在發生的事情嗎?
這是一個功能,而不是一個錯誤!
您可以指定程序中使用的庫版本(在項目關聯的 Cargo.toml 文件中)(甚至是隱式版本)以確保庫版本兼容性。 另一方面,這需要將特定庫靜態鏈接到可執行文件,從而生成大型運行時映像。
嘿,現在已經不是 1978 年了 - 許多人的計算機內存超過 2 MB :-)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.