Perché i file eseguibili di Rust sono così enormi?


153

Dopo aver trovato Rust e aver letto i primi due capitoli della documentazione, trovo particolarmente interessante l'approccio e il modo in cui hanno definito la lingua. Così ho deciso di bagnarmi le dita e ho iniziato con Hello world ...

L'ho fatto su Windows 7 x64, a proposito.

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

Emettendo cargo builde guardando il risultato in targets\debugho trovato il risultante .exeessere 3 MB. Dopo alcune ricerche (la documentazione delle bandiere della riga di comando del carico è difficile da trovare ...) Ho trovato l' --releaseopzione e ho creato la build di rilascio. Con mia sorpresa, la dimensione .exe è diventata più piccola di una quantità insignificante: 2,99 MB anziché 3 MB.

Quindi, confessando che sono un principiante di Rust e del suo ecosistema, la mia aspettativa sarebbe stata che un linguaggio di programmazione di sistemi avrebbe prodotto qualcosa di compatto.

Qualcuno può approfondire ciò che Rust sta compilando, come è possibile che produca immagini così grandi da un programma di 3 linee? Si sta compilando su una macchina virtuale? C'è un comando strip che ho perso (informazioni di debug all'interno della build di rilascio?)? Qualcos'altro che potrebbe consentire di capire cosa sta succedendo?


4
Penso che 3Mb contenga non solo Hello World, ma anche tutto l'ambiente necessario per la piattaforma. La stessa cosa può essere vista con Qt. Ciò non significa che se si scrive un programma a 6 righe la dimensione diventerà 6 Mb. Rimarrà a 3 Mb e successivamente crescerà molto lentamente.
Andrei Nikolaenko,

8
@AndreiNikolaenko Ne sono consapevole. Ma ciò suggerisce che o non gestiscono le librerie come fa C, aggiungendo solo ciò che è richiesto a un'immagine o che sta succedendo qualcos'altro.
BitTickler

@ user2225104 Vedi la mia risposta, RUST gestisce le librerie nello stesso modo (o simile) di C, ma per impostazione predefinita C non compila le librerie statiche nel tuo programma (almeno su C ++).
Dopo il


1
È obsoleto adesso? Con la versione 1.35.0 di Rustc e nessuna opzione di cli ho un exe di 137kb di dimensione. Si compila automaticamente collegato dinamicamente ora o è successo qualcos'altro nel frattempo?
Itmuckel,

Risposte:


139

Rust utilizza collegamenti statici per compilare i suoi programmi, il che significa che tutte le librerie richieste anche dal Hello world!programma più semplice verranno compilate nel tuo eseguibile. Ciò include anche il runtime Rust.

Per forzare Rust a collegare dinamicamente i programmi, utilizzare gli argomenti della riga di comando -C prefer-dynamic; questo comporterà una dimensione del file molto più piccola ma richiederà anche che le librerie Rust (incluso il suo runtime) siano disponibili per il tuo programma in fase di runtime. Ciò significa essenzialmente che dovrai fornirli se il computer non li possiede, occupando più spazio di quanto occupi il tuo programma staticamente collegato originale.

Per la portabilità, ti consiglio di collegare staticamente le librerie Rust e il runtime come hai fatto se dovessi distribuire i tuoi programmi ad altri.


4
@ user2225104 Non sei sicuro di Cargo, ma secondo questo bug report su GitHub , sfortunatamente non è ancora possibile.
Dopo il

2
Ma non appena avrai più di 2 eseguibili ruggine su un sistema, il collegamento dinamico inizierà a farti risparmiare spazio ...
binki

15
Non credo che i collegamenti statici spieghino l'enorme CIAO-MONDO. Non dovrebbe collegarsi solo alle parti delle librerie che sono effettivamente utilizzate e HELLO-WORLD non usa praticamente nulla?
MaxB

8
BitTicklercargo rustc [--debug or --release] -- -C prefer-dynamic
Zach Mertes,

3
@daboross Grazie mille. Ho seguito questo RFC correlato . È davvero un peccato poiché Rust si rivolge anche alla programmazione del sistema.
Franklin Yu,

62

Non ho alcun sistema Windows da provare, ma su Linux, un mondo di Rust ciao compilato staticamente è in realtà più piccolo dell'equivalente C. Se stai vedendo un'enorme differenza di dimensioni, è probabilmente perché stai collegando l'eseguibile di Rust staticamente e quello C dinamicamente.

Con il collegamento dinamico, è necessario tenere conto anche delle dimensioni di tutte le librerie dinamiche, non solo dell'eseguibile.

Quindi, se si desidera confrontare le mele con le mele, è necessario assicurarsi che entrambi siano dinamici o entrambi statici. Compilatori diversi avranno impostazioni predefinite diverse, quindi non puoi semplicemente fare affidamento sulle impostazioni predefinite del compilatore per produrre lo stesso risultato.

Se sei interessato, ecco i miei risultati:

-rw-r - r-- 1 aij aij 63 5 aprile 14:26 printf.c
-rwxr-xr-x 1 aij aij 6696 5 aprile 14:27 printf.dyn
-rwxr-xr-x 1 aij aij 829344 5 aprile 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 aprile 14:27 put.dyn
-rwxr-xr-x 1 aij aij 829344 5 aprile 14:27 puts.static
-rwxr-xr-x 1 aij aij 8712 5 aprile 14:28 rust.dyn
-rw-r - r-- 1 aij aij 46 aprile 5 14:09 rust.rs
-rwxr-xr-x 1 aij aij 661496 5 aprile 14:28 rust.static

Questi sono stati compilati con gcc (Debian 4.9.2-10) 4.9.2 e rustc 1.0.0-nightly (d17d6e7f1 2015-04-02) (costruito 2015-04-03), entrambi con opzioni predefinite e con -staticper gcc e -C prefer-dynamicper rustc.

Avevo due versioni del mondo C Hello perché pensavo che usare puts()avrebbe potuto collegarsi in un minor numero di unità di compilazione.

Se vuoi provare a riprodurlo su Windows, ecco le fonti che ho usato:

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

Inoltre, tieni presente che anche diverse quantità di informazioni di debug o diversi livelli di ottimizzazione farebbero la differenza. Ma mi aspetto che se vedi un'enorme differenza, è dovuta al collegamento statico vs. dinamico.


27
gcc è abbastanza intelligente da fare esattamente printf -> mette la sostituzione stessa, ecco perché i risultati sono identici.
bluss

6
A partire dal 2018 se si desidera un confronto equo, ricordarsi di "eliminare" gli eseguibili, dato che un eseguibile di ruggine sul mio sistema è un enorme 5,3 MB, ma scende a meno del 10% quando si rimuovono tutti i simboli di debug e come.
Matti Virkkunen,

@MattiVirkkunen: ancora il caso nel 2020; la dimensione naturale sembra più piccola (da nessuna parte vicino a 5,3 M), ma il rapporto tra simboli e codice è ancora piuttosto estremo. La build di debug, opzioni puramente predefinite su Rust 1.34.0 su CentOS 7, strip -srimossa con , scende da 1.6M a 190K. La build di rilascio (impostazioni predefinite plus opt-level='s', lto = truee panic = 'abort'per ridurre al minimo le dimensioni) scende da 623K a 158K.
ShadowRanger

Come distinguere le mele statiche e dinamiche? Quest'ultimo non suona sano.
LF

30

Durante la compilazione con Cargo, è possibile utilizzare il collegamento dinamico:

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

Ciò ridurrà drasticamente le dimensioni del file binario, poiché ora è collegato in modo dinamico.

Su Linux, almeno, puoi anche rimuovere il binario di simboli usando il stripcomando:

strip target/release/<binary>

Ciò dimezzerà approssimativamente le dimensioni della maggior parte dei file binari.


8
Solo alcune statistiche, versione predefinita di Hello World (Linux x86_64). 3,5 M, con 8904 B preferito-dinamico, spogliato 6392 B.
Zitrax

30

Per una panoramica di tutti i modi per ridurre le dimensioni di un binario Rust, consultare il min-sized-rustrepository.

Gli attuali passaggi di alto livello per ridurre le dimensioni binarie sono:

  1. Usa Rust 1.32.0 o più recente (che non include jemallocper impostazione predefinita)
  2. Aggiungi quanto segue a 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. Costruisci in modalità di rilascio usando cargo build --release
  2. Esegui stripsul binario risultante.

C'è molto di più che si può fare usando nightlyRust, ma lascerò quelle informazioni min-sized-rustmentre cambiano nel tempo a causa dell'uso di funzionalità instabili.

Puoi anche usare #![no_std]per rimuovere Rust libstd. Vedi min-sized-rustper i dettagli.


-10

Questa è una funzione, non un bug!

È possibile specificare le versioni della libreria (nel file Cargo.toml associato al progetto ) utilizzate nel programma (anche quelle implicite) per garantire la compatibilità della versione della libreria. Ciò, d'altra parte, richiede che la libreria specifica sia staticamente collegata all'eseguibile, generando immagini di runtime di grandi dimensioni.

Ehi, non è più il 1978 - molte persone hanno più di 2 MB di RAM nei loro computer :-)


9
specificare le versioni della libreria [...] richiede che la libreria specifica sia collegata staticamente - no, non lo fa. Esiste un sacco di codice in cui le versioni esatte delle librerie sono collegate dinamicamente.
Shepmaster,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.