Почему исполняемые файлы Rust такие большие?

153

Просто найдя Rust и прочитав первые две главы документации, я нахожу подход и способ определения языка особенно интересными. Поэтому я решил намочить пальцы и начал с Hello world ...

Я сделал это на Windows 7 x64, кстати.

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

Выдав cargo buildи посмотрев на результат, targets\debugя обнаружил, .exeчто получилось 3MB. После некоторого поиска (документация флагов грузовой командной строки трудно найти ...) я нашел --releaseвариант и создал сборку релиза. К моему удивлению, размер .exe уменьшился лишь на незначительную величину: 2,99 МБ вместо 3 МБ.

Итак, признавшись, что я новичок в Rust и его экосистеме, я ожидал, что язык системного программирования создаст что-то компактное.

Кто-нибудь может уточнить, что компилирует Rust, как это возможно, что он производит такие огромные изображения из 3-х линейной программы? Компилируется ли это на виртуальную машину? Я пропустил команду стриптиза (отладочную информацию в сборке релиза?)? Что-нибудь еще, что могло бы позволить понять, что происходит?

BitTickler
источник
4
Я думаю, что 3Mb содержит не только Hello World, но и всю необходимую среду для платформы. То же самое можно увидеть с Qt. Это не значит, что если вы напишите 6-строчную программу, размер станет 6 Мб. Он останется на 3Mb и будет расти очень медленно после этого.
Андрей Николаенко
8
@AndreiNikolaenko Я знаю об этом. Но это намекает на то, что либо они не обрабатывают библиотеки, как С, добавляя только то, что требуется к изображению, или что-то еще происходит.
BitTickler
@ user2225104 Смотрите мой ответ: RUST обрабатывает библиотеки таким же (или похожим) образом, как C, но по умолчанию C не компилирует статические библиотеки в вашу программу (по крайней мере, на C ++).
AStopher
1
Это уже устарело? С RustC версии 1.35.0 и без параметров Cli я получаю EXE размером 137 КБ. Это автоматически компилируется динамически связано сейчас или что-то еще произошло в это время?
itmuckel

Ответы:

139

Rust использует статическую компоновку для компиляции своих программ, это означает, что все библиотеки, требуемые даже самой простой Hello world!программой, будут скомпилированы в ваш исполняемый файл. Это также включает среду выполнения Rust.

Чтобы заставить Rust динамически связывать программы, используйте аргументы командной строки -C prefer-dynamic; это приведет к гораздо меньшему размеру файла, но также потребует, чтобы библиотеки Rust (включая время выполнения) были доступны вашей программе во время выполнения. По сути, это означает, что вам нужно будет предоставить их, если на компьютере их нет, занимая больше места, чем ваша исходная статически связанная программа.

Для переносимости я бы порекомендовал вам статически связать библиотеки Rust и среду выполнения так, как вы это делали, если бы вы когда-нибудь распространяли свои программы среди других.

AStopher
источник
4
@ user2225104 Не уверен насчет Cargo, но, согласно отчету об ошибке на GitHub , к сожалению, это пока невозможно.
AStopher
2
Но как только у вас в системе будет более 2 исполняемых файлов ржавчины, динамическое связывание начнет экономить ваше пространство…
binki
15
Я не думаю, что статические ссылки объясняют огромный ПРИВЕТНЫЙ МИР. Разве он не должен связывать только те части библиотек, которые фактически используются, а HELLO-WORLD практически ничего не использует?
MaxB
8
BitTicklercargo rustc [--debug or --release] -- -C prefer-dynamic
Зак Мертес
3
@daboross Большое спасибо. Я отслеживал этот связанный RFC . Очень жаль, поскольку Rust также нацелен на системное программирование.
Франклин Ю
62

У меня нет систем для Windows, чтобы попробовать, но в Linux статически скомпилированный мир Rust hello на самом деле меньше, чем эквивалентный C. Если вы видите огромную разницу в размерах, это возможно потому, что вы связываете исполняемый файл Rust статически и C один динамически.

При динамическом связывании вам также необходимо учитывать размер всех динамических библиотек, а не только исполняемого файла.

Итак, если вы хотите сравнить яблоки с яблоками, вам нужно убедиться, что оба они динамические или оба статические. Разные компиляторы будут иметь разные значения по умолчанию, поэтому вы не можете просто полагаться на значения по умолчанию компилятора для получения одинакового результата.

Если вам интересно, вот мои результаты:

-rw-r - r-- 1 aij aij 63 апр. 5 14:26 printf.c
-rwxr-xr-x 1 aij aij 6696 апр. 5 14:27 printf.dyn
-rwxr-xr-x 1 aij aij 829344 5 апр. 14:27 printf.static
-rw-r - r-- 1 aij aij 59 апр. 5 14:26 put.c
-rwxr-xr-x 1 aij aij 6696 апр. 5 14:27 put.dyn
-rwxr-xr-x 1 aij aij 829344 5 апреля 14:27 Put.static
-rwxr-xr-x 1 aij aij 8712 5 апр. 14:28 rust.dyn
-rw-r - r-- 1 aij aij 46 апр. 5 14:09 rust.rs
-rwxr-xr-x 1 aij aij 661496 5 апр. 14:28 rust.static

Они были скомпилированы с помощью gcc (Debian 4.9.2-10) 4.9.2 и rustc 1.0.0-nightly (d17d6e7f1 2015-04-02) (построено 2015-04-03), как с параметрами по умолчанию, так и -staticдля gcc и -C prefer-dynamicдля rustc.

У меня было две версии C hello world, потому что я думал, что использование puts()может связать меньшее количество модулей компиляции.

Если вы хотите попробовать воспроизвести его в Windows, вот источники, которые я использовал:

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

Кроме того, имейте в виду, что различные объемы отладочной информации или разные уровни оптимизации также могут иметь значение. Но я ожидаю, что если вы видите огромную разницу, это связано со статическим и динамическим связыванием.

ау
источник
27
gcc достаточно умен, чтобы выполнять только подстановку printf ->, поэтому результаты идентичны.
bluss
6
Начиная с 2018 года, если вы хотите честное сравнение, не забудьте «убрать» исполняемые файлы, поскольку исполняемый файл Rust в моей системе hello world занимает колоссальные 5,3 МБ, но уменьшается до менее чем 10% от этого при удалении всех символов отладки и например.
Матти Вирккунен
@MattiVirkkunen: все еще имеет место в 2020 году; естественный размер кажется меньшим (не более 5,3 м), но соотношение символов и кода все еще довольно экстремально. Отладочная сборка, параметры по умолчанию на Rust 1.34.0 на CentOS 7, лишены strip -s, уменьшена с 1.6M до 190K. Сборка релиза (по умолчанию плюс opt-level='s', lto = trueи, panic = 'abort'чтобы минимизировать размер) падает с 623K до 158K.
ShadowRanger
Как отличить статичные и динамичные яблоки? Последнее не звучит здорово.
LF
30

При компиляции с Cargo вы можете использовать динамическое связывание:

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

Это значительно уменьшит размер двоичного файла, поскольку теперь он динамически связан.

В Linux, по крайней мере, вы также можете удалить двоичные символы с помощью stripкоманды:

strip target/release/<binary>

Это примерно вдвое уменьшит размер большинства двоичных файлов.

Каспер Скерн Вилструп
источник
8
Просто статистика, стандартная версия hello world (linux x86_64). 3,5 M, с предпочтительной динамикой 8904 B, раздели 6392 B.
Zitrax
30

Обзор всех способов уменьшения размера двоичного файла Rust см. В min-sized-rustрепозитории.

Текущие шаги высокого уровня для уменьшения размера двоичного файла:

  1. Используйте Rust 1.32.0 или новее ( jemallocпо умолчанию не включается )
  2. Добавьте следующее к 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. Сборка в режиме релиза с использованием cargo build --release
  2. Запустите stripполученный бинарный файл.

С помощью nightlyRust можно сделать гораздо больше , но я оставлю эту информацию, min-sized-rustпоскольку она меняется со временем из-за использования нестабильных функций.

Вы также можете использовать #![no_std]для удаления ржавчины libstd. Смотрите min-sized-rustподробности.

Феникс
источник
-10

Это особенность, а не ошибка!

Вы можете указать версии библиотеки (в связанном с проектом файле Cargo.toml ), используемые в программе (даже неявные) для обеспечения совместимости версий библиотеки. Это, с другой стороны, требует, чтобы конкретная библиотека была статически связана с исполняемым файлом, генерируя большие образы времени выполнения.

Эй, это уже не 1978 год - многие люди имеют более 2 МБ ОЗУ на своих компьютерах :-)

NPHighview
источник
9
указать версии библиотеки [...] требует, чтобы конкретная библиотека была статически связана - нет, это не так. Существует множество кода, где точные версии библиотек динамически связаны.
овчарка