Большие бинари в моем Rust?

https://lifthrasiir.github.io/rustlog/why-is-a-rust-executable-large.html
  • Перевод
Disclaimer: Эта статья является очень вольным переводом и некоторые мометы достаточно сильно отличаются от оригинала

Бороздя просторы интернета вы наверняка уже успели услышать про Rust. После всех красноречивых отзывов и расхваливаний вы, конечно же, не смогли не потрогать это чудо. Первая программа выглядела не иначе как:
fn main() {
    println!("Hello, world!");
}


Скомпилировав получим соответствующий исполняемый файл:
$ rustc hello.rs
$ du -h hello
632K hello

632 килобайт для простого принта?! Rust позиционируется как системный язык, который имеет потенциал для замены C/C++, верно? Так почему бы не проверить аналогичную программу на ближайшем конкуренте?
$ cat hello.c
#include <stdio.h>
int main() {
    printf("Hello, World!\n");
}
$ gcc hello.c -ohello
$ du -h hello
6.7K hello


Более безопасные и громоздкие iostream-ы C++ выдают не сильно иной результат:
$ cat hello.cpp
#include <iostream>
int main() {
    std::cout << "Hello, World!" << std::endl;
}
$ g++ hello.cpp -ohello
$ du -h hello
8.3K hello


Флаги -O3/-Os практически не меняют конечного размера


Так что не так с Rust?


Кажется, что необычный размер исполняемых файлов Rust интересует много кого, и вопрос этот совершенно не нов. Взять, к примеру, этот вопрос на stackoverflow, или множество других. Даже немного странно, что все еще не было статей или каких-либо заметок описывающих эту проблему.
Все примеры были перетестированы на Rust 1.11.0-nightly (1ab87b65a 2016-07-02) на Linux 4.4.14 x86_64 без использования cargo и stable-ветки в отличии от оригинальной статьи.


Уровень оптимизации


Любой опытный программист конечно же воскликнет, что дебаг билд на то и дебаг, и нередко его размер значительно превышает релиз-версию. Rust в данном случае не исключение и достаточно гибко позволяет настраивать параметры сборки. Уровни оптимизации аналогичны gcc, задать его можно с помощью параметра -C opt-level=x, где вместо x число от 0-3, либо s для минимизации размера. Ну что же, посмотрим что из этого выйдет:
$ rustc helloworld.rs -C opt-level=s
$ du -h helloworld                 
630K helloworld


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

Оптимизация линковки (LTO)


Rust по стандартному поведению к каждому исполняемому файлу линкует всю свою стандартную библиотеку. Так что мы можем избавиться и от этого, ведь глупый линковщик не понимает, что нам не очень нужно взаимодействие с сетью.
На самом деле есть хорошая причина для такого поведения. Как вы наверное знаете языки C и C++ компилируют каждый файл по отдельности. Rust же поступает немного иначе, где единицей компиляции выступает крейт (crate). Не трудно догадаться, что вызов функций из других файлов компилятор не сможет оптимизировать, так как он попросту работает с одним большим файлом.
Изначально в C/C++ компилятор производил оптимизацию независимо каждого файла. Со временем появилась технология оптимизации при линковке. Хоть это и стало занимать значительно больше времени, зато в результате получались исполняемые файлы куда лучше, чем раньше. Посмотрим как изменит положение дел эта функциональность в Rust:
$ rustc helloworld.rs -C opt-level=s -C lto
$ du -h helloworld
604K helloworld


Так что же внутри?


Первое, чем наверное стоит воспользоваться — это небезызвестная утилита strings из набора GNU Binutils. Вывод ее достаточно большой (порядка 6 тыс. строк), так что приводить его полностью не имеет смысла. Вот самое интересное:
$ strings helloworld
capacity overflow
attempted to calculate the remainder with a divisor of zero
<jemalloc>: Error in atexit()
<jemalloc>: Error in pthread_atfork()
DW_AT_member
DW_AT_explicit
_ZN4core3fmt5Write9write_fmt17ha0cd161a5f40c4adE # или core::fmt::Write::write_fmt::ha0cd161a5f40c4ad
_ZN4core6result13unwrap_failed17h072f7cd97aa67a9cE # или core::result::unwrap_failed::h072f7cd97aa67a9c


На основе этого результата можно сделать несколько выводов:
— К исполняемым файлам Rust статически линкуется вся стандартная библиотека.
— Rust использует jemalloc вместо системного аллокатора
— К файлам также статически линкуется библиотека libbacktrace, которая нужна для трассировки стека
Все это, как вы понимаете, для обычного println не очень то и нужно. Значит самое время от них всех избавиться!

Отладочные символы и libbacktrace


Начнем с простого — убрать из исполняемого файла отладочные символы.
$ strip hello
# du -h hello
356K helloworld


Очень неплохой результат, почти половину исходного размера занимают отладочные символы. Хотя в этом случае удобочитаемого вывода при ошибках, вроде panic! нам не получить:
$ cat helloworld.rs 
fn main() {
    panic!("Hello, world!");
}
$ rustc helloworld.rs && RUST_BACKTRACE=1 ./helloworld 
thread 'main' panicked at 'Hello, world!', helloworld.rs:2
stack backtrace:
   1:     0x556536e40e7f - std::sys::backtrace::tracing::imp::write::h6528da8103c51ab9
   2:     0x556536e4327b - std::panicking::default_hook::_$u7b$$u7b$closure$u7d$$u7d$::hbe741a5cc3c49508
   3:     0x556536e42eff - std::panicking::default_hook::he0146e6a74621cb4
   4:     0x556536e3d73e - std::panicking::rust_panic_with_hook::h983af77c1a2e581b
   5:     0x556536e3c433 - std::panicking::begin_panic::h0bf39f6d43ab9349
   6:     0x556536e3c3a9 - helloworld::main::h6d97ffaba163087d
   7:     0x556536e42b38 - std::panicking::try::call::h852b0d5f2eec25e4
   8:     0x556536e4aadb - __rust_try
   9:     0x556536e4aa7e - __rust_maybe_catch_panic
  10:     0x556536e425de - std::rt::lang_start::hfe4efe1fc39e4a30
  11:     0x556536e3c599 - main
  12:     0x7f490342b740 - __libc_start_main
  13:     0x556536e3c268 - _start
  14:                0x0 - <unknown>
$ strip helloworld && RUST_BACKTRACE=1 ./helloworld
thread 'main' panicked at 'Hello, world!', helloworld.rs:2
stack backtrace:
   1:     0x55ae4686ae7f - <unknown>
...
  11:     0x55ae46866599 - <unknown>
  12:     0x7f70a7cd9740 - __libc_start_main
  13:     0x55ae46866268 - <unknown>
  14:                0x0 - <unknown>


Вытащить целиком libbacktrace из линковки без последствий не получится, он сильно связан со стандартной библиотекой. Но зато размотка для паники из libunwind нам не нужна, и мы можем ее выкинуть. Незначительные улучшения мы все таки получим:
$ rustc helloworld.rs -C lto -C panic=abort -C opt-level=s
$ du -h helloworld
592K helloworld


Убираем jemalloc


Компилятор Rust стандартной сборки чаще всего использует jemalloc, вместо системного аллокатора. Изменить это поведение очень просто: нужно всего лишь вставить макро и импортировать нужный крейт аллокатора.
#![feature(alloc_system)]
extern crate alloc_system;

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

$ rustc helloworld.rs && du -h helloworld
235K helloworld
$ strip helloworld && du -h helloworld 
133K helloworld


Небольшой вывод


Завершающим штрихом в нашем шаманстве могло быть удаление из исполняемого файла всей стандартной библиотеки. В большинстве случаев это не нужно, да и к тому же в офф.книге (или в переводе) все шаги подробно описаны. Этим способом можно получить файл размером, сопоставимым с аналогом на Си.
Стоит также отметить, что размер стандартного набора библиотек постоянен и сами линковочные файлы(перечисленные в статье) не увеличиваются в зависимости от вашего кода, а значит вам скорее всего не придется беспокоится о размерах. На крайний случай вы всегда можете использовать упаковщики кода вроде upx

Большое спасибо русскоязычному комьюнити Rust за помощь с переводом
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

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

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


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

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

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

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


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

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

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


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

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


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

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

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

        • +1
          Кстати, как раз в С, при статической линковки компилятор(по крайней мере gcc) оторвет все лишнее. Если хочется всю либу засунуть в бинарь, мало кто, что захочет из .so-ки вызвать, то там особые флаги для этого нужны.
        • +2
          В принципе это ещё не так плохо, как 10-ти мегабайтовый хелоуворлд на D. Причём экспорт отдельных функций резал только до 7-ми.
          Хорошая идея != хорошая реализация.
          Правда, времени, которое потратил на изучение, я не жалею, оно того стоило.
          • +5
            Не надо бросаться камнями в D, не проверив на хоть сколько-то актуальной версии. На Win7 64-bit с DMD 2.070.0 helloworld занимает 200 килобайт. Без ключей, без конфигов. Если заморочиться размером, то можно не отставать от C. Вот, например, helloworld в 438 байт: http://forum.dlang.org/thread/qcbicxrtmjmwiljsyhdf@forum.dlang.org
            • +1
              В винде ниразу не собирал, предполагалось что там всё ещё хуже. На самом деле, это вполне радужное открытие для меня. Но покуда приходится только наблюдать.
              Буду знать почему он сейчас подымается в tiobe — есть нормальные реализации.
              • +3
                Справедливости ради отмечу, что под линуксом GDC действительно сгенерировал 10 мегабайт. Но gdc сейчас во многом отстаёт, вон и версия языка только 2.066.
                Свежий LDC сгенерировал 645 килобайт. Много, но я не пытался ничего ужимать. Думаю, что тут как и у Rust пожмётся выкидыванием лишнего.
            • 0
              Сейчас Hello world на D занимает около мегабайта. Никак не 10.
              • +2

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

                • 0
                  Кстати это может быть одним из тестов на «профпригодность» языка. Бинарник «hello world» должен быть меньше килобайта (и это с учетом всех таблиц, коих напихали в экешники всех форматов).
                  Какой язык сейчас может пройти этот тест? Разве что Ассемблер))
                  • +1
                    Тест на профпригодность написания маленьких хелловорлдов?

                    Обычно важнее скорость роста размера бинарника.
                • +3
                  Напоминает историю с delphi. Общеизвестно было, что «дельфя делает гигантские бинарники». Малоизвестно было, что можно было собирать приложения на Delphi без стандартных библиотек и получать исполняемые файлы в единицы килобайт.

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

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

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

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

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

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

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

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

                                    • +4

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


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

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

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

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


                                            #![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
                                            • +2

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

                                              • 0
                                                Ладно, пробуем…
                                                И получаем:

                                                error[E0554]: #[feature] may not be used on the stable release channel

                                                То есть в итоге, no_stdlib работает и стабильно, но без феатур оно не работает, а феатуры не работают в стабильном канале.

                                                А если стереть #[feature] — то не видит этих функций, и все равно ругается.

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

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

                                • 0

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

                                  • 0

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

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

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

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

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

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

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

                              • +7

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

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

                                  char make_program_look_big[10000000000]
                                  
                                  • +1
                                    А в чем отличие jemalloc от стандартного аллокатора? И почему в Rust он используется чаще всего?
                                    • +5
                                      С оф.сайта:
                                      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
                                    • +2

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


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

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

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

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

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

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

                                              • +2

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


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

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


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

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

                                              • 0

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


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

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

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


                                                  рассчитан на компиляцию динамических библиотек, предназначенных для встраивания в программы на других языках
                                              • 0
                                                rustc не показывает вариант опции --crate-type=cdylib

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

                                          • 0
                                            Не вы первый :) users.rust-lang.org
                                            • 0

                                              Ну так статья потому и была написана, что вопрос очень часто всплывает у знакомящихся с языком. Чаще, наверное, чем медленная скорость работы кода (из-за отладочных сборок по умолчанию).

                                            • +6

                                              По поводу компиляций и оптимизаций, обновился только что до 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)

                                              Так что всё сопоставимо по размеру.

                                              • +1
                                                Во, вот это похоже на правду уже. Теперь вопрос в том, насколько в таком режиме можно свободно пользоваться rust'овым рантаймом. Не для hello world, а для всего-всего. И как много этого райнтайма будет вынесено как динамические библиотеки наружу.

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

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


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

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


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

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

                                              • +4

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


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

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

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

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

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


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


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

                                                    • +2

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

                                                  • –1
                                                    В чём преимущество бинарника 6 Кб вместо 600 Кб? В каких задачах сокращение объёма исполняемого файла даст существенное преимущество?
                                                    • +2
                                                      Экономия дискового пространства, сокращение времени чтения с диска?
                                                      • –1
                                                        На современном железе не очень актуально, разве нет?
                                                        • +4

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


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


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

                                                          • 0
                                                            Когда на сервер ломится пару десятков тысяч пользователей — лишним не будет.

                                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.