Pourquoi les exécutables Rust sont-ils si énormes?

153

Ayant juste trouvé Rust et ayant lu les deux premiers chapitres de la documentation, je trouve l'approche et la façon dont ils ont défini le langage particulièrement intéressantes. J'ai donc décidé de me mouiller les doigts et j'ai commencé avec Hello world ...

Je l'ai fait sur Windows 7 x64, btw.

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

En émettant cargo buildet en regardant le résultat, targets\debugj'ai trouvé que le résultat .exeétait 3MB. Après quelques recherches (la documentation des drapeaux de la ligne de commande de fret est difficile à trouver ...), j'ai trouvé l' --releaseoption et créé la version de version. À ma grande surprise, la taille du .exe n'est devenue plus petite que d'un montant insignifiant: 2,99 Mo au lieu de 3 Mo.

Donc, en admettant que je suis un débutant dans Rust et son écosystème, je m'attendais à ce qu'un langage de programmation système produise quelque chose de compact.

Quelqu'un peut-il expliquer ce que Rust compile, comment il peut être possible de produire des images aussi énormes à partir d'un programme 3 lignes? S'agit-il d'une compilation sur une machine virtuelle? Y a-t-il une commande de bande que j'ai manquée (informations de débogage dans la version de version?)? Y a-t-il autre chose qui pourrait permettre de comprendre ce qui se passe?

BitTickler
la source
4
Je pense que 3Mb contient non seulement Hello World, mais également tout l'environnement nécessaire à la plate-forme. La même chose peut être observée avec Qt. Cela ne signifie pas que si vous écrivez un programme de 6 lignes, la taille deviendra 6 Mb. Il restera à 3Mb et augmentera très lentement par la suite.
Andrei Nikolaenko
8
@AndreiNikolaenko J'en suis conscient. Mais cela laisse entendre qu'ils ne gèrent pas les bibliothèques comme le fait C, ajoutant uniquement ce qui est nécessaire à une image ou qu'il se passe autre chose.
BitTickler
@ user2225104 Voir ma réponse, RUST gère les bibliothèques de la même manière (ou similaire) que C, mais par défaut C ne compile pas les bibliothèques statiques dans votre programme (du moins, sur C ++).
AStopher
1
Est-ce obsolète maintenant? Avec la version 1.35.0 de rustc et aucune option cli, j'obtiens un exe d'une taille de 137kb. Est-ce qu'il compile automatiquement les liens dynamiques maintenant ou est-ce qu'il s'est passé autre chose entre-temps?
itmuckel

Réponses:

139

Rust utilise des liens statiques pour compiler ses programmes, ce qui signifie que toutes les bibliothèques requises par le Hello world!programme le plus simple seront compilées dans votre exécutable. Cela inclut également le runtime Rust.

Pour forcer Rust à lier dynamiquement des programmes, utilisez les arguments de ligne de commande -C prefer-dynamic; cela se traduira par une taille de fichier beaucoup plus petite mais exigera également que les bibliothèques Rust (y compris son exécution) soient disponibles pour votre programme au moment de l'exécution. Cela signifie essentiellement que vous devrez les fournir si l'ordinateur ne les a pas, occupant plus d' espace que votre programme d'origine lié statiquement.

Pour la portabilité, je vous recommande de lier statiquement les bibliothèques Rust et le runtime comme vous l'avez fait si vous deviez jamais distribuer vos programmes à d'autres.

AStopher
la source
4
@ user2225104 Je ne suis pas sûr de Cargo, mais d'après ce rapport de bogue sur GitHub , ce n'est malheureusement pas encore possible.
AStopher
2
Mais dès que vous avez plus de 2 exécutables rust sur un système, la liaison dynamique commencera à vous faire gagner de la place…
binki
15
Je ne pense pas que les liens statiques expliquent l'énorme HELLO-WORLD. Ne devrait-il pas seulement lier les parties des bibliothèques qui sont réellement utilisées, et HELLO-WORLD n'utilise pratiquement rien?
MaxB
8
BitTicklercargo rustc [--debug or --release] -- -C prefer-dynamic
Zach Mertes
3
@daboross Merci beaucoup. J'ai suivi cette RFC associée . C'est vraiment dommage car Rust cible également la programmation système.
Franklin Yu
62

Je n'ai pas de système Windows à essayer, mais sous Linux, un monde Rust hello compilé statiquement est en fait plus petit que l'équivalent C.Si vous voyez une énorme différence de taille, c'est probablement parce que vous liez l'exécutable Rust statiquement et le C dynamiquement.

Avec la liaison dynamique, vous devez également prendre en compte la taille de toutes les bibliothèques dynamiques, pas seulement l'exécutable.

Donc, si vous souhaitez comparer des pommes avec des pommes, vous devez vous assurer que les deux sont dynamiques ou les deux sont statiques. Différents compilateurs auront des valeurs par défaut différentes, vous ne pouvez donc pas vous fier uniquement aux valeurs par défaut du compilateur pour produire le même résultat.

Si vous êtes intéressé, voici mes résultats:

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

Ceux-ci ont été compilés avec gcc (Debian 4.9.2-10) 4.9.2 et rustc 1.0.0-nightly (d17d6e7f1 2015-04-02) (construit 2015-04-03), à la fois avec les options par défaut et avec -staticpour gcc et -C prefer-dynamicpour rustc.

J'avais deux versions du monde C hello parce que je pensais que l'utilisation puts()pouvait être liée à moins d'unités de compilation.

Si vous souhaitez essayer de le reproduire sous Windows, voici les sources que j'ai utilisées:

printf.c:

#include <stdio.h>
int main() {
  printf("Hello, world!\n");
}

met.c:

#include <stdio.h>
int main() {
  puts("Hello, world!");
}

rouille.rs

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

Gardez également à l'esprit que différentes quantités d'informations de débogage ou différents niveaux d'optimisation feraient également une différence. Mais je suppose que si vous voyez une énorme différence, c'est dû à la liaison statique par rapport à la liaison dynamique.

aij
la source
27
gcc est assez intelligent pour faire exactement le printf -> met la substitution elle-même, c'est pourquoi les résultats sont identiques.
bluss
6
À partir de 2018, si vous voulez une comparaison équitable, n'oubliez pas de "supprimer" les exécutables, car un exécutable Hello world Rust sur mon système représente un énorme 5,3 Mo, mais tombe à moins de 10% lorsque vous supprimez tous les symboles de débogage et tel.
Matti Virkkunen
@MattiVirkkunen: Toujours le cas en 2020; la taille naturelle semble plus petite (loin de 5,3 millions), mais le ratio symboles / code est encore assez extrême. La version de débogage, les options purement par défaut sur Rust 1.34.0 sur CentOS 7, dépouillées de strip -s, passe de 1,6M à 190K. La version de version (valeurs par défaut plus opt-level='s', lto = trueet panic = 'abort'pour réduire la taille) passe de 623 Ko à 158 Ko.
ShadowRanger le
Comment distinguer les pommes statiques et dynamiques? Ce dernier ne semble pas sain.
LF
30

Lors de la compilation avec Cargo, vous pouvez utiliser la liaison dynamique:

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

Cela réduira considérablement la taille du binaire, car il est maintenant lié dynamiquement.

Sur Linux, au moins, vous pouvez également supprimer le binaire des symboles à l'aide de la stripcommande:

strip target/release/<binary>

Cela réduira environ de moitié la taille de la plupart des binaires.

Casper Skern Wilstrup
la source
8
Juste quelques statistiques, version par défaut de hello world (linux x86_64). 3,5 M, avec 8904 B de préférence dynamique, dépouillé 6392 B.
Zitrax
30

Pour un aperçu de toutes les façons de réduire la taille d'un binaire Rust, consultez le min-sized-rustréférentiel.

Les étapes de haut niveau actuelles pour réduire la taille binaire sont:

  1. Utilisez Rust 1.32.0 ou plus récent (qui n'inclut pas jemallocpar défaut)
  2. Ajoutez ce qui suit à 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. Construire en mode version avec cargo build --release
  2. Exécutez stripsur le binaire résultant.

Il y a plus à faire avec nightlyRust, mais je laisserai ces informations min-sized-rustcar elles changent avec le temps en raison de l'utilisation de fonctionnalités instables.

Vous pouvez également utiliser #![no_std]pour supprimer Rust libstd. Voir min-sized-rustpour plus de détails.

phénix
la source
-10

C'est une fonctionnalité, pas un bug!

Vous pouvez spécifier les versions de bibliothèque (dans le fichier Cargo.toml associé au projet ) utilisées dans le programme (même les versions implicites) pour assurer la compatibilité des versions de bibliothèque. Ceci, d'autre part, nécessite que la bibliothèque spécifique soit liée statiquement à l'exécutable, générant de grandes images d'exécution.

Hé, ce n'est plus 1978 - beaucoup de gens ont plus de 2 Mo de RAM dans leurs ordinateurs :-)

NPHighview
la source
9
spécifier les versions de la bibliothèque [...] exige que la bibliothèque spécifique soit liée statiquement - non, ce n'est pas le cas. Il existe de nombreux codes où les versions exactes des bibliothèques sont liées dynamiquement.
Shepmaster