Comments 80
Отсюда же и невозможность хранения шаблонов С++ в библиотеках и необходимость использования заголовочных файлов.
Пора уже придумать формат библиотек, в котором код хранился бы в виде синтаксического дерева, и пересмотреть сам процесс линковки как таковой.
Хотя в случае Rust возможно и к самому языку — зачем линковать «рантайм» статически ..., если от программы хотят вывести строку на консоль?
Потому что он для этого нужен по умолчанию, если специально не извращаться. Просто у Си рантайм идет сразу со всеми распространенными ОСями.
… и что это за рантайм вообще такой ...
https://www.rust-lang.org/en-US/faq.html#does-rust-have-a-runtime
Почему рантайм монолитный, почему компилятор не умеет добавлять в экзешник только то, что нужно конкретно этому экзешнику?
Совсем весь рантайм и не тащится, но обычная печать косвенно задействует значительную его часть: если что-то не так пошло с печатью, то может потребоваться раскрутить стек, хоть он и не большой, да еще и напечатать бэктрейс, а это за собой тащит как минимум форматирование и аллокатор.
Я как-то так ситуацию себе представляю, хотя сам был бы рад статье со свежей и подробной информацией по теме, а то гуглятся, в основном, объяснения которым уже пара лет, когда еще libgreen был.
Если в конфигурации нашего проекта нет обработки исключений и аллокации памяти (допустим, железка с парой киболайт оперативки на борту), то код генерации исключений в библиотечных функциях должен при компиляции заменяться на просто return с кодом ошибки. Как-то так.
все это — следствие того, что код в библиотеках хранится уже в окончательно скомпилированном виде
Разве? Мне кажется протаскивание в helloworld значительной части стандартной библиотеки является следствием параметров сборки по умолчанию и архитектуры стандартной библиотеки.
Если в конфигурации нашего проекта нет обработки исключений и аллокации памяти
Так в helloworld выше они неявно есть, надо просто явно их отключить.
Ну и, кстати, Cargo пока только с зависимостями в виде исходников и умеет работать, так что при изменении параметров сборки из зависимостей как раз вполне могут быть выкинуты целые куски еще на уровне исходников. Не очень понимаю, что принципиально должна изменить инновация с ast-зависимостями, да и как вообще все это должно работать.
Пора уже придумать формат библиотек, в котором код хранился бы в виде синтаксического дерева, и пересмотреть сам процесс линковки как таковой.
По умолчанию ржавчина так и делает для своих собственных библиотек, иначе бы обобщенный код из библиотек был бы недоступен (заголовочных файлов-то нет).
Чтобы не было голословного обмена утверждениями, вот данные по актуальным компиляторам на x86_64 Arch Linux (-O -inline
+ strip
):
-rwxr-xr-x 1 dicebot dicebot 12K Jul 11 16:44 ./hello-dmd-shared
-rwxr-xr-x 1 dicebot dicebot 558K Jul 11 16:43 ./hello-dmd-static
-rwxr-xr-x 1 dicebot dicebot 1.8M Jul 11 16:44 ./hello-gdc-static
-rwxr-xr-x 1 dicebot dicebot 12K Jul 11 16:44 ./hello-ldc-shared
(ldc-static и gdc-shared под рукой не было, но общая картина должна быть очевидна)
Какой язык сейчас может пройти этот тест? Разве что Ассемблер))
У меня другой вопрос: а почему rust не использует динамически линкуемые библиотеки для подключения стандартной библиотеки?
Ну это же не решает проблему размера бинарника. Разве что позволяет вам выбирать, таскать стандартную библиотеку вместе с вашим приложением, или разделять её между разными приложениями в системном хранилище, вместе с вероятностью получить dll hell.
Вариант «таскать всё своё с собой» вызывает серьёзнейшее сопротивление со стороны security team в дистрибутивах. Потому что если в очередном libssl найдут дыру размером в пол-интернета, то куда проще залатать одну libssl и перезапустить все приложения, чем пересобирать все приложения (судорожно пытаясь понять, какие из них от этой библиотеки зависят).
Вы же понимаете, что это как на дороге — каким бы аккуратным водителем вы бы не были, вы никогда не сможете поручиться за других участников дорожного движения.
2. Пока что память стоит дешевле труда по отлову багов, связанных с разными версиями либ. Пример Андроида это хорошо показывает.
Память стоит дешевле до определённого предела. Если вам сейчас выкатить системные требования в 20Гб на телефон entry level, никому мало не покажется. И внутри андроида ровно те же so'шки, что и всюду, просто эти so'шки либо системные, либо замаскированы под «вызовы API play store».
Отказ от повторного использования кода — это индустриальная катастрофа на текущем уровне. И на любом я думаю, потому что удесятирение требований по памяти ко всему (а не только к Главному Бизнес-Приложению) — это слишком много.
До тех пор пока мы говорим, что Главное Бизнес-Приложение может прожить без линковки — ок, ок. Но это не индустриальный язык, это уютная песочница класса python/go/ruby/php/java. Никто не будет писать код начальной инициализации системы без разделяемых библиотек.
Если ржавчина станет настолько популярна, что легкое распухание сотен тысяч исполняемых файлов на компьютере рядового пользователя станет серьезной проблемой, то тогда и будут стабилизировать ABI и все это разгребать. На данном этапе развития языка это далеко далеко не самая актуальная проблема.
Все по разному понимают что такое "системный язык". Под одни определения ржавчина подходит, под другие — нет.
Это же не какой-то врожденный недостаток самого языка. Просто язык новый, он активно развивается, как и его основной компилятор, а стабилизация ABI усложнит эту работу. Плюс, на ржавчине в данный момент нет готовых и полезных широкой общественности приложений или библиотек.
Да и вообще, у ржавчины даже просто стандарта языка еще нет)
Здесь уже я не соглашусь — у Go порог вхождения сильно ниже. Как кто-то из буржуйнета метко заметил — «Go is a new PHP». Ну и при всем желании из Go рантайм на данном этапе развития не выковыряешь. Тем не менее я бы не сказал, что толстый бинарь останавливает народ от изучения Rust для написания низкоуровневых вещей. По своему опыту работы с Rust и гуглением проблем насущных, с ним связанных, могу заметить, что людям очень не хватает разжеванных примеров реализации шаблонов проектирования, к чему все привыкли за десятилетия ООП. Подвижки в этом плане есть, но уж очень вялые, ввиду непривычности управления памятью (фраза «borrow checker shortcomings» встречалась мне довольно часто). Так что я бы сказал, что претензии на «новый системный язык» обоснованы, но дорабатывать напильником надо дофига, причем в основном документацию.
Так что вполне себе конкурент и «новый системный язык».
Такс, попробуем...
#![no_std]
fn main(){
let x=10;
}
компилим: rustc main.rs
Получаем ошибку:
error: language item required, but not found: `panic_fmt`
error: language item required, but not found: `eh_personality`
error: aborting due to 2 previous errors
Там же по ссылке написано что надо предоставить свои реализации этих функций, раз выкинули станартную библиотеку.
И получаем:
error[E0554]: #[feature] may not be used on the stable release channel
То есть в итоге, no_stdlib работает и стабильно, но без феатур оно не работает, а феатуры не работают в стабильном канале.
А если стереть #[feature] — то не видит этих функций, и все равно ругается.
Да, нужен компилятор из ночного канала, эта глава же в секции "Nightly Rust" :-) Такая у ржавчины философия с feature'ами и каналами распространения
Следующий шаг — использовать nightly, собственно в доке оно не зря идёт в разделе "Nightly Rust".
Соглашусь наполовину. Многое зависит и от, так сказать, хостера библиотек — операционной системы. В дебиане и производных все реализуемо. В винде увы.
Если что, в андроиде я имел в виду джаву, а не си. С джавой ведь теоретически динамическую подгрузку тоже можно было бы реализовать, но для несистемных приложений ее решили не делать, и разработчиков здесь тоже можно понять.
Правда почему-то латание дыры в одной libssl не спасает от того, что обновляются десятки пакетов, зависящих от нее. Хотя вроде как по концепции в них ничего не должно было поменяться. Я даже задавал по этому поводу вопрос на тостере, но внятного ответа так и не получил. Так что похоже не особо это всех и волнует (и меня в том числе)
То-то же вдруг не с того ни с сего snap появился, и на свой вопрос я так и не получил внятного ответа. Видимо, все из-за недостаточно правильно версионированных so-шках, 13 лет и вправду как-то маловато для этого.
Чтобы rust могу претендовать на замену Си, он должен уметь не меньше, чем Си для большинства случаев. Работа с системными библиотеками — как раз то, что хотят «для большинства случаев». А вот статическая линковка — это уже экзотика, нужная в редких случаях.
(И не надо мне говорить, что статическая линковка спасёт мир. Я сижу за ноутбуком с 4Гб памяти и мне этого за глаза и за уши, не смотря на то, что если сделать декартво произведение от запущенных бинарников и их so'шек, там получится за 16Гб только кода — столько в память без swap'а не засунуть).
если сделать декартво произведение от запущенных бинарников и их so'шек
По идее, во время сборки неиспользуемые куски зависимостей будут выкинуты оптимизатором.
Мой эстимейт (посмотрел на нескольких программах) — удесятирение размера кода. Т.е. вместо 2Гб за глаза и за уши — «16Гб не достаточно».
А если они используемые?
Тогда все плохо, конечно, и остается только разводить филосовские беседы по поводу тотального доминирования раздутого и переусложненного ПО в индустрии и с грустью смотреть на всякие экспериментальные минималистичные штуки в духе http://sta.li ))
Весь пост можно было сократить до простого "в Rust рантайм линкуется статически". Зачем так много букв?
char make_program_look_big[10000000000]
jemalloc is a general purpose malloc(3) implementation that emphasizes fragmentation avoidance and scalable concurrency support
А можете что-нибудь рассказать про это:
Представлен новый формат исполняемых контейнеров (crate type) cdylib, который рассчитан на компиляцию динамических библиотек, предназначенных для встраивания в программы на других языках. Ранее доступный формат dylib позиционируется для создания динамических библиотек для проектов на языке Rust. В отличие от dylib в cdylib не включаются метаданные, экспортируемые символы аналогичны исполняемым файлам, не допускается LTO и все библиотеки связываются статически. Сборка простейшего примера "hello world" при использовании cdylib занимает 7.2 Кб, в то время как при dylib — 2.4 Мб;
(цитата с opennet.ru)
Непонятно, можно ли это уже использовать в 1.10-stable (rustc
не показывает вариант опции --crate-type=cdylib
) и как именно собрать "hello world" с таким размером.
А если с оптимизациями?
Да, оптимизация меняет дело:
strip
+opt-level=s
: 3.4кб против 1.7мбopt-level=s
: 4.1кб против 2.5мб
Это собираем крейт, а можно ли собрать с этим нововведением helloworld.exe минимального размера?
Занятно: сейчас при rustup update nightly касперский нашёл якобы троян в graphviz-c8005792.dll
rustc не показывает вариант опции --crate-type=cdylib
У меня в справке варианта нет, но команда выполняется. Наверное, просто забыли обновить справку.
По поводу компиляций и оптимизаций, обновился только что до Rust 1.10.
fn main() {
println!("Hello, world!");
}
int main(int argc, char **argv) {
printf("Hello, world!\n");
}
Компилирую так:
$ rustc -C prefer-dynamic -C opt-level=3 hello.rs
$ gcc -Os hello.c
Результаты:
$ ls -lh
-rwxr-xr-x 1 kstep kstep 6.7K Jul 11 18:25 a.out
-rwxr-xr-x 1 kstep kstep 7.9K Jul 11 18:24 hello
$ strip -s a.out hello
$ ls -lh
-rwxr-xr-x 1 kstep kstep 4.4K Jul 11 18:25 a.out
-rwxr-xr-x 1 kstep kstep 5.4K Jul 11 18:25 hello
$ ldd hello
linux-vdso.so.1 (0x00007ffdb5aeb000)
libstd-e8edd0fd.so => /usr/lib/libstd-e8edd0fd.so (0x00007f4621eee000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007f4621b4d000)
libdl.so.2 => /usr/lib/libdl.so.2 (0x00007f4621949000)
libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f462172c000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f4621516000)
/lib64/ld-linux-x86-64.so.2 (0x00007f46224b5000)
libm.so.6 => /usr/lib/libm.so.6 (0x00007f4621212000)
$ ldd a.out
linux-vdso.so.1 (0x00007fffc0974000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007f5688810000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5688bb1000)
Так что всё сопоставимо по размеру.
И насколько получившийся бинарь будет способен пережить различия в минорных версиях компилятора (про мажорные пока ни слова) между программой и её библиотеками.
насколько в таком режиме можно свободно пользоваться rust'овым рантаймом
А какие вообще сложности с этим могут быть? Тут только теряется автономность приложения.
И как много этого райнтайма будет вынесено как динамические библиотеки наружу.
Смотря что конкретно иметь ввиду под "рантайм", но в обычном смысле, наверное, можно сказать что он весь во внешней библиотеке.
насколько получившийся бинарь будет способен пережить различия в минорных версиях компилятора
Насколько я знаю, с этим никаких гарантий никто не дает сейчас.
У динамической линковки есть только один большой плюс — это экономия оперативной памяти, т.к. внешняя библиотека грузится один раз и затем маппится в каждый процесс. Ну и экономия диска, да. Но актуальном все это становится на современном железе, только если таких скомпилированных приложений и процессов — десятки.
А если делать выбор между "скопируй бинарник на другую машину и он заработает" и "без установленных библиотек правильной версии не запустится"...
У динамической линковки есть только один большой плюс — это экономия оперативной памяти, т.к. внешняя библиотека грузится один раз и затем маппится в каждый процесс.
Это только если релокации не произойдет.
Есть ещё несколько плюсов:
1) более эффективная работа с процессорным кэшем инструкций при переключении между разными процессами использующими одну и ту же библиотеку
2) меньше размер пакета для развёртывания на сервере (полезно в основном владельцам бюджетных VPS с лимитами на upload)
3) централизованное получение security обновлений если авторы библиотеки серьёзно относятся к версионирования ABI
Я бы сказал что динамическая линковка строго лучше статической, но только для стабильных библиотек с строгой системой версий. Что почти никогда не применимо к пакетам cargo и им подобным, но можно ожидать от стандартной библиотеки.
Смотря для какого современного железа, смотря что за ось и все такое.
Если я пишу игру и она на мегабайт стала больше — да и не важно.
Но если есть какая-то новая железяка с линуксом для встраивания куда-то там, то на ней увеличение размера каждой из сотен (или тысяч?) мелких юниксовых утилит за счет теоретического переписывания их на ржавчину очень легко может вызвать лишние сложности.
Большие бинари в моем Rust?