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

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

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

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

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

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

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


person BitTickler    schedule 12.03.2015    source источник
comment
Думаю, 3Мб содержит не только Hello World, но и всю необходимую среду для платформы. То же самое можно увидеть и с Qt. Это не значит, что если вы напишете программу из 6 строк, ее размер станет 6 Мб. Он останется на уровне 3 МБ и после этого будет расти очень медленно.   -  person Andrei Nikolaenko    schedule 12.03.2015
comment
@AndreiNikolaenko Я в курсе. Но это намекает на то, что либо они не обрабатывают библиотеки, как C, добавляя только то, что требуется к изображению, либо что-то еще происходит.   -  person BitTickler    schedule 12.03.2015
comment
@ user2225104 См. мой ответ, RUST обрабатывает библиотеки таким же (или аналогичным) способом, что и C, но по умолчанию C не компилирует статические библиотеки в вашу программу (по крайней мере, на C ++).   -  person AStopher    schedule 12.03.2015
comment
инструмент strip.exe от MinGW, похоже, работает с EXE, созданным ржавчиной. Но я слышал, что rustc построен на llvm, не уверен, есть ли какая-либо совместимая проблема.   -  person Sorayuki    schedule 30.09.2015
comment
Не фанат mingw, cygwin и т. Д. Если я программирую на Windows, я программирую на Windows, а не на заменителях Linux для бедных людей. Если rust заявляет, что является языком системного программирования, у них не должно возникнуть проблем с поддержкой различных платформ.   -  person BitTickler    schedule 30.09.2015
comment
@BitTickler, хотите ли вы попробовать параметр -C link-args = -s, чтобы уменьшить размер (вы сказали команду полосы, я попробовал, и она работает). а параметр -C link-args = -static-libgcc удалит зависимость от libgcc_xxxx.dll для платформы x86-32. Мне тоже не нравится слишком много зависимостей от DLL. C / C ++ имеет относительно более простую среду выполнения, поэтому размер сгенерированного exe не так велик даже для статической компоновки. В случае размера файла MSVC, похоже, хорошо справляется. (mingw-g ++ также сгенерировал мегабайты exe для статической компоновки)   -  person Sorayuki    schedule 08.10.2015
comment
Я обнаружил, что -C opt-level = 3 -C lto помогает значительно уменьшить размер статических библиотек.   -  person Learn OpenGL ES    schedule 24.12.2015
comment
См. min-sized-rust для обзора всех различных методов минимизации двоичного размера Rust. Приложения.   -  person phoenix    schedule 23.02.2019
comment
Это сейчас устарело? С rustc версии 1.35.0 и без параметров cli я получаю exe размером 137 КБ. Компилируется ли он автоматически с динамической компоновкой сейчас или за это время произошло что-то еще?   -  person itmuckel    schedule 28.06.2019


Ответы (6)


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

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

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

person AStopher    schedule 12.03.2015
comment
Скомпилировать как rustc -C prefer-dynamic [file name].rs. - person AStopher; 12.03.2015
comment
@ user2225104 Не уверен насчет Cargo, но, согласно этому отчету об ошибке на GitHub, это к сожалению, пока невозможно. - person AStopher; 12.03.2015
comment
Но как только у вас будет более двух исполняемых файлов ржавчины в системе, динамическое связывание начнет экономить ваше пространство ... - person binki; 08.08.2016
comment
Я не думаю, что статические ссылки объясняют огромный HELLO-WORLD. Разве он не должен связывать только те части библиотек, которые фактически используются, а HELLO-WORLD практически ничего не использует? - person bobcat; 29.08.2016
comment
BitTickler cargo rustc [--debug or --release] -- -C prefer-dynamic - person Zoey Mertes; 14.09.2016
comment
Можно ли установить библиотеку времени выполнения Rust без компилятора Rust? Нравится установка glibc без GCC? - person Franklin Yu; 17.05.2017
comment
@FranklinYu В настоящее время это невозможно - и, вероятно, не скоро. Основным ограничением является то, что релизы ржавчины несовместимы на двоичном уровне, а ржавчина не имеет стабильного ABI. При динамической компоновке вам нужно будет синхронизировать обновления всех библиотек и двоичных файлов одновременно; и делайте это каждые 6 недель при выпуске нового компилятора. - person daboross; 19.10.2017
comment
@daboross Большое спасибо. Я отслеживал этот связанный RFC. Очень жаль, поскольку Rust также нацелен на системное программирование. - person Franklin Yu; 19.10.2017
comment
Да, я надеюсь, что через 2-5 лет появится уверенность в оптимизации языка и бинарном формате, и будет существовать стабилизированный ABI. Надеюсь, это все, что я могу сделать: /. - person daboross; 20.10.2017
comment
@daboross: Надеюсь, ABI не стабилизируется. Нестабильный ABI необходим для выполнения таких оптимизаций, как оптимизация ниши, изменение структуры структуры и т. Д. - person Matthieu M.; 20.03.2018
comment
@MattheiM У нас всегда может быть версионный ABI с четко написанной спецификацией, если мы не станем «полностью» стабильными. Я полагаю, что это принесет по крайней мере 90% преимуществ. - person daboross; 20.03.2018
comment
исполняемый файл Rust больше, чем исполняемый файл Golang? - person Nulik; 10.11.2018
comment
@Nulik: Да, по умолчанию, но это потому, что Rust по умолчанию использует статические сборки (включая все зависимости, включая время выполнения), а Go динамически связывает свою среду выполнения. В моей системе CentOS 7 helloworld Go компилируется до ~ 76 КБ, но, помимо стандартных вещей, требуется динамическая зависимость времени выполнения от libgo.so, которая превышает 47 МБ. Rust helloworld по умолчанию (как сделано с cargo new) не имеет каких-либо уникальных динамических зависимостей, он содержит все, кроме базового материала времени выполнения C, в исполняемом файле размером 1,6 Мбайт; с настройками (оптимизация по размеру, использование LTO, прерывание при панике) он падает до 0,6M. - person ShadowRanger; 21.02.2020
comment
Параметр -C prefer-dynamic позволяет уменьшить количество сборок выпуска (с включенной только оптимизацией по размеру; это не позволит мне использовать LTO или прервать работу в случае паники) до 8,8 КБ, хотя и с новой динамической зависимостью 4,7 млн. Итак, от яблок к яблокам, Rust меньше; это десятая часть размера, динамически связанная, полагаясь на среду выполнения, которая также составляет одну десятую размера. - person ShadowRanger; 21.02.2020
comment
Голосование против, потому что это неправильно: статическое связывание не обязательно подразумевает до неприличия большие двоичные файлы. Ненужные символы могут быть удалены. Например, Visual C ++ 2017 x64 дает 219 КБ для int main(){std::cout<<"hello world\n";} с / EHsc / O2 / MT. Или Mingw-w64 g ++ 10.2 914 kiB, передавая -s -O2 -static. И это относится к C ++ Iostreams, которые сами по себе довольно раздуты и должны нести состояние форматирования, исключения, локали и многое другое. Заменив на std::printf, я получу 117 килобайт (cl) или 41 килобайт (g ++). Rust мог бы улучшить эти 41 kiB, поскольку Rust I / O не имеет локалей, но все намного хуже. - person purefanatic; 23.02.2021
comment
Fwiw, на моей машине пример Rust спрашивающего по умолчанию производит 4092 kiB (x86_64-pc-windows-gnu). Добавление lto = true в Cargo.toml составляет 1406 КБ. Добавление panic = abort, opt-level = z, codegen-units = 1 составляет 1264 КБ. Ночные символы полосы = уменьшают это значение до 220 КБ. Это число может быть разумным для C ++, но, на мой взгляд, у Rust нет причин создавать такие излишне большие исполняемые файлы. Насколько я могу судить, Rust имеет гораздо меньше предполагаемых накладных расходов по сравнению с C ++. Было бы намного лучше, если бы люди действительно заботились. - person purefanatic; 23.02.2021
comment
@purefanatic Этот ответ был правильным на момент написания. Если это изменилось, отредактируйте ответ или добавьте свой. - person AStopher; 24.02.2021
comment
@AStopher Я придерживаюсь своего мнения: ваш ответ подразумевает, что статическое связывание всегда связано с огромным размером двоичного файла, но вы не указываете, что это просто проблема с текущей реализацией Rust и настройками по умолчанию. Мне кажется, что статическая компоновка - это общая проблема, но это не так. Вот почему я считаю ваш ответ вводящим в заблуждение. - person purefanatic; 24.02.2021

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

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

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

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

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

ржавчина

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

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

person aij    schedule 05.04.2015
comment
gcc достаточно умен, чтобы делать именно такую ​​подстановку, как printf - ›помещает саму замену, поэтому результаты идентичны. - person bluss; 29.05.2015
comment
По состоянию на 2018 год, если вы хотите честного сравнения, не забудьте удалить исполняемые файлы, так как исполняемый файл hello world Rust в моей системе составляет колоссальные 5,3 МБ, но падает до менее 10% от этого, когда вы удаляете все символы отладки и тому подобное. - person Matti Virkkunen; 01.07.2018
comment
@MattiVirkkunen: В 2020 году все будет по-прежнему; естественный размер кажется меньше (далеко не 5,3M), но соотношение символов и кода по-прежнему довольно велико. Отладочная сборка, параметры по умолчанию в Rust 1.34.0 на CentOS 7, без strip -s, упала с 1.6M до 190K. Сборка выпуска (значения по умолчанию плюс opt-level='s', lto = true и panic = 'abort' для минимизации размера) упала с 623 КБ до 158 КБ. - person ShadowRanger; 21.02.2020

Для обзора всех способов уменьшить размер двоичного файла Rust см. _1 _ репозиторий.

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

  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 для полученного двоичного файла.

С nightly Rust можно сделать больше, но я оставлю эту информацию в _8 _ поскольку он меняется со временем из-за использования нестабильных функций.

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

person phoenix    schedule 23.02.2019

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

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

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

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

strip target/release/<binary>

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

person Casper Skern Wilstrup    schedule 30.01.2016
comment
Просто немного статистики, версия hello world по умолчанию (linux x86_64). 3,5 м, с предпочитаемым динамиком 8904 б, раздетым 6392 б. - person Zitrax; 25.05.2017
comment
следует добавить примечание о том, что это плохо для распространения. - person TheTechRobo36414519; 08.03.2021

если вы используете команду rustc для компиляции, просто напишите команду rustc -C identify-dynamic = yes main.rs

person TANDEX    schedule 10.11.2020
comment
Об этом уже упоминалось в принятом ответе; этот ответ не дает нового значения. - person Shepmaster; 10.11.2020

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

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

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

person NPHighview    schedule 15.02.2018
comment
указать версии библиотеки [...] требует, чтобы конкретная библиотека была статически связана - нет, это не так. Существует много кода, в котором точные версии библиотек динамически связаны. - person Shepmaster; 15.02.2018