Comments 41
Нет проблем установить IDE для любого языка, кроме RUST.
Так и не смог скомпилировать что-то работающее на RUST под Windows для ESP32C3(RISC-V) . Постоянно ошибки и отсутствие библиотек.
Причем в интернете такие ошибки есть, а решений для них нет.
Можете дать ссылку на нормальное описание установки RUST для ESP под Windows.
А еще лучше, написать статью на хабре как это сделать, чтобы не было мучительно больно.
Как обычно проблема кроется в задаче. сделали такую архитектуру языка с такими модулями, макросами вызывающими произвольно непойми что в отдельных процессах и отсутствием форвард деклараций, что быстро что-либо скомпилировать невозможно.
На это накладывается лёгкость добавления ненужных зависимостей и всё, вы компилируете гораздо медленнее чем С++
А что предлагаете вместо макросов использовать? "вызывать непонятно что в отдельных процессах" как часть скриптов сборки? Так быстрее не станет.
Форвард декларации это крайне неудобно, Да и очень сомневаюсь что это хоть как-то ускоряет компиляцию. Особенно учитывая, что модули в C++ всё как-то не доходят, а инклуд всех этих тысяч строк форвард деклараций на каждый файл исходников явно не ускоряет процесс. Precompiled headers конечно несколько помогают, но это решение, ИМХО, так себе.
Так быстрее не станет.
станет станет, потому что компилятор будет знать что происходит и это сильно упростит инкрементальную сборку
Форвард декларации невероятно ускоряют сборку, отрицать это сложно.Можно вообще не собирать что-то, оно в собранной заранее dll, от которой есть лишь форвард декларации. Это применяется почти во всех программах
Плюс форвард декларации делают сборку гораздо лучше параллелизуемой
станет станет, потому что компилятор будет знать что происходит и это сильно упростит инкрементальную сборку
Как вообще компилятор может знать меньше о том, что происходит в макросах, которые он сам же и вызывает, по сравнению с генерацией кода вне макроса? А с инкрементальной компиляцией всё и так просто - макросы считаются чистыми функциями, запускаются снова только когда код в пределах крейта изменился.
Форвард декларации невероятно ускоряют сборку, отрицать это сложно...
Не собирать вообще крайне редко получается. Например, если есть темплейты - то уже не всё получится в динлибу сложить. А если хочется опции компиляции поменять или под какую-то другую платформу собрать проект, то всё ещё придётся собирать самому.
Да и то, максимум на один раз меньше собирать библиотеку придётся, дальше всё равно инкрементальная компиляция будет работать и значительно снизит количество работы.
К слову, иногда и в rust можно выделить код в dll, например, это делает bevy, чтобы снизить количество времени, проводимое в линковке в дебаг сборках.
Как вообще компилятор может знать меньше о том, что происходит в макросах, которые он сам же и вызывает
он вызывает их в отдельных процессах и не знает что они делают. Я уж промолчу про эффективность создания процессов на макросы
Ну а какая разница, что именно они там делают - главное чтобы на входе им был TokenStream, и на выходе тоже был TokenStream.
Также - откуда информация, что создаётся по отдельному процессу на макрос? Макросы собираются в динамическую библиотеку, после чего функции из них вызываются компилятором.
Ну и третье - а какое решение по вашему лучше? В С++ нет аналогичного макросам механизма, максимально близкое - это вызывать системой сборки скрипты, которые генерируют исходники. И не думаю, что этот подход лучше макросов.
погуглите макрос try_compile
Создаёт ФОРК КОМПИЛЯТОРА и пытается скомпилировать код, в другом форке отвечая скомпилировалось или нет
погуглите макрос try_compile
Вы бы ссылку скинули, искать макрос по названию без названия крейта, в котором он находится... ну такое.
К слову, даже не зная, о каком макросе вы конкретно говорите, есть разница между
он (компилятор) вызывает их (макросы) в отдельных процессах и не знает что они делают
и вызовом компилятором макроса в том же процессе, который уже запускает отдельный процесс.
Этот макрос часть проекта https://github.com/scufflecloud/scuffle, нужен им для тестирования:
📦 postcompile: A macro for compiling Rust code at runtime. Useful for snapshot testing.
Вообще первый раз вижу, чтобы макрос делал fork, и нельзя ли было без него обойтись. В остальных 99% макросов никаких форков нет, и все в одном процессе.
А вы заявляете как будто любой макрос в Rust запускает что-то в отдельных процессах, что очевидная ложь. Вобщем продолжайте набрасывать дальше..
Вы нашли не тот макрос. Вот смотрите
https://github.com/m-ou-se/whichever-compiles?ysclid=mc1lrjb7od652533761

Rust запускает что-то в отдельных процессах, что очевидная ложь
Есть куча документации прямо говорящей, что процедурные макросы компилируются в отдельные библиотеки, запускаются в отдельных процессах, в коде компилятора сами разбирайтесь зачем им сервера и клиенты макросов
https://github.com/rust-lang/rust/blob/master/compiler/rustc_expand/src/proc_macro_server.rs
Ну вы можете считать правду про любимый язык вбросами
Вы написали
погуглите макрос try_compile
Я и искал try_compile. А сейчас вы скинули совсем другие макросы. И вы в скриншоте вырезали строку "Please do not use this.", что как-бы намекает..
Есть куча документации прямо говорящей, что процедурные макросы компилируются в отдельные библиотеки, запускаются в отдельных процессах, в коде компилятора сами разбирайтесь зачем им сервера и клиенты макросов
Видимо вы не различаете декларативные и процедурные макросы. Последние по определению работают в отдельном процессе. Но вы писали про try_compile, который декларативный.
Зачем вообще использовать этот макрос за пределами автотестов компилятора и некоторых инфраструктурных библиотек?
Зачем нужны форвард декларации на уровне языка? Если очень нужно, можно все необходимые метаданные функций и типов из модуля запихнуть в обьектный файл. Если исходник модуля не изменился с последней компиляции объектного файла, то читаем все декларации оттуда (реализации функций, очевидно, не читаем, они не нужны компилятору, только линкеру) и не парсим исходник. Получаются те же форвард декларации, но автогенерируемые и в машиночитаемом виде.
Впрочем, даже так не обязательно. Это даст выигрыш лишь если преобразование исходника в AST занимает существенное время от общего времени компиляции. У вас есть информация о том, что в Rust именно парсинг узкое место? Потому что если нет, то никто не заставляет обращаться к AST нодам реализации, если мы компилируем не этот модуль. Можно пробежаться по верхам AST и, опять же, собрать одни декларации. Оверхед лишь на парсинг.
Форвард декларации появились в те времена, когда компилятор не мог держать в памяти полное AST дерево программы, а то и даже полный исходный текст одного модуля держать не мог. И надо было уметь компилировать файл на ходу, не загружая его в память целиком.
Сейчас это не актуально.
Также в те времена появились языки типа C/C++, эксплуатирующие форвард декларации в синтаксисе - он без них становится неоднозначным.
Например, в C и C++ выражение
A*B;
может быть как и арифметической операцией, так и декларацией переменной - зависит от того был ли A обьявлен типом или переменной. То есть для корректной работы парсера, он должен заранее знать всё, что может встретить.
В Rust (и многих других языках) такой неоднозначности нет - декларации переменных всегда предварены ключевым словом let, имена типов всегда идут после двоеточия и т д. Можно распарсить исходник не имея представления о том, что есть тип, а что переменная.
В rustc есть постоянные замеры производительности. И те этапы, в которых могли бы задействоваться форвард декларации не встречаются в топе этих замеров.
Основное время сжирает либо трейт-солвинг, либо собственно кодген(то есть llvm) - в разный крестах по-разному.
Инкрементальная и параллельная сборка в rustc поддерживается при том на хорошем уровне. При этом, инкрементальная сборка не требует компромиссов вроде pimpl и подобного.
Либо быстро, но грязно, либо медленно, но качественно. Гарантии Rust по безопасности все же ценнее, чем скорость компиляции, imho
И ни слова об отсутствии стабильного ABI и его влиянии на время сборки больших проектов.
Вот допустим у нас есть мега-фреймворк на C++ и Rust типа Qt, допустим собирали мы его несколько часов.
Можем ли мы теперь им просто пользоваться и почти мгновенно пересобирать свое придожение?
С++ - да, Rust - нет, даже с хаками и плохой скоростью.
Единственное исключение - это LTO, если хочется всю программу с ним собрать то сборка релиза будет одного порядка скорости, по крайней мере с llvm бэком. Но LTO сборка обычно и не нужна каждый раз, это выжимание финальных ~5% на финальном этапе.
А вот попытка ускорения компиляции в разы чтобы было хотя-бы не так долго собиралось - явно ударит по производительности так что может стать не комфортно пользоваться для теста, и не понимаеш это так из-за сборки или пора оптимизировать, бенчмаркать его уже невозможно. Хаки с ir и патчинг врядли будут когда-то до конца юзабельными из-за архитектуры когда малое изменение одного крейта затрагивает кодогенерацию по всему приложению. jit на лету тоже такой себе аналог java для нативного приложения.
Пока единственное решение взять достаточно большую либу Rust и пользоваться без проблем - приделать к ней C интерфейс.
А что, отсутствие стабильного ABI как-то влияет на время сборки? Если что, нестабильное оно только между релизами компилятора, в пределах одного релиза оно вполне стабильное и возможны как инкрементальная сборка, так и динамические библиотеки без перехода в C ABI. Так делает тот же bevy для ускорения повторной сборки.
Конечно мешает, у инкрементальной сборки есть специальный шаг analysis, он и делает все возможное чтобы исключить то что не надо, но если вы посмотрите на шаг "optimization and codegen" инкрементальной сборки, то оно все еще ест львиную долю на реальных проектах.
Пример бенчмаркоа можно посмотреть например тут: https://blog.rust-lang.org/2016/09/08/incremental/
Статья не самая свежая, если у вас есть бенчмарки новее - кидайте, мне тоже интересно было бы посмотреть.
Ну и "пользоваться без проблем" включает в себя в том числе переживание апдейтов компилятора, хотя-бы минорных. И вообще частая ситуация, когда толстая либа вообще на билдере собирается, а ты потом и локально и на другом билдере можеш собирать. Держать везде окрушение прям совсем одинаковое весьма проблематично, да и cargo пока не поддерживает режим где тебе с билдера промежуточные объектники выдаст, которые в инкрементальной сборке можно дальше использовать.
Даже в текущей статье говорится про рост скорости с 22 по 25 год в почти 2 раза. Про 16 год вообще смешно тогда говорить
Другие компиляторы тоже не стоят на месте, на том же clang это тоже должно было сказаться. Конечно все хотят и пооптимизирование и побыстрее. Поинт же не в этом, поинт в том что архитектура такая - что даже инкрементальная сборка делает львиную долю компиляции.
Про то что analyze шаг тоже не бесплатный вообще молчю, он доложен пройтись по всему "миллиону" файлов, их отркрыть и прочитать чтобы проанализировать, это вот вообеще не про собрал либу и пользуйся.
Стабильный ABI позволяет скачать готовые бинарники какого-нибудь Qt и никогда его не собирать руками.
Или собрать один раз руками, а потом использовать во всех проектах на этой машине.
Rust собирает с нуля каждый крейт в проекте. Инкрементальная сборка работает только в рамках одного проекта на одной машине. И ещё нельзя взять чужой результат инкрементальной сборки и самому не собирать тяжёлые библиотеки, а только свой код.
У стабильного ABI есть ещё и минусы - например, шанс застрять с неоптимальной реализацией хэшмапы.
Также... а зачем все эти трудности с переносом инкрементальных билдов, когда с нуля всё собирается за минуты? Вот только что проверил на одном из своих гуишных проектов (тык), в дебаге с нуля собирается на моей машине за 56 секунд, а в релизе - за 2 минуты. Неиллюзорный шанс потратить на скачивание готовых бинарников вручную больше, чем на такую сборку.
Ну так кода мало совсем, аналогичное кол-во кода на си тоже будет быстро собираться.
К тому же вы там используете lua и ewext.dll, как бы вы к ним вклинились без ABI?
ABI нужно чтобы была точка входа, где один раз собрал, - много раз использовал во многих местах. Расставлять эти точка разумеется желательно так что скорость выполнения страдала минимально.
Хэшмапы будут уже внутри большой либы, внутри либы и ABI им действительно не нужен и если символы корректно скрыты, то с этим все должно быть в порядке и в C/C++.
Кроме самого проекта там ещё около 600 зависимостей, сборка которых тоже в эти 2 минуты включена. Не Qt конечно, но достаточно зависимостей, чтобы интерфейс и прочее было.
ewext к слову тоже на rust, и использует C ABI для ffi, конечно.
Часть из этих зависимостей - биндинги к сишным либам, например opus.
Ну и из-за того что зависимостей много и имеем аж 2 минуты, это не так мало, но аналогичный сишный код должен тем же clang компилиться примерно столько же.
Не вижу причин почему нет т.к. львиная доля оптимизирующего компилятора ir->машинный код по сути через тот же бэк идет, у rust только различие что почти все блоки noalias помечены.
ewext к слову тоже на rust, и использует C ABI для ffi, конечно.
Ну вот о чем и речь, единственный вариант как сейчас это можно сделать. А так бы вы ее тоже собирали, и так очевидно до любого времени сборки\пересборки можно дойти, вопрос лиш в объеме проекта.
Так-то отсутствие стабильного abi не мешает собрать и динлибу, и её пользователя одинаковой версией компилятора, не сходя до C abi.
ewext к слову в том же репозитории, но является отдельной от noita-proxy вещью и собирается под другой таргет (windows x32), а вызывается из lua, так что там кроме C abi вариантов нет, да.
Как уже писал выше, речь чтобы обновление компилятора тоже переживало, иначе проблем не оберешся, на билдере одна версия, локально другая и все, приехали.
Но проблема не только в этом, теоретически сделать можно было бы, но в rust оно пока не до конца нормально работает по моим эксперементам, вот по всем типам:
cdylib staticlib - для C ffi, вычеркиваем.
rlib - архив где объектник с мета инофрмацией, там есть пути до файлов на это машине, перемещать его нельзя.
dylib - для простого сложения генерит 14/2mb debug/release .so, это не то что бы хотелось для простого add(), особенно если потом это а приложение подкладывать и их несколько.
Способа не дублировать рантайм в них я пока не нашел, когда для традиционных либ несколько экземпляров code1.so code2.so -> one_runtime.so/exe обычное дело.
Вот минимальный пример https://github.com/rob9315/corrosion-dylib
Лично я не пробовал, но случаем задание RUSTFLAGS="-C prefer-dynamic=yes"
при сборке не заставит линковать std динамически, таким образом сильно снижая размер каждой динлибы?
Сгенерился пустой .so:
0000000000004008 b completed.0
0000000000001040 t deregister_tm_clones
00000000000010b0 t __do_global_dtors_aux
0000000000003e30 d __do_global_dtors_aux_fini_array_entry
0000000000004000 d __dso_handle
0000000000003e38 d _DYNAMIC
00000000000010fc t _fini
00000000000010f0 t frame_dummy
0000000000003e28 d __frame_dummy_init_array_entry
0000000000002000 r __FRAME_END__
0000000000003fc8 d _GLOBAL_OFFSET_TABLE_
0000000000001000 t _init
0000000000001070 t register_tm_clones
0000000000000000 N rust_metadata_dylib_8f917b1ee30d8ed1
0000000000004008 d __TMC_END__
Да вы сами можете поэксперементировать на репе выше.
Хм, вроде как функция есть, последняя строка:
quant@Fenrir:~/extproj/corrosion-dylib/src/bin$ RUSTFLAGS="-C prefer-dynamic=yes" cargo run
...
Hello, world!
dylib: 1 + 1 = 2
quant@Fenrir:~/extproj/corrosion-dylib/src/bin$ objdump -T ../../target/debug/libdylib.so
../../target/debug/libdylib.so: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 DF *UND* 0000000000000000 _RNvCscSpY9Juk0HT_7___rustc12___rust_alloc
0000000000000000 DF *UND* 0000000000000000 _RNvCscSpY9Juk0HT_7___rustc14___rust_realloc
0000000000000000 w D *UND* 0000000000000000 __cxa_finalize
0000000000000000 DF *UND* 0000000000000000 _RNvCscSpY9Juk0HT_7___rustc17rust_begin_unwind
0000000000000000 DF *UND* 0000000000000000 _ZN4core9panicking11panic_const24panic_const_add_overflow17h6145de6f6a28f279E
0000000000000000 w D *UND* 0000000000000000 _ITM_registerTMCloneTable
0000000000000000 w D *UND* 0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000 DF *UND* 0000000000000000 _RNvCscSpY9Juk0HT_7___rustc19___rust_alloc_zeroed
0000000000000000 DF *UND* 0000000000000000 _RNvCscSpY9Juk0HT_7___rustc14___rust_dealloc
0000000000000000 DF *UND* 0000000000000000 rust_eh_personality
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
0000000000000000 g DO .rustc 0000000000000897 rust_metadata_dylib_d6f206bc2ada0c34
00000000000010f0 g DF .text 0000000000000030 _ZN5dylib3add17h55c63aad830c226bE
Единственное - я убрал всё, кроме варианта с dylib, потому что иначе не собирается, и я пока не разбирался, почему.
Да, в дебаге есть символ, а в релизе нету, вот такие странности.
Да, действительно. Судя по всему уровень оптимизации влияет (если opt-level = 0 поставить в release конфигурации, то символ возвращается). Правда почему тут функция была заинлайнена между крйтами несмотря на выключенный lto я не уверен.
Похоже это не просто какой-то inline, похоже он вообще полностью проигнорировал dylib, достал где искать исходник и вкомпилил все из исходников, то-есть в релизе dylib не dylib.
Так же похоже терминирует он не в произвольный свой ABI, а в C++, что make sense при использовании llvm, там даже манглинг имен от с++, а so зависит от libgcc.
То есть как я понял никакой поддержки своего нестабильного ABI нет, ну кроме добавления в конце хеша в с++ манглинг, потому то и используется только в дебаге. А в релизе использование из разных мест может требовтаь разного ABI, потому терминирование вообеще всего в с++ ABI, включая кусков рантайма пытаются избежать.
Есть еще проблема: в dylib включаются только куски рантайма которые нужны. И если cargo не позаботится влинковать в прогу все что надо для dylib, - он очевидно не загрузится. Нам просто повезло что наш add зависит только от global_dtors и panic_const_add_overflow, потому даже если рантайм исключим то загружается. А вот более сложный пример уже не работает, ну и если в приложенине вообще подсовывать dylib`ы о которых оно не знает то тут даже cargo никак теоретически не спасет. Может есть возможность влинковать в приложение полный раст рантайм, но я пока не знаю как.
Так что вот, в контексте времени сборки большого проекта и использования большой либы, - пока все же есть проблемы, даже в рамках одной точной версии компилятора (чего тоже не достаточно).
те, кто жалуются на скорость rustc, должны посмотреть на скорость работы dotnet build 😆
Я переводил статью про скорость компиляции 5 лет назад.
С той поры времени много утекло, Раст вот ускорился вдвое.
Но думаю, общий расклад поменялся не очень сильно.
Оригинальный автор обновляет репу с тестами, каждый может проверить текущее состояние сам.
Почему Rust так мало волнует производительность компилятора