さびた大きなバイナリ?

免責事項:この記事は非常に無料の翻訳であり、いくつかの点はオリジナルとはかなり異なります



インターネットをサーフィンしてください、あなたはおそらくすでにRustについて聞いたことがあります。 すべての雄弁なレビューと宣伝の後、もちろん、あなたはこの奇跡に触れずにはいられませんでした。 最初のプログラムは他の何物にも見えませんでした。

fn main() { println!("Hello, world!"); }
      
      







コンパイルが完了すると、対応する実行可能ファイルが取得されます。

 $ rustc hello.rs $ du -h hello 632K hello
      
      





単純な印刷では632キロバイト?! Rustは、C / C ++を置き換える可能性のあるシステム言語として位置付けられていますよね? 最寄りの競合他社で同様のプログラムをチェックしてみませんか?

 $ cat hello.c #include <stdio.h> int main() { printf("Hello, World!\n"); } $ gcc hello.c -ohello $ du -h hello 6.7K hello
      
      







より安全でかさばるC ++ iostreamの結果はそれほど変わりません。

 $ cat hello.cpp #include <iostream> int main() { std::cout << "Hello, World!" << std::endl; } $ g++ hello.cpp -ohello $ du -h hello 8.3K hello
      
      







フラグ-O3 / -Osは、実際には最終サイズを変更しません





それでは、Rustの何が問題になっていますか?



多くの人がRust実行可能ファイルの異常なサイズに興味を持っているようで、この質問はまったく新しいものではありません。 たとえば、stackoverflow、または他の多くのこの質問を取ります 。 この問題を説明する記事やメモがまだなかったことは少し奇妙です。

すべての例は、元の記事とは異なり、貨物と安定ブランチを使用せずにLinux 4.4.14 x86_64でRust 1.11.0-nightly(1ab87b65a 2016-07-02)でテストされました。





最適化レベル



もちろん、経験豊富なプログラマなら誰でも、デバッグビルドはデバッグであり、多くの場合、そのサイズはリリースバージョンよりもはるかに大きいと断言できます。 この場合の錆は例外ではなく、ビルドパラメーターを構成できるほど柔軟です。 最適化レベルはgccに似ており、-C opt-level = xパラメーターを使用して設定できます。xの代わりに0から3の数値、またはサイズを最小化するためのsです。 さて、これが何をもたらすのか見てみましょう:

 $ rustc helloworld.rs -C opt-level=s $ du -h helloworld 630K helloworld
      
      







驚くべきことに、大きな変更はありません。 実際、これは、最適化がユーザーコードのみに適用され、既に組み込まれているRustランタイムには適用されないという事実によるものです。



リンク最適化(LTO)



Rustは、標準の動作に従って、その標準ライブラリ全体を各実行可能ファイルにリンクします。 愚かなリンカーは、ネットワークとの相互作用が本当に必要ないことを理解していないため、これを取り除くことができます。

実際、この動作には正当な理由があります。 おそらくご存知のように、CおよびC ++言語は各ファイルを個別にコンパイルします。 Rustは、コンパイルの単位が木枠である場合、少し異なります。 コンパイラは1つの大きなファイルで動作するだけなので、他のファイルからの関数呼び出しを最適化できないと推測することは難しくありません。

最初は、C / C ++では、コンパイラは各ファイルを個別に最適化しました。 時間が経つにつれて、リンク中に最適化テクノロジーが登場しました。 これにはかなり時間がかかり始めましたが、結果は以前よりもはるかに優れた実行可能ファイルになりました。 Rustでこの機能がどのように変化するかを見てみましょう。

 $ rustc helloworld.rs -C opt-level=s -C lto $ du -h helloworld 604K helloworld
      
      







それで、中身は何ですか?



おそらく最初に使用すべきものは、 GNU Binutilsスイートの悪名高い文字列ユーティリティです。 その結論は非常に大きい(約6,000行)ので、完全に持ってくることは意味がありません。 ここからがおもしろい部分です。

 $ strings helloworld capacity overflow attempted to calculate the remainder with a divisor of zero <jemalloc>: Error in atexit() <jemalloc>: Error in pthread_atfork() DW_AT_member DW_AT_explicit _ZN4core3fmt5Write9write_fmt17ha0cd161a5f40c4adE #  core::fmt::Write::write_fmt::ha0cd161a5f40c4ad _ZN4core6result13unwrap_failed17h072f7cd97aa67a9cE #  core::result::unwrap_failed::h072f7cd97aa67a9c
      
      







この結果に基づいて、いくつかの結論を出すことができます。

-標準ライブラリ全体がRust実行可能ファイルに静的にリンクされています。

-Rustはシステムアロケーターの代わりにjemallocを使用します

-スタックトレースに必要なlibbacktraceライブラリも静的にファイルにリンクされます

ご存知のように、これはすべて通常のprintlnではあまり必要ありません。 だから、それらをすべて取り除く時が来ました!



シンボルとlibbacktraceのデバッグ



簡単なものから始めましょう-実行可能ファイルからデバッグシンボルを削除します。

 $ strip hello # du -h hello 356K helloworld
      
      







非常に良い結果で、元のサイズのほぼ半分がデバッグシンボルで占められています。 この場合でも、パニックのようなエラーに関する読み取り可能な出力! 取得しません:

 $ cat helloworld.rs fn main() { panic!("Hello, world!"); } $ rustc helloworld.rs && RUST_BACKTRACE=1 ./helloworld thread 'main' panicked at 'Hello, world!', helloworld.rs:2 stack backtrace: 1: 0x556536e40e7f - std::sys::backtrace::tracing::imp::write::h6528da8103c51ab9 2: 0x556536e4327b - std::panicking::default_hook::_$u7b$$u7b$closure$u7d$$u7d$::hbe741a5cc3c49508 3: 0x556536e42eff - std::panicking::default_hook::he0146e6a74621cb4 4: 0x556536e3d73e - std::panicking::rust_panic_with_hook::h983af77c1a2e581b 5: 0x556536e3c433 - std::panicking::begin_panic::h0bf39f6d43ab9349 6: 0x556536e3c3a9 - helloworld::main::h6d97ffaba163087d 7: 0x556536e42b38 - std::panicking::try::call::h852b0d5f2eec25e4 8: 0x556536e4aadb - __rust_try 9: 0x556536e4aa7e - __rust_maybe_catch_panic 10: 0x556536e425de - std::rt::lang_start::hfe4efe1fc39e4a30 11: 0x556536e3c599 - main 12: 0x7f490342b740 - __libc_start_main 13: 0x556536e3c268 - _start 14: 0x0 - <unknown> $ strip helloworld && RUST_BACKTRACE=1 ./helloworld thread 'main' panicked at 'Hello, world!', helloworld.rs:2 stack backtrace: 1: 0x55ae4686ae7f - <unknown> ... 11: 0x55ae46866599 - <unknown> 12: 0x7f70a7cd9740 - __libc_start_main 13: 0x55ae46866268 - <unknown> 14: 0x0 - <unknown>
      
      







リンクからlibbacktrace全体を抽出しないと機能しません。標準ライブラリに強く関連付けられています。 ただし、 libunwindからパニックを解く必要はなく、 破棄することができます。 マイナーな改善が行われます:

 $ rustc helloworld.rs -C lto -C panic=abort -C opt-level=s $ du -h helloworld 592K helloworld
      
      







jemallocを削除します



標準ビルドのRustコンパイラは、ほとんどの場合、システムアロケーターの代わりにjemallocを使用します。 この動作の変更は非常に簡単です。マクロを挿入し、必要なアロケータークレートをインポートするだけです。

 #![feature(alloc_system)] extern crate alloc_system; fn main() { println!("Hello, world!"); }
      
      





 $ rustc helloworld.rs && du -h helloworld 235K helloworld $ strip helloworld && du -h helloworld 133K helloworld
      
      







小さな結論



シャーマニズムの最後の仕上げは、実行可能ファイルから標準ライブラリ全体を削除することです。 ほとんどの場合、これは必要ありません。また、 オフブック (または翻訳 )では、すべての手順が詳細に説明されています。 このようにして、Cの同等物に匹敵するサイズのファイルを取得できます。

また、ライブラリの標準セットのサイズは一定であり、リンクファイル自体(記事に記載)はコードによって増加しないため、サイズについて心配する必要はほとんどないことにも注意してください。 極端な場合、upxのようなコードパッカーをいつでも使用できます。



翻訳を手伝ってくれたロシア語を話すRust コミュニティに感謝します。



All Articles