Warum sind die ausführbaren Dateien von Rust so groß?

153

Nachdem ich Rust gefunden und die ersten beiden Kapitel der Dokumentation gelesen habe, finde ich den Ansatz und die Art und Weise, wie sie die Sprache definiert haben, besonders interessant. Also beschloss ich, meine Finger nass zu machen und begann mit Hello World ...

Ich habe das übrigens unter Windows 7 x64 gemacht.

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

Als ich cargo builddas Ergebnis herausgab und betrachtete, stellte targets\debugich fest, dass das Ergebnis .exe3 MB betrug. Nach einigem Suchen (Dokumentation der Frachtbefehlszeilenflags ist schwer zu finden ...) habe ich die --releaseOption gefunden und den Release-Build erstellt. Zu meiner Überraschung ist die EXE-Größe nur unwesentlich kleiner geworden: 2,99 MB statt 3 MB.

Als ich gestand, dass ich ein Neuling in Rust und seinem Ökosystem bin, hätte ich erwartet, dass eine Systemprogrammiersprache etwas Kompaktes hervorbringen würde.

Kann jemand näher erläutern, woraus Rust kompiliert und wie es möglich sein kann, dass aus einem 3-Liner-Programm so große Bilder erzeugt werden? Wird es zu einer virtuellen Maschine kompiliert? Gibt es einen Strip-Befehl, den ich verpasst habe (Debug-Informationen im Release-Build?)? Gibt es noch etwas, das es erlauben könnte zu verstehen, was los ist?

BitTickler
quelle
4
Ich denke, 3 MB enthält nicht nur Hello World, sondern auch die gesamte für die Plattform erforderliche Umgebung. Das gleiche gilt für Qt. Das bedeutet nicht, dass beim Schreiben eines 6-Zeilen-Programms die Größe 6 MB beträgt. Es bleibt bei 3 MB und wächst danach sehr langsam.
Andrei Nikolaenko
8
@AndreiNikolaenko Das ist mir bewusst. Dies deutet jedoch darauf hin, dass sie Bibliotheken entweder nicht wie C behandeln und nur das hinzufügen, was zu einem Bild erforderlich ist, oder dass etwas anderes vor sich geht.
BitTickler
@ user2225104 Siehe meine Antwort, RUST behandelt Bibliotheken auf die gleiche (oder ähnliche) Weise wie C, aber standardmäßig kompiliert C keine statischen Bibliotheken in Ihr Programm (zumindest unter C ++).
AStopher
1
Ist das jetzt veraltet? Mit rustc Version 1.35.0 und ohne CLI-Optionen bekomme ich eine Exe mit einer Größe von 137 KB. Wird es jetzt automatisch dynamisch verknüpft kompiliert oder ist in der Zwischenzeit etwas anderes passiert?
Itmuckel

Antworten:

139

Rust verwendet statische Verknüpfungen, um seine Programme zu kompilieren. Dies bedeutet, dass alle Bibliotheken, die selbst das einfachste Hello world!Programm benötigt , in Ihre ausführbare Datei kompiliert werden. Dies schließt auch die Rust-Laufzeit ein.

Verwenden Sie die Befehlszeilenargumente, um Rust zu zwingen, Programme dynamisch zu verknüpfen -C prefer-dynamic. Dies führt zu einer viel kleineren Dateigröße , erfordert jedoch auch, dass die Rust-Bibliotheken (einschließlich ihrer Laufzeit) zur Laufzeit für Ihr Programm verfügbar sind. Dies bedeutet im Wesentlichen, dass Sie sie bereitstellen müssen, wenn der Computer sie nicht hat, was mehr Speicherplatz beansprucht, als Ihr ursprüngliches statisch verknüpftes Programm beansprucht.

Aus Gründen der Portabilität würde ich empfehlen, die Rust-Bibliotheken und die Laufzeit statisch so zu verknüpfen, wie Sie es getan haben, wenn Sie Ihre Programme jemals an andere verteilen würden.

AStopher
quelle
4
@ user2225104 Unsicher über Fracht, aber laut diesem Fehlerbericht auf GitHub ist dies leider noch nicht möglich.
AStopher
2
Aber sobald Sie mehr als 2 ausführbare
Rostdateien
15
Ich glaube nicht, dass statische Verknüpfungen die riesige HELLO-WORLD erklären. Sollte es nicht nur in den Teilen der Bibliotheken verlinken, die tatsächlich verwendet werden, und HELLO-WORLD verwendet praktisch nichts?
MaxB
8
BitTicklercargo rustc [--debug or --release] -- -C prefer-dynamic
Zach Mertes
3
@daboross Vielen Dank. Ich habe diesen verwandten RFC verfolgt . Schade, denn Rust zielt auch auf die Systemprogrammierung ab.
Franklin Yu
62

Ich habe keine Windows-Systeme zum Anprobieren, aber unter Linux ist eine statisch kompilierte Rust-Hallo-Welt tatsächlich kleiner als das entsprechende C. Wenn Sie einen großen Größenunterschied feststellen, liegt dies wahrscheinlich daran, dass Sie die ausführbare Rust-Datei verknüpfen statisch und das C dynamisch.

Bei der dynamischen Verknüpfung müssen Sie auch die Größe aller dynamischen Bibliotheken berücksichtigen, nicht nur die ausführbare Datei.

Wenn Sie also Äpfel mit Äpfeln vergleichen möchten, müssen Sie sicherstellen, dass entweder beide dynamisch oder beide statisch sind. Unterschiedliche Compiler haben unterschiedliche Standardeinstellungen, sodass Sie sich nicht einfach auf die Compiler-Standardeinstellungen verlassen können, um das gleiche Ergebnis zu erzielen.

Wenn Sie interessiert sind, hier sind meine Ergebnisse:

-rw-r - r-- 1 aij aij 63 Apr 5 14:26 printf.c
-rwxr-xr-x 1 aij aij 6696 5. April 14:27 printf.dyn
-rwxr-xr-x 1 aij aij 829344 5. April 14:27 printf.static
-rw-r - r-- 1 aij aij 59 Apr 5 14:26 puts.c
-rwxr-xr-x 1 aij aij 6696 5. April 14:27 puts.dyn
-rwxr-xr-x 1 aij aij 829344 5. April 14:27 puts.static
-rwxr-xr-x 1 aij aij 8712 5. April 14:28 rust.dyn
-rw-r - r-- 1 aij aij 46 Apr 5 14:09 rust.rs
-rwxr-xr-x 1 aij aij 661496 5. April 14:28 rust.static

Diese wurden mit gcc (Debian 4.9.2-10) 4.9.2 und rustc 1.0.0-nightly (d17d6e7f1 2015-04-02) (erstellt am 03.04.2015) kompiliert, sowohl mit Standardoptionen als auch mit -staticfür gcc und -C prefer-dynamicfür rustc.

Ich hatte zwei Versionen der C-Hallo-Welt, weil ich dachte, die Verwendung puts()könnte weniger Kompilierungseinheiten enthalten.

Wenn Sie versuchen möchten, es unter Windows zu reproduzieren, habe ich folgende Quellen verwendet:

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!");
}

Beachten Sie auch, dass unterschiedliche Mengen an Debugging-Informationen oder unterschiedliche Optimierungsstufen ebenfalls einen Unterschied machen würden. Aber ich gehe davon aus, dass ein großer Unterschied auf statische und dynamische Verknüpfungen zurückzuführen ist.

aij
quelle
26
gcc ist klug genug, um genau die printf -> setzt die Substitution selbst durchzuführen, deshalb sind die Ergebnisse identisch.
Bluss
6
Ab 2018, wenn Sie einen fairen Vergleich wünschen, denken Sie daran, die ausführbaren Dateien zu "entfernen", da die ausführbare Rust-Datei auf meinem System satte 5,3 MB beträgt, aber auf weniger als 10% abfällt, wenn Sie alle Debug-Symbole und entfernen eine solche.
Matti Virkkunen
@ MattiVirkkunen: Im Jahr 2020 immer noch der Fall; Die natürliche Größe scheint kleiner zu sein (bei weitem nicht 5,3 Millionen), aber das Verhältnis von Symbolen zu Code ist immer noch ziemlich extrem. Der Debug-Build, bei dem es sich um reine Standardoptionen für Rust 1.34.0 unter CentOS 7 handelt strip -s, sinkt von 1,6 Millionen auf 190 KB. Der Release - Build (Standard Plus opt-level='s', lto = trueund panic = 'abort'Größe zu minimieren) fällt von 623K bis 158K.
ShadowRanger
Wie unterscheidet man statische und dynamische Äpfel? Letzteres klingt nicht gesund.
LF
30

Beim Kompilieren mit Cargo können Sie dynamische Verknüpfungen verwenden:

cargo rustc --release -- -C prefer-dynamic

Dadurch wird die Größe der Binärdatei drastisch reduziert, da sie jetzt dynamisch verknüpft ist.

Zumindest unter Linux können Sie die Binärdatei der Symbole auch mit dem folgenden stripBefehl entfernen:

strip target/release/<binary>

Dadurch wird die Größe der meisten Binärdateien ungefähr halbiert.

Casper Skern Wilstrup
quelle
8
Nur ein paar Statistiken, Standardversion von Hello World (Linux x86_64). 3,5 m, mit bevorzugter Dynamik 8904 B, 6392 B abgestreift.
Zitrax
30

Eine Übersicht über alle Möglichkeiten zum Reduzieren der Größe einer Rust-Binärdatei finden Sie im min-sized-rustRepository.

Die aktuellen Schritte auf hoher Ebene zur Reduzierung der Binärgröße sind:

  1. Verwenden Sie Rust 1.32.0 oder neuer ( jemallocstandardmäßig nicht enthalten ).
  2. Fügen Sie Folgendes hinzu: 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
  1. Build im Release-Modus mit cargo build --release
  2. Führen Sie stripdie resultierende Binärdatei aus.

Mit nightlyRust kann noch mehr getan werden , aber ich werde diese Informationen belassen, min-sized-rustda sie sich im Laufe der Zeit aufgrund der Verwendung instabiler Funktionen ändern.

Sie können auch #![no_std]Rusts entfernen libstd. Siehe min-sized-rustfür Details.

Phönix
quelle
-10

Dies ist eine Funktion, kein Fehler!

Sie können die im Programm verwendeten Bibliotheksversionen (in der dem Projekt zugeordneten Datei Cargo.toml ) angeben (auch die impliziten), um die Kompatibilität der Bibliotheksversionen sicherzustellen. Dies erfordert andererseits, dass die spezifische Bibliothek statisch mit der ausführbaren Datei verknüpft ist, wodurch große Laufzeitbilder erzeugt werden.

Hey, es ist nicht mehr 1978 - viele Leute haben mehr als 2 MB RAM in ihren Computern :-)

NPHighview
quelle
9
Für die Angabe der Bibliotheksversionen [...] muss die jeweilige Bibliothek statisch verknüpft sein - nein, dies ist nicht der Fall. Es gibt viel Code, in dem genaue Versionen von Bibliotheken dynamisch verknüpft sind.
Shepmaster