Por que os executáveis ​​do Rust são tão grandes?

153

Tendo encontrado Rust e tendo lido os dois primeiros capítulos da documentação, acho a abordagem e a maneira como eles definiram a linguagem particularmente interessante. Então eu decidi molhar meus dedos e comecei com Hello world ...

Eu fiz isso no Windows 7 x64, aliás.

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

Emitindo cargo builde analisando o resultado targets\debug, encontrei o resultado .exesendo 3 MB. Após algumas pesquisas (é difícil encontrar a documentação das bandeiras da linha de comando de carga ...), encontrei a --releaseopção e criei a versão build. Para minha surpresa, o tamanho do .exe ficou menor em uma quantidade insignificante: 2,99 MB em vez de 3 MB.

Então, confessando que sou novato no Rust e em seu ecossistema, minha expectativa seria que uma linguagem de programação de sistemas produzisse algo compacto.

Alguém pode elaborar sobre o que o Rust está compilando, como é possível que produza imagens tão grandes a partir de um programa de 3 linhas? Está compilando em uma máquina virtual? Existe um comando strip que eu perdi (informações de depuração dentro da compilação do release?)? Mais alguma coisa que permita entender o que está acontecendo?

BitTickler
fonte
4
Acho que o 3Mb contém não apenas o Hello World, mas também todo o ambiente necessário para a plataforma. O mesmo pode ser visto com o Qt. Isso não significa que, se você escrever um programa de 6 linhas, o tamanho será de 6 Mb. Ele permanecerá em 3Mb e crescerá muito lentamente depois disso.
Andrei Nikolaenko
8
@AndreiNikolaenko Estou ciente disso. Mas isso sugere que eles não lidam com bibliotecas como C, adicionando apenas o que é necessário para uma imagem ou que algo está acontecendo.
BitTickler
@ user2225104 Veja minha resposta, o RUST lida com bibliotecas da mesma maneira (ou similar) que C, mas, por padrão, C não compila bibliotecas estáticas em seu programa (pelo menos em C ++).
AStopher
1
Isso está desatualizado agora? Com a versão 1.35.0 do rustc e nenhuma opção cli, recebo um exe com 137kb de tamanho. Ele compila automaticamente vinculado dinamicamente agora ou algo mais aconteceu nesse meio tempo?
itmuckel

Respostas:

139

O Rust usa o link estático para compilar seus programas, o que significa que todas as bibliotecas exigidas pelo Hello world!programa mais simples serão compiladas no seu executável. Isso também inclui o tempo de execução do Rust.

Para forçar o Rust a vincular dinamicamente programas, use os argumentos da linha de comando -C prefer-dynamic; isso resultará em um tamanho de arquivo muito menor, mas também exigirá que as bibliotecas Rust (incluindo seu tempo de execução) estejam disponíveis para o seu programa em tempo de execução. Isso significa essencialmente que você precisará fornecê-los se o computador não os possuir, ocupando mais espaço do que o programa vinculado estaticamente original ocupa.

Para portabilidade, eu recomendo que você vincule estaticamente as bibliotecas e o tempo de execução do Rust da maneira que está fazendo, se quiser distribuir seus programas a outras pessoas.

AStopher
fonte
4
@ user2225104 Não tenho certeza sobre o Cargo, mas, de acordo com este relatório de erros no GitHub , infelizmente ainda não é possível.
AStopher 12/03/2015
2
Mas assim que você tem mais de 2 executáveis ferrugem em um sistema, a ligação dinâmica vai começar a poupar-lhe espaço ...
binki
15
Não acho que a vinculação estática explique o enorme OLÁ-MUNDO. Ele não deveria vincular apenas as partes das bibliotecas realmente usadas e o HELLO-WORLD não usa praticamente nada?
MaxB 29/08/16
8
BitTicklercargo rustc [--debug or --release] -- -C prefer-dynamic
Zach Mertes
3
@daboross Muito obrigado. Eu tenho rastreado esse RFC relacionado . É realmente uma pena, pois o Rust também tem como alvo a programação do sistema.
Franklin Yu
62

Não tenho nenhum sistema Windows para testar, mas no Linux, um mundo Rust hello compilado estaticamente é realmente menor que o equivalente a C. Se você está vendo uma enorme diferença de tamanho, provavelmente é porque está vinculando o executável Rust estaticamente e o C dinamicamente.

Com a vinculação dinâmica, você precisa levar em consideração o tamanho de todas as bibliotecas dinâmicas, não apenas o executável.

Portanto, se você quiser comparar maçãs com maçãs, verifique se as duas são dinâmicas ou estáticas. Compiladores diferentes terão padrões diferentes, então você não pode confiar apenas nos padrões do compilador para produzir o mesmo resultado.

Se você estiver interessado, aqui estão meus resultados:

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

Eles foram compilados com o gcc (Debian 4.9.2-10) 4.9.2 e rustc 1.0.0-nightly (d17d6e7f1 2015-04-02) (compilado em 2015-04-03), ambos com opções padrão e com o -staticgcc e -C prefer-dynamicpara rustc.

Eu tinha duas versões do mundo C hello, porque pensei que o uso puts()poderia estar vinculado em menos unidades de compilação.

Se você quiser tentar reproduzi-lo no Windows, aqui estão as fontes que eu usei:

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

Além disso, lembre-se de que diferentes quantidades de informações de depuração ou diferentes níveis de otimização também fazem diferença. Mas espero que, se você vê uma enorme diferença, seja devido à vinculação estática versus dinâmica.

aij
fonte
27
O gcc é inteligente o suficiente para fazer exatamente a impressão -> coloca a própria substituição, é por isso que os resultados são idênticos.
bluss
6
A partir de 2018, se você quiser uma comparação justa, lembre-se de "retirar" os executáveis, pois o Olá mundo executável Rust no meu sistema é de 5,3 MB, mas cai para menos de 10% quando você remove todos os símbolos de depuração e tal.
Matti Virkkunen
@MattiVirkkunen: Ainda é o caso em 2020; o tamanho natural parece menor (nem perto de 5,3M), mas a proporção de símbolos para código ainda é bastante extrema. A compilação de depuração, opções puramente padrão no Rust 1.34.0 no CentOS 7, removida strip -s, cai de 1.6M para 190K. A compilação do release (padrões mais opt-level='s', lto = truee panic = 'abort'para minimizar o tamanho) cai de 623K para 158K.
ShadowRanger
Como distinguir maçãs estáticas e dinâmicas? O último não parece saudável.
LF
30

Ao compilar com o Cargo, você pode usar o vínculo dinâmico:

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

Isso reduzirá drasticamente o tamanho do binário, pois agora está vinculado dinamicamente.

No Linux, pelo menos, você também pode remover o binário de símbolos usando o stripcomando:

strip target/release/<binary>

Isso reduzirá pela metade o tamanho da maioria dos binários.

Casper Skern Wilstrup
fonte
8
Apenas algumas estatísticas, versão padrão do hello world (linux x86_64). 3,5 M, com preferem-dinâmico B 8904, 6392 despojado B.
Zitrax
30

Para uma visão geral de todas as maneiras de reduzir o tamanho de um binário Rust, consulte o min-sized-rustrepositório.

As etapas atuais de alto nível para reduzir o tamanho binário são:

  1. Use Rust 1.32.0 ou mais recente (que não inclui jemallocpor padrão)
  2. Adicione o seguinte 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. Construa no modo de liberação usando cargo build --release
  2. Execute stripno binário resultante.

É possível fazer mais usando o nightlyRust, mas deixarei essas informações min-sized-rustcomo elas mudam ao longo do tempo devido ao uso de recursos instáveis.

Você também pode usar #![no_std]para remover o Rust's libstd. Veja min-sized-rustpara detalhes.

Fénix
fonte
-10

Este é um recurso, não um bug!

Você pode especificar as versões da biblioteca (no arquivo Cargo.toml associado ao projeto ) usadas no programa (mesmo as implícitas) para garantir a compatibilidade da versão da biblioteca. Por outro lado, isso requer que a biblioteca específica seja vinculada estaticamente ao executável, gerando imagens grandes em tempo de execução.

Ei, não é mais 1978 - muitas pessoas têm mais de 2 MB de RAM em seus computadores :-)

NPHighview
fonte
9
especificar as versões da biblioteca [...] requer que a biblioteca específica seja vinculada estaticamente - não, não. Existe muito código em que versões exatas das bibliotecas são vinculadas dinamicamente.
Shepmaster