¿Por qué los ejecutables de Rust son tan grandes?
Encuentro particularmente interesante el enfoque y la forma en que definen el lenguaje en los dos primeros capítulos de la documentación. Así que decidí mojarme los dedos y comencé con "¡Hola mundo!".
Lo hice en Windows 7 x64, por cierto.
fn main() {
println!("Hello, world!");
}
Al emitir cargo build
y observar el resultado, targets\debug
encontré que el resultado .exe
era 3 MB. Después de algunas búsquedas (la documentación de los indicadores de la línea de comando de carga es difícil de encontrar...) encontré la --release
opción y creé la versión de lanzamiento. Para mi sorpresa, el tamaño del .exe sólo se ha reducido en una cantidad insignificante: 2,99 MB en lugar de 3 MB.
Mi expectativa habría sido que un lenguaje de programación de sistemas produciría algo compacto.
¿Alguien puede explicar en qué se está compilando Rust y cómo es posible que produzca imágenes tan grandes a partir de un programa de 3 líneas? ¿Se está compilando en una máquina virtual? ¿Hay algún comando de extracción que me perdí (¿información de depuración dentro de la versión de lanzamiento?)? ¿Algo más que pueda permitir entender lo que está pasando?
De forma predeterminada, el compilador de Rust optimiza la velocidad de ejecución, la velocidad de compilación y la facilidad de depuración (al incluir símbolos, por ejemplo), en lugar de un tamaño binario mínimo.
Para obtener una descripción general de todas las formas de reducir el tamaño de un binario de Rust, consulte mi min-sized-rust
repositorio de GitHub.
Los pasos actuales de alto nivel para reducir el tamaño binario son:
- Utilice Rust 1.32.0 o posterior (que no se incluye
jemalloc
de forma predeterminada) - Agregue lo siguiente 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
strip = true # Strip symbols from binary*
*
strip = true
requiere Rust 1.59+. En versiones anteriores de Rust, ejecútelostrip
manualmente en el binario resultante.
- Construir en modo de lanzamiento usando
cargo build --release
Se puede hacer más con nightly
Rust, pero dejaré esa información ya min-sized-rust
que cambia con el tiempo debido al uso de funciones inestables.
También puedes usarlo #![no_std]
para eliminar Rust's libstd
. Ver min-sized-rust
para más detalles.
Rust utiliza enlaces estáticos para compilar sus programas, lo que significa que todas las bibliotecas requeridas incluso por el Hello world!
programa más simple se compilarán en su ejecutable. Esto también incluye el tiempo de ejecución de Rust.
Para obligar a Rust a vincular programas dinámicamente, utilice los argumentos de la línea de comandos -C prefer-dynamic
; Esto dará como resultado un tamaño de archivo mucho más pequeño , pero también requerirá que las bibliotecas de Rust (incluido su tiempo de ejecución) estén disponibles para su programa en tiempo de ejecución. Básicamente, esto significa que deberá proporcionarlos si la computadora no los tiene, lo que ocupará más espacio del que ocupa su programa original vinculado estáticamente.
Para la portabilidad, le recomendaría que vincule estáticamente las bibliotecas de Rust y el tiempo de ejecución de la forma en que lo ha estado haciendo si alguna vez distribuyera sus programas a otros.
No tengo ningún sistema Windows para probar, pero en Linux, un Hola mundo de Rust compilado estáticamente es en realidad más pequeño que el C equivalente. Si ve una gran diferencia en tamaño, probablemente se deba a que está vinculando el ejecutable de Rust. estáticamente y el C de forma dinámica.
Con los enlaces dinámicos, también debes tener en cuenta el tamaño de todas las bibliotecas dinámicas, no solo el ejecutable.
Por lo tanto, si desea comparar manzanas con manzanas, debe asegurarse de que ambas sean dinámicas o estáticas. Diferentes compiladores tendrán diferentes valores predeterminados, por lo que no puede confiar simplemente en los valores predeterminados del compilador para producir el mismo resultado.
Si estás interesado, aquí están mis resultados:
-rw-r--r-- 1 aij aij 63 5 de abril 14:26 printf.c -rwxr-xr-x 1 aij aij 6696 5 de abril 14:27 printf.dyn -rwxr-xr-x 1 aij aij 829344 5 de abril 14:27 printf.static -rw-r--r-- 1 aij aij 59 5 abr 14:26 puts.c -rwxr-xr-x 1 aij aij 6696 5 de abril 14:27 puts.dyn -rwxr-xr-x 1 aij aij 829344 5 de abril 14:27 puts.static -rwxr-xr-x 1 aij aij 8712 5 de abril 14:28 Rust.dyn -rw-r--r-- 1 aij aij 46 5 de abril 14:09 Rust.rs -rwxr-xr-x 1 aij aij 661496 5 de abril 14:28 Rust.static
Estos fueron compilados con gcc (Debian 4.9.2-10) 4.9.2 y rustc 1.0.0-nightly (d17d6e7f1 2015-04-02) (compilado el 2015-04-03), ambos con opciones predeterminadas y con -static
for gcc y -C prefer-dynamic
for oxidado.
Tenía dos versiones de C hola mundo porque pensé que usarlo puts()
podría vincularse en menos unidades de compilación.
Si quieres intentar reproducirlo en Windows, aquí están las fuentes que utilicé:
printf.c:
#include <stdio.h>
int main() {
printf("Hello, world!\n");
}
pone.c:
#include <stdio.h>
int main() {
puts("Hello, world!");
}
óxido.rs
fn main() {
println!("Hello, world!");
}
Además, tenga en cuenta que diferentes cantidades de información de depuración o diferentes niveles de optimización también marcarían la diferencia. Pero espero que si ve una gran diferencia se deba a los enlaces estáticos versus dinámicos.