Pull to refresh

Comments 80

Все это следствие того что до сих пор применяется древняя как язык Си технология статической линковки.
Отсюда же и невозможность хранения шаблонов С++ в библиотеках и необходимость использования заголовочных файлов.
Пора уже придумать формат библиотек, в котором код хранился бы в виде синтаксического дерева, и пересмотреть сам процесс линковки как таковой.
Динамическая линковка в текущем виде — это, скорее, очень специфичное для Си. Мы настолько привыкли, что Си — это стандарт для системного ПО, что привыкли считать, что *.so (*.dll) — это системные библиотеки. А они не системные, они всего лишь «от языка Си».
DLL это вроде как часть Windows, насчет so не знаю но тоже думаю что это скорее часть Linux чем принадлежность языка (формально в стандарте Си вроде бы нет никаких упоминаний о dll или so).

Ага, а для DOS были оверлеи и различные подгружаемые на старте «DOS экстендеры» в виде exe. Это всё от ОС зависит.
Тут скорее претензии к формату *.lib / *.a — именно там что-то криво реализовано и в результате линкуется вся библиотека (и эти же форматы не приспособлены хранить AST различных шаблонов). Хотя в случае Rust возможно и к самому языку — зачем линковать «рантайм» статически и что это за рантайм вообще такой, если от программы хотят вывести строку на консоль?
Хотя в случае Rust возможно и к самому языку — зачем линковать «рантайм» статически ..., если от программы хотят вывести строку на консоль?

Потому что он для этого нужен по умолчанию, если специально не извращаться. Просто у Си рантайм идет сразу со всеми распространенными ОСями.


… и что это за рантайм вообще такой ...

https://www.rust-lang.org/en-US/faq.html#does-rust-have-a-runtime

Вопрос в том, зачем тащить ВЕСЬ рантайм, если нужна всего лишь ОДНА функция?
Почему рантайм монолитный, почему компилятор не умеет добавлять в экзешник только то, что нужно конкретно этому экзешнику?

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


Я как-то так ситуацию себе представляю, хотя сам был бы рад статье со свежей и подробной информацией по теме, а то гуглятся, в основном, объяснения которым уже пара лет, когда еще libgreen был.

Ну вот, а все это — следствие того, что код в библиотеках хранится уже в окончательно скомпилированном виде, а не в виде заготовки — синтаксического дерева.
Если в конфигурации нашего проекта нет обработки исключений и аллокации памяти (допустим, железка с парой киболайт оперативки на борту), то код генерации исключений в библиотечных функциях должен при компиляции заменяться на просто return с кодом ошибки. Как-то так.
все это — следствие того, что код в библиотеках хранится уже в окончательно скомпилированном виде

Разве? Мне кажется протаскивание в helloworld значительной части стандартной библиотеки является следствием параметров сборки по умолчанию и архитектуры стандартной библиотеки.


Если в конфигурации нашего проекта нет обработки исключений и аллокации памяти

Так в helloworld выше они неявно есть, надо просто явно их отключить.


Ну и, кстати, Cargo пока только с зависимостями в виде исходников и умеет работать, так что при изменении параметров сборки из зависимостей как раз вполне могут быть выкинуты целые куски еще на уровне исходников. Не очень понимаю, что принципиально должна изменить инновация с ast-зависимостями, да и как вообще все это должно работать.

Именно. Формально нет, по факту — на этом всё держится. Динамическая линковка — стандартная практика индустриального Си.
Пора уже придумать формат библиотек, в котором код хранился бы в виде синтаксического дерева, и пересмотреть сам процесс линковки как таковой.

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

Кстати, как раз в С, при статической линковки компилятор(по крайней мере gcc) оторвет все лишнее. Если хочется всю либу засунуть в бинарь, мало кто, что захочет из .so-ки вызвать, то там особые флаги для этого нужны.
UFO just landed and posted this here
Не надо бросаться камнями в D, не проверив на хоть сколько-то актуальной версии. На Win7 64-bit с DMD 2.070.0 helloworld занимает 200 килобайт. Без ключей, без конфигов. Если заморочиться размером, то можно не отставать от C. Вот, например, helloworld в 438 байт: http://forum.dlang.org/thread/qcbicxrtmjmwiljsyhdf@forum.dlang.org
UFO just landed and posted this here
Справедливости ради отмечу, что под линуксом GDC действительно сгенерировал 10 мегабайт. Но gdc сейчас во многом отстаёт, вон и версия языка только 2.066.
Свежий LDC сгенерировал 645 килобайт. Много, но я не пытался ничего ужимать. Думаю, что тут как и у Rust пожмётся выкидыванием лишнего.
Сейчас Hello world на D занимает около мегабайта. Никак не 10.

Чтобы не было голословного обмена утверждениями, вот данные по актуальным компиляторам на 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 под рукой не было, но общая картина должна быть очевидна)

Кстати это может быть одним из тестов на «профпригодность» языка. Бинарник «hello world» должен быть меньше килобайта (и это с учетом всех таблиц, коих напихали в экешники всех форматов).
Какой язык сейчас может пройти этот тест? Разве что Ассемблер))
UFO just landed and posted this here
Напоминает историю с delphi. Общеизвестно было, что «дельфя делает гигантские бинарники». Малоизвестно было, что можно было собирать приложения на Delphi без стандартных библиотек и получать исполняемые файлы в единицы килобайт.

У меня другой вопрос: а почему rust не использует динамически линкуемые библиотеки для подключения стандартной библиотеки?
> а почему rust не использует динамически линкуемые библиотеки для подключения стандартной библиотеки?
Ну это же не решает проблему размера бинарника. Разве что позволяет вам выбирать, таскать стандартную библиотеку вместе с вашим приложением, или разделять её между разными приложениями в системном хранилище, вместе с вероятностью получить dll hell.
Опять началось. DLL hell давно отсутствует при правильно версионированных so'шках, rust может делать то же самое. Он никогда не станет системным языком, если не решит проблемы внешних библиотек.

Вариант «таскать всё своё с собой» вызывает серьёзнейшее сопротивление со стороны security team в дистрибутивах. Потому что если в очередном libssl найдут дыру размером в пол-интернета, то куда проще залатать одну libssl и перезапустить все приложения, чем пересобирать все приложения (судорожно пытаясь понять, какие из них от этой библиотеки зависят).
> DLL hell давно отсутствует при правильно версионированных so'шках
Вы же понимаете, что это как на дороге — каким бы аккуратным водителем вы бы не были, вы никогда не сможете поручиться за других участников дорожного движения.
Да, вменяемость системы определяется вменяемостью каждого автора каждой используемой библиотеки. Обычно «вменяемость» происходящего модерируется мейнтейнерами дистрибутивов и всякими штуками вроде update-alternatives. Но это единственная работающая in-the-wild модель с системными зависимостями. Альтернатива — статическая или псевдостатическая (bring your own DLLs) компоновка, главная проблема которой — если всё переписывать на статику, никакой памяти не хватит.
1. Чем меньше в механизме сочленяющихся частей, тем он надежней. С либами плюс минус то же самое. Хотя реализация версионирования либ в линуксе мне нравится (с одним, правда, но — если либа стянута с неподтвержденного источника, плохо будет всем, кто ее использует).
2. Пока что память стоит дешевле труда по отлову багов, связанных с разными версиями либ. Пример Андроида это хорошо показывает.
Вы знаете, я не соглашусь с этим пунктом. Чем меньше в механизме гибких сочленений, тем более катастрофически он ломается при первой невыносимой нагрузке. Любой, кто задевал зеркалами заднего вида зеркала соседа знает, какое это чудо — складывающиеся зеркала заднего вида. А могли бы и отламываться.

Память стоит дешевле до определённого предела. Если вам сейчас выкатить системные требования в 20Гб на телефон entry level, никому мало не покажется. И внутри андроида ровно те же so'шки, что и всюду, просто эти so'шки либо системные, либо замаскированы под «вызовы API play store».

Отказ от повторного использования кода — это индустриальная катастрофа на текущем уровне. И на любом я думаю, потому что удесятирение требований по памяти ко всему (а не только к Главному Бизнес-Приложению) — это слишком много.

До тех пор пока мы говорим, что Главное Бизнес-Приложение может прожить без линковки — ок, ок. Но это не индустриальный язык, это уютная песочница класса python/go/ruby/php/java. Никто не будет писать код начальной инициализации системы без разделяемых библиотек.

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

окай. Но тогда не следует говорить о претензиях на «новый системный язык», увы. Конкурент Go — да. Конкурент Си — нет.

Все по разному понимают что такое "системный язык". Под одни определения ржавчина подходит, под другие — нет.

Это же не какой-то врожденный недостаток самого языка. Просто язык новый, он активно развивается, как и его основной компилятор, а стабилизация ABI усложнит эту работу. Плюс, на ржавчине в данный момент нет готовых и полезных широкой общественности приложений или библиотек.


Да и вообще, у ржавчины даже просто стандарта языка еще нет)

> Конкурент Go — да. Конкурент Си — нет.
Здесь уже я не соглашусь — у Go порог вхождения сильно ниже. Как кто-то из буржуйнета метко заметил — «Go is a new PHP». Ну и при всем желании из Go рантайм на данном этапе развития не выковыряешь. Тем не менее я бы не сказал, что толстый бинарь останавливает народ от изучения Rust для написания низкоуровневых вещей. По своему опыту работы с Rust и гуглением проблем насущных, с ним связанных, могу заметить, что людям очень не хватает разжеванных примеров реализации шаблонов проектирования, к чему все привыкли за десятилетия ООП. Подвижки в этом плане есть, но уж очень вялые, ввиду непривычности управления памятью (фраза «borrow checker shortcomings» встречалась мне довольно часто). Так что я бы сказал, что претензии на «новый системный язык» обоснованы, но дорабатывать напильником надо дофига, причем в основном документацию.
В Rust можно создавать бинарники вообще без стандартной библиотеки. Хотите динамически линковаться с libc как это делает C – никаких проблем. Тоже самое с использованием стандартного аллокатора вместо «продвинутого» jemalloc.

Так что вполне себе конкурент и «новый системный язык».

Такс, попробуем...


#![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, собственно в доке оно не зря идёт в разделе "Nightly Rust".

> Отказ от повторного использования кода — это индустриальная катастрофа на текущем уровне. И на любом я думаю, потому что удесятирение требований по памяти ко всему (а не только к Главному Бизнес-Приложению) — это слишком много.
Соглашусь наполовину. Многое зависит и от, так сказать, хостера библиотек — операционной системы. В дебиане и производных все реализуемо. В винде увы.
Если что, в андроиде я имел в виду джаву, а не си. С джавой ведь теоретически динамическую подгрузку тоже можно было бы реализовать, но для несистемных приложений ее решили не делать, и разработчиков здесь тоже можно понять.

Правда почему-то латание дыры в одной libssl не спасает от того, что обновляются десятки пакетов, зависящих от нее. Хотя вроде как по концепции в них ничего не должно было поменяться. Я даже задавал по этому поводу вопрос на тостере, но внятного ответа так и не получил. Так что похоже не особо это всех и волнует (и меня в том числе)

То-то же вдруг не с того ни с сего snap появился, и на свой вопрос я так и не получил внятного ответа. Видимо, все из-за недостаточно правильно версионированных so-шках, 13 лет и вправду как-то маловато для этого.

Ну вот. Перечитываешь новые комментарии, а оказывается, уже отвечал :)

Вообще есть такая штука "-C prefer-dynamic", если не ошибаюсь. Но тогда проблема — как эти библиотеки доставлять. Rust API не очень стабильно, поэтому, по крайней мере сейчас, имеет смысл все тащить с собой
О, а вот тут вот как раз интересное и вылезает. Написать клёвую штуку — это одно. Сделать его индустриальным решением — совсем другое. Стабильность API на уровне версий so'шек — как раз из этого.

Чтобы rust могу претендовать на замену Си, он должен уметь не меньше, чем Си для большинства случаев. Работа с системными библиотеками — как раз то, что хотят «для большинства случаев». А вот статическая линковка — это уже экзотика, нужная в редких случаях.

(И не надо мне говорить, что статическая линковка спасёт мир. Я сижу за ноутбуком с 4Гб памяти и мне этого за глаза и за уши, не смотря на то, что если сделать декартво произведение от запущенных бинарников и их so'шек, там получится за 16Гб только кода — столько в память без swap'а не засунуть).
если сделать декартво произведение от запущенных бинарников и их so'шек

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

А если они используемые? Каждая гуёвая программа хочет себе «весь qt» или «весь gtk», который в свою очередь хочет всего и вся.
Мой эстимейт (посмотрел на нескольких программах) — удесятирение размера кода. Т.е. вместо 2Гб за глаза и за уши — «16Гб не достаточно».
А если они используемые?

Тогда все плохо, конечно, и остается только разводить филосовские беседы по поводу тотального доминирования раздутого и переусложненного ПО в индустрии и с грустью смотреть на всякие экспериментальные минималистичные штуки в духе http://sta.li ))

Весь пост можно было сократить до простого "в Rust рантайм линкуется статически". Зачем так много букв?

Как минимум рассмотрено то, что в этом рантайме есть, а также то, как от этого можно избавиться
Поддерживаю, кроме того далеко не все вообще знают, что у таких языков как С++ и Rust вообще есть рантайм
Вспомнилось из исходников винды 3.11

char make_program_look_big[10000000000]
А в чем отличие jemalloc от стандартного аллокатора? И почему в Rust он используется чаще всего?
С оф.сайта:
jemalloc is a general purpose malloc(3) implementation that emphasizes fragmentation avoidance and scalable concurrency support


Либо более наглядно
Источник: https://www.percona.com/blog/2013/03/08/mysql-performance-impact-of-memory-allocators-part-2/
image

А можете что-нибудь рассказать про это:


Представлен новый формат исполняемых контейнеров (crate type) cdylib, который рассчитан на компиляцию динамических библиотек, предназначенных для встраивания в программы на других языках. Ранее доступный формат dylib позиционируется для создания динамических библиотек для проектов на языке Rust. В отличие от dylib в cdylib не включаются метаданные, экспортируемые символы аналогичны исполняемым файлам, не допускается LTO и все библиотеки связываются статически. Сборка простейшего примера "hello world" при использовании cdylib занимает 7.2 Кб, в то время как при dylib — 2.4 Мб;

(цитата с opennet.ru)

Могу порекомендовать взглянуть на соответствующий RFC

Непонятно, можно ли это уже использовать в 1.10-stable (rustc не показывает вариант опции --crate-type=cdylib) и как именно собрать "hello world" с таким размером.

Очень странно, у меня все работает, что на stable, что на nightly
Кстати, значения кажутся очень преувеличеными
image

А если с оптимизациями?

Да, оптимизация меняет дело:


  • strip + opt-level=s: 3.4кб против 1.7мб
  • opt-level=s: 4.1кб против 2.5мб

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


#[no_mangle]
pub extern "C" fn hello() {
        println!("hello!");
}

это уже так хорошо не укукоживается.

Это собираем крейт, а можно ли собрать с этим нововведением helloworld.exe минимального размера?


Занятно: сейчас при rustup update nightly касперский нашёл якобы троян в graphviz-c8005792.dll

а можно ли собрать с этим нововведением helloworld.exe минимального размера?

cdylib для библиотек нужен


рассчитан на компиляцию динамических библиотек, предназначенных для встраивания в программы на других языках
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'овым рантаймом. Не для hello world, а для всего-всего. И как много этого райнтайма будет вынесено как динамические библиотеки наружу.

И насколько получившийся бинарь будет способен пережить различия в минорных версиях компилятора (про мажорные пока ни слова) между программой и её библиотеками.
насколько в таком режиме можно свободно пользоваться rust'овым рантаймом

А какие вообще сложности с этим могут быть? Тут только теряется автономность приложения.


И как много этого райнтайма будет вынесено как динамические библиотеки наружу.

Смотря что конкретно иметь ввиду под "рантайм", но в обычном смысле, наверное, можно сказать что он весь во внешней библиотеке.


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

Насколько я знаю, с этим никаких гарантий никто не дает сейчас.

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


А если делать выбор между "скопируй бинарник на другую машину и он заработает" и "без установленных библиотек правильной версии не запустится"...

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

Это только если релокации не произойдет.

Сейчас же PIC повсюду, с чего бы ей произойти?

Есть ещё несколько плюсов:


1) более эффективная работа с процессорным кэшем инструкций при переключении между разными процессами использующими одну и ту же библиотеку
2) меньше размер пакета для развёртывания на сервере (полезно в основном владельцам бюджетных VPS с лимитами на upload)
3) централизованное получение security обновлений если авторы библиотеки серьёзно относятся к версионирования ABI


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

К первому плюсу, опять-таки, применимо замечание про релокацию.

В чём преимущество бинарника 6 Кб вместо 600 Кб? В каких задачах сокращение объёма исполняемого файла даст существенное преимущество?
UFO just landed and posted this here
На современном железе не очень актуально, разве нет?

Смотря для какого современного железа, смотря что за ось и все такое.


Если я пишу игру и она на мегабайт стала больше — да и не важно.


Но если есть какая-то новая железяка с линуксом для встраивания куда-то там, то на ней увеличение размера каждой из сотен (или тысяч?) мелких юниксовых утилит за счет теоретического переписывания их на ржавчину очень легко может вызвать лишние сложности.

UFO just landed and posted this here
Sign up to leave a comment.

Articles