¿Por qué los ejecutables de Rust son tan grandes?

153

Solo por haber encontrado Rust y haber leído los dos primeros capítulos de la documentación, encuentro el enfoque y la forma en que definieron el lenguaje particularmente interesante. Así que decidí mojarme los dedos y comencé con Hello world ...

Lo hice en Windows 7 x64, por cierto.

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

Emitiendo cargo buildy mirando el resultado targets\debug, encontré que el resultado .exees 3MB. Después de algunas búsquedas (la documentación de las banderas de línea de comando de carga es difícil de encontrar ...) encontré la --releaseopción y creé la versión de lanzamiento. Para mi sorpresa, el tamaño .exe solo se ha reducido en una cantidad insignificante: 2.99MB en lugar de 3MB.

Entonces, confesando que soy un novato en Rust y su ecosistema, mi expectativa habría sido que un lenguaje de programación de sistemas produciría algo compacto.

¿Alguien puede dar más detalles sobre qué está compilando Rust, cómo puede ser posible que produzca imágenes tan grandes de un programa de 3 líneas? ¿Se está compilando en una máquina virtual? ¿Hay un comando de tira que me perdí (información de depuración dentro de la compilación de lanzamiento)? ¿Algo más que pueda permitir entender lo que está pasando?

BitTickler
fuente
44
Creo que 3Mb contiene no solo Hello World, sino también todo el entorno necesario para la plataforma. Lo mismo se puede ver con Qt. Eso no significa que si escribe un programa de 6 líneas, el tamaño será de 6 Mb. Se mantendrá en 3Mb y crecerá muy lentamente después de eso.
Andrei Nikolaenko
8
@AndreiNikolaenko Soy consciente de eso. Pero esto sugiere que o bien no manejan bibliotecas como lo hace C, agregando solo lo que se requiere a una imagen o que algo más está sucediendo.
BitTickler
@ user2225104 Vea mi respuesta, RUST maneja las bibliotecas de la misma manera (o similar) que C, pero de manera predeterminada C no compila bibliotecas estáticas en su programa (al menos, en C ++).
AStopher
1
¿Está desactualizado ahora? Con la versión rustc 1.35.0 y sin opciones de cli, obtengo un exe de 137 kb de tamaño. ¿Se compila automáticamente vinculado dinámicamente ahora o sucedió algo más mientras tanto?
itmuckel

Respuestas:

139

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 forzar a Rust a vincular dinámicamente programas, use 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 Rust (incluido su tiempo de ejecución) estén disponibles para su programa en tiempo de ejecución. Esto significa esencialmente que deberá proporcionarlos si la computadora no los tiene, ocupando más espacio del que ocupa su programa original vinculado estáticamente.

Para la portabilidad, le recomiendo que enlace estáticamente las bibliotecas Rust y el tiempo de ejecución de la forma en que lo ha estado haciendo si alguna vez distribuyera sus programas a otros.

AStopher
fuente
44
@ user2225104 No estoy seguro acerca de Cargo, pero de acuerdo con este informe de error en GitHub , desafortunadamente esto aún no es posible.
AStopher
2
Pero tan pronto como tenga más de 2 ejecutables
oxidados
15
No creo que los enlaces estáticos expliquen el enorme HOLA-MUNDO. ¿No debería vincular solo las partes de las bibliotecas que realmente se usan, y HELLO-WORLD no usa prácticamente nada?
MaxB
8
BitTicklercargo rustc [--debug or --release] -- -C prefer-dynamic
Zach Mertes
3
@daboross Muchas gracias. He estado siguiendo este RFC relacionado . Es realmente una pena, ya que Rust también apunta a la programación del sistema.
Franklin Yu
62

No tengo ningún sistema de Windows para probar, pero en Linux, un mundo de Rust hello compilado estáticamente es en realidad más pequeño que el equivalente C. Si está viendo una gran diferencia de tamaño, probablemente sea porque está vinculando el ejecutable de Rust estáticamente y el C dinámicamente.

Con el enlace dinámico, también debe tener en cuenta el tamaño de todas las bibliotecas dinámicas, no solo el ejecutable.

Entonces, si desea comparar manzanas con manzanas, debe asegurarse de que ambas sean dinámicas o ambas sean estáticas. Los diferentes compiladores tendrán valores predeterminados diferentes, por lo que no puede confiar solo 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 abr 5 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 de abril 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 abr 5 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) (construido 2015-04-03), ambos con opciones predeterminadas y con -staticgcc y -C prefer-dynamicpara rustc.

Tenía dos versiones del mundo C hello porque pensé que usar puts()podría enlazar en menos unidades de compilación.

Si quieres intentar reproducirlo en Windows, estas son las fuentes que utilicé:

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

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 ves una gran diferencia se deba a la vinculación estática frente a la dinámica.

aij
fuente
27
gcc es lo suficientemente inteligente como para hacer exactamente el printf -> pone la sustitución en sí, por eso los resultados son idénticos.
rubor
66
A partir de 2018, si desea una comparación justa, recuerde "quitar" los ejecutables, ya que un ejecutable de Hello World Rust en mi sistema tiene una enorme cantidad de 5.3 MB, pero se reduce a menos del 10% de eso cuando elimina todos los símbolos de depuración y tal.
Matti Virkkunen
@MattiVirkkunen: sigue siendo el caso en 2020; el tamaño natural parece más pequeño (en ninguna parte cerca de 5.3M), pero la relación de símbolos a código sigue siendo bastante extrema. La construcción de depuración, opciones puramente predeterminadas en Rust 1.34.0 en CentOS 7, despojado de strip -s, cae de 1.6M a 190K. La versión de lanzamiento (valores predeterminados más opt-level='s', lto = truey panic = 'abort'para minimizar el tamaño) cae de 623K a 158K.
ShadowRanger
¿Cómo distinguir las manzanas estáticas y dinámicas? Este último no suena saludable.
LF
30

Al compilar con Cargo, puede usar la vinculación dinámica:

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

Esto reducirá drásticamente el tamaño del binario, ya que ahora está vinculado dinámicamente.

En Linux, al menos, también puede quitar el binario de símbolos con el stripcomando:

strip target/release/<binary>

Esto reducirá aproximadamente a la mitad el tamaño de la mayoría de los binarios.

Casper Skern Wilstrup
fuente
8
Solo algunas estadísticas, versión de lanzamiento predeterminada de hello world (linux x86_64). 3.5 M, con 8904 B de preferencia dinámica, despojado 6392 B.
Zitrax
30

Para obtener una descripción general de todas las formas de reducir el tamaño de un binario Rust, consulte el min-sized-rustrepositorio.

Los pasos actuales de alto nivel para reducir el tamaño binario son:

  1. Use Rust 1.32.0 o más reciente (que no incluye jemallocpor defecto)
  2. 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
  1. Construir en modo de lanzamiento usando cargo build --release
  2. Ejecutar stripen el binario resultante.

Se puede hacer más con nightlyRust, pero dejaré esa información a min-sized-rustmedida que cambie con el tiempo debido al uso de características inestables.

También puedes usar #![no_std]para eliminar Rust's libstd. Ver min-sized-rustpara más detalles.

fénix
fuente
-10

¡Esto es una característica, no un error!

Puede especificar las versiones de la biblioteca (en el archivo Cargo.toml asociado al proyecto ) utilizadas en el programa (incluso las implícitas) para garantizar la compatibilidad de la versión de la biblioteca. Esto, por otro lado, requiere que la biblioteca específica esté vinculada estáticamente al ejecutable, generando grandes imágenes en tiempo de ejecución.

Hola, ya no es 1978, muchas personas tienen más de 2 MB de RAM en sus computadoras :-)

NPHighview
fuente
9
especificar las versiones de la biblioteca [...] requiere que la biblioteca específica esté vinculada estáticamente , no, no lo hace. Existe una gran cantidad de código donde las versiones exactas de las bibliotecas están vinculadas dinámicamente.
Shepmaster