Как стать автором
Обновить

Комментарии 281

Я склонен верить, что в большом количестве сценариев у Rust есть преимущество в плане возможности проведения различных оптимизаций, потому что это memory-safe язык. Насколько я понимаю, оптимизаторы Си сильно ограничены из-за того, что в языке есть арифметика указателей и не всегда можно однозначно сказать, используется ли какой-то определенный фрагмент памяти или нет (strict aliasing?).

Да но в расте это не включено везде, потому что llvm ломается

А что ещё осталось включить?

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


Можно и щас включить через флаг, но на свой страх и риск.

Я склонен верить, что в большом количестве сценариев у Rust есть преимущество в плане возможности проведения различных оптимизаций, потому что это memory-safe язык

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

Простейший пример:
  foo(int& x, int& y, int[] z) {
    ...
    x = y;
    ...
  }


Компилятор C++ обязан в этом месте читать из памяти и писать в память — потому что он не знает объекты x и y — это один объект или это разные объекты… а может один из них — это ещё и элемент массива?

В Rust вся эта информация доступна не только программисту, но и ком пилятору и он может её использовать… однако пока что он использует её плохо, да ещё иногда и генерит нееправильный код — потому в стабильной ветке Rust подобные оптимизации отключены.
"некоторые оптимизации", про которые написал я, это не те "некоторые оптимизации", про которые написали вы. Потому и "некоторые"

Используйте в С/С++ restrict и будет вам счастье.

Как было сказано выше, в гцц и ллвм есть баги с restrict: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87609 и https://bugs.llvm.org/show_bug.cgi?id=39282. Нашли баги из-за раста, в котором этот restrict раньше подставлялся автоматически для каждой &mut ссылки. Сейчас из-за баги не подставляется, ждут фиксов в ллвм, чтобы потом опять включить эту оптимизацию.


А вы дальше советуйте restrict. Пусть люди стреляют себе в ногу.

Язык с ручным управлением памятью быстрее, чем язык с автоматическим при прочих равных. Ваш К.О.

Хотя немножечко неточно — тесты не при прочих равных, т.к g++ != LLVM, надо было сравнивать одинаковый тулчейн.
Ну по поводу автоматического управления памятью в раст, спорный вопрос.
По поводу терминологии можно спорить до посинения, но практически граница проходит между языками, умеющими автоматически «разрывать циклы» и языками, которые этого не делают. А считать ли Rust (и современный C++, кстати) «языком с GC» или «языком без GC» — это неважно.

Потому что языки, «разрывающие циклы» — платят за это высокую цену. Как мне кажется… слишком высокую цену.

Потому что в таких языках вы не знаете когда и как вы будете «платить» за выделение и освобождение памяти, можете только догадываться — опираясь на конкретную реализацию GC конкретно вот в конкретной версии вашего runtime. А завтра — всё может быть совсем по-другому.

О том что это важно можно заметить по количеству статей, посвящённой этому вопросу на Хабре (да и на любом сайте где обсуждается C#, Java, JavaScript и тому подобные языки). Рано или поздно об этом приходится задумываться всем, кто на этих языках работает.

А вот при обсуждении C++ тонкости общения с jemalloc'ом или tcmalloc'ом итересуют очень малый процент разработчиков… потому что если вы не пытаетесь «разрывать циклы», то вы всё ещё не можете сказать точно какую цену вы заплатите за выделение и освобождение памяти… зато можете сказать точно — когда эта цена будет нулевой… и оказывается что для 99.99% случаев на практике — этого достаточно.
Что значит разрывать циклы? break и continue?

Речь про циклические зависимости. Например:


void Foo() 
{
  var a1 = new A();
  var a2 = new A();
  a1.Next = a2;
  a2.Next = a1;
}

В С++ подобный код вызовет утечку памяти (если использовать рефкаунтинг), в расте оно в таком виде не скомпилится, а при попытке переписать на рефкаунтинг будет поведение как в плюсах, а в языках с трассирующим гц он спокойно обнаружит цикл и удалит оба объекта.

Поправка «если неправильно использовать рефкаунтинг» — но да, все возможно, компилятор не спасет. Верно так.
Как вы увидели, что код по вашей ссылке падает, если он даже не собирается.
Потому что не Раст. ЛОЛ

Я проверил верно исправленный — не падает.

Посмеялся, глядя на процесс правок. Нет чтобы учебники читать, всё критикуют…
cout << (x->Next).lock()->Value;
так нельзя, lock() может вернуть nullptr

Спасибо, поправил.




Основная идея тут не в том, что я не умею проверять что weak_ptr.lock() вернул нулл, а в том, что структура данных именно что должна владеть данными, а не просто ссылаться. В некоторых случаях типа деревьев weak_ptr может помочь, но и в расте точно так же получится Weak использовать.


Речь про полноценный ГЦ была именно в том что разработчику там не надо забивать себе голову — он всегда делает сильную ссылку, а с циклом разберется рантайм. И это действительно помогает. А еще это помогает делать структуры вроде "массив и ссылка на первый элемент". Self-referential structures огромная морока в расте, для которой целый Pin изобрели, в плюсах я не слышал ни про какое известное решение, а в языках с ГЦ это вообще не проблема.




так нельзя, lock() может вернуть nullptr

Ну вот, так и знал что мне про это напишут...

:) Ну да, поскольку тут владение a2 по сути отсутствует (weak_ptr не дает владения), то в момент завершения Foo() для него вызовется деструктор, expired() для соответствующего weak_ptr вернет true, а lock() вернет nullptr.

P.S. Честно говоря, рефкаунтинг сам по себе и не способен бороться с циклическими зависимостями. Поэтому в питончике вдобавок к рефкаунтингу добавили (отключаемый) gc.
Поэтому в питончике вдобавок к рефкаунтингу добавили (отключаемый) gc.
В третьей версии вроде как уже неотключаемый.

Но споры на тему «а насколько это нужно» будут вечными. Потому что тут речь же не о теоретической оценке чего либо, а о том «насколько сложно руками разрывать циклы» в сравнении с «насколько сложно бороться с GC, который, внезапно, начинает собирать мусор тогда, когда нам это невыгодно».

С учётом того, что одна из самых популярных в мире платформ (iOS) обходится без «полноценного GC»… похоже что Rust идёт в верном направлении…
Стив Клабник не совсем ключевой разработчик — он главный в команде документации. Хорошо знает, как устроен GC, Patric Walton, но он пишет в основном в твиттере ссылка
НЛО прилетело и опубликовало эту надпись здесь
Язык с ручным управлением памятью быстрее, чем язык с автоматическим при прочих равных. Ваш К.О.
Вот только прочие — неравные. Ваш К.О.

Собственно многие программы до сих пор написаны на Fortran, потому что аналогичные же программы на C++ — медленнее. А медленнее они как раз потому что «Язык с ручным управлением памятью» — не всегда быстрее.

Разработчикам компиляторов C++ проблемы алиасинга реально часто мешают порождать оптимальный код. И у Rust'а тут вполне себе есть преимущество.

P.S. И да: тут как раз вопрос лежит в практической плоскости. Теоретически любую программу на Fortran вдумчивой работой с типами и restrict можно довести до более быстрой программы на C++. А вот практически — у реально существующих разработчиков — это не выходит… Такие дела.
В С++ применяется ARC — еще раз, это Automatic Reference Counting. Я не понимаю, почему в противопоставлении вы пишете, что С++ — с ручным управлением %-)

И кстати, я немного отстал от жизни и не помню наличия работы с хипом в Фортране.
UPD. В Фортране95 есть ручное управление памятью операторами ALLOCATE, DEALLOCATE.
Собственно тезис строго наоборот.

unique_ptr — вовсе не подсчет ссылок. shared_ptr — это подсчет ссылок. Но в хорошей кодовой базе его почти не должно быть. Он в идеале нужен только для shared ownership (внезапно), а оно нужно очень редко.

В блоке управления он вполне себе атомарный


Note that the control block of a shared_ptr is thread-safe: different std::shared_ptr objects can be accessed using mutable operations, such as operator= or reset, simultaneously by multiple threads, even when these instances are copies, and share the same control block internally.
shared_ptr содержит атомарный счетчик в контрольном блоке. Точнее, целых два атомарных счетчика — отдельно для weak и strong ссылок. Так что время жизни разделенного объекта, как и время жизни контрольного блока, корректно обрабатывается в многопоточке*. Доступ к самому объекту при этом не является потокобезопасным.

*За исключением юзкейса, когда два потока пытается без синхронизации менять один и тот же инстанс shared_ptr. Тогда реальное число инстансов может рассинхронизироваться со значениями счетчиков. Для этого существует набор атомарных операций над shared_ptr, а также в стандарте будет std::atomic_shared_ptr.

Советую читать Мейерса
Хороший совет — он в книжке как раз обо всём этом рассказывал.
В C++ применяется автоматический подсчёт ссылок и много чего ещё — но это всё конструкции библиотеки (стандартной или нестандартной), а не языка.

Компилятору очень мало чего о семантике всего этого известно.

В Rust же все ограничения заложены в язык, попадают в IR и далее в оптимизатор… где они, в настоящее время, особо не используются, так как оптимизатор заточен, в первую очередь, под C/C++.
С++ прекрасно проверяется линтерами, которые знают про стандартную библиотеку (она же часть Стандарта), и например простой пример — gcc проверяет строку формата printf, хотя теоретически тоже не должен ничего знать.

Так что насчет неверного утверждения про Фортран? Пока что оно подтверждает моё
Пока что оно подтверждает моё
Не очень понимаю о чём вы там говорите.

Так что насчет неверного утверждения про Фортран?
А что там с фортраном не так? SciPy уже без него собиратеся? Нет? Ну значит пока — реально существующий код написанный на Fortran всё ещё быстрее реально существующего кода, написаного на C++.

То что в Fortran «сливает» в Benchmak Game — это нормально: это достаточно ограниченный язык, некоторые алгоритмы на него ложатся плохо. Однако в своей области компетенции — он быстрейший… о чём, собственно, и речь.

Rust претендует на то, что будет быстрее C/C++, но при этом ещё и всегда, а не только при работе с матрицами, как Fortran… поживём, увидим.
Язык с ручным управлением памятью быстрее
Фортран с ручным, C++ с автоматическим. Мое утверждение тут не противоречит вашему.

С++ язык с автоматическим управлением? Простите, но — нет.

Ну если у вас C++ стал языком с автоматическим управлением памяти, то что-либо обсуждать становится бессмысленно.
НЛО прилетело и опубликовало эту надпись здесь
Конечно так. Поздновато от С++ требовать идеала =)

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

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

С точки зрения компилятора C++ — это язык с ручным управлением памяти. А Fortran, опять-таки, с точки зрения компилятора — язык с ручным управлением памяти (то, что вы назвали «отсутствие работы с хипом», как раз).
Кажется я начинаю понимать… Но тут не раст и не ява, которые имеют исключительно свою точку зрения.

Я предпочитаю иметь свою т.зр. в своих программах:
1. Использую new/delete — ручное управление
2. Использую unique_ptr — RAII
3. Выбираю shared/weak_ptr — ARC
4. Подключаю boehm — оппа — программа с GC
5. Беру в расход только автоматическую и статическую память — вообще красота — никаких нежданок, никакого управления…

Ну да, конечно — это мое ручное управление всей подсистемой памяти, компилятор то не знает =)

Но в современном C++11 и новее рекомендуется таки пп2,3 — которые относятся к автоматическим
Ну да, конечно — это мое ручное управление всей подсистемой памяти, компилятор то не знает =)
Именно так. Это не смешно, а довольно грустно. Например потому, что любая манипуляция с память через указатель на char может, потенциально, изменить значение любой переменной, адрес которой куда-то, когда-то, хоть раз передавался.

При этом «стрелять» это может самым неожиданным способом. Сравните, например, foo и bar — и подумайте почему случае с bar компилятор смог показать всю свою изворотливость (и развёрнутый цикл и векторизация и вообще всё круто), а вот foo — осталась, фактически, неоптимизированной.

Но в современном C++11 и новее рекомендуется таки пп2,3 — которые относятся к автоматическим
Да — это облегчает работу программисту, но… увы и ах — не компилятору. Более того — эти все чудесные обёртки в системной библиотке зачастую усложняют работу компилятору… ну вот простейший (хотя и довольно-таки патологический) пример… внушает, да?

Именно за счёт этого языки с автоматическим управлением памяти (не путать с трассирующим GC) могут иногда выигрывать у C/C++… а иногда — и довольно-таки заметно выигрывать…
При этом «стрелять» это может самым неожиданным способом. Сравните, например, foo и bar — и подумайте почему случае с bar компилятор смог показать всю свою изворотливость (и развёрнутый цикл и векторизация и вообще всё круто), а вот foo — осталась, фактически, неоптимизированной.

Компилятору можно и подсказать. Ну да, char* в C/C++ — это «особенный» тип, ничего не попишешь. Но средствА есть.
СредствА-то есть, но вот только эти многоуровневые системы костылей — честно говоря начинают надоедать.

Так что если Rust будет показывать сравнимую производительность на идеоматичном коде без специальных забот обо всех этих тонкостях (типа «разворачивания» std::unique_ptr для возврата значения из функции и заворачивания его обратно в std::unique_ptr в месте получения) — то это будет разумный довод в пользу перехода.

Потому что сейчас ситация такая: на C++ можно сделать так, чтобы твоя программа была максимально быстрой… но умеют делать это один человек из ста — и то требуется много возни и чуть ли не изучение каждой функции в дизассемблере, чтобы не перепутать куда какие костылики и как вбивать… неудобно.
Такие «тонкости», как с unique_ptr могут и починить в будущих версиях компилятора. Меня в расте печалит главным образом его упертость в плане «компилятор лучше знает». Да, мне уже на это отвечали, дескать, «чистый код не самоцель», но… тогда получается, что шило меняется на мыло. К тому же мне надо решать задачи, а не писать обертки, и тем более не тешить свой NIH-синдром, занимаясь RIIR, а если что-то где-то надо оптимизировать в узком месте, то средствА всегда найдутся. В общем, я пока не готов.
НЛО прилетело и опубликовало эту надпись здесь
Ну а чо мы, лысые, что ли. Те же RVO и copy elision же запилили.
RVO и copy elision не зашиты в стандартах. unique_ptr, увы, зашит.

А поскольку это затрагивает «священную корову» — обратную совместимость — то шансов на исправление, мягко говоря, немного.
НЛО прилетело и опубликовало эту надпись здесь
RVO уже зашито.
только потому, что эта оптимизация может влиять на наблюдаемое поведение.
НЛО прилетело и опубликовало эту надпись здесь
Такие «тонкости», как с unique_ptr могут и починить в будущих версиях компилятора.
Нет, не могут. Это ABI.

Теоретически можно выпустить новую версию ABI — но шансов на это примерно нуль.

А в Rust — это не зафиксировано пока в ABI никак и, когда зафиксируют, могут сделать лучше.
Ну да, пожалуй, не сломав обратной совместимости, этого не починить.
типа «разворачивания» std::unique_ptr для возврата значения из функции и заворачивания его обратно в std::unique_ptr в месте получения
ABI в обе стороны «играет» — сегодня на коне раст, а завтра плюсы. Хотя казалось бы… А еще ABI не имеет значения при инлайнинге функций
Потому что сейчас ситация такая: на C++ можно сделать так, чтобы твоя программа была максимально быстрой… но умеют делать это один человек из ста
собственно точно так же и в расте и примерно любом другом ЯП.
ABI в обе стороны «играет» — сегодня на коне раст, а завтра плюсы. Хотя казалось бы…
А что именно «казалось бы»?

Rust имеено потому и не имеет аналога Itanium C++ ABI, что не хочет, чтобы «замороженный» раз и навсегда ABI приводил к тому, что подобные вещи нельзя было бы исправить.

А вот у C++ — приоритеты другие.

Потому «на дальней дистанции» Rust должен бы иметь преимущество.

собственно точно так же и в расте и примерно любом другом ЯП.
Ну вот эти «игры» и помогают понять насколько нужно отказываться от «типичных идиом» ради скорости.

Core Guidelines прямо говорит: используйте unique_ptr<T> и создавайте эти переменные с помощью make_unique… но на самом-то деле нужно использовать new и возвращать gsl::owner, если мы хотим «эффективности как в C»!

Вот и интересно понять — насколько реально быстрый код на Rust отличается от «идеоматичного», «рекомендуемого»…

Ну на нашей практике максимально тупой идиоматичный код выдал практически максимальный перфоманс, судя по профайлеру и загрузке процессора. Еще в 2 раза выжали, пройдясь vtune'ом и расставив стратегические префетчи где надо, убрав работу с памятью.


И судя по всему, это не у нас одних.


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

А что именно «казалось бы»?
а вы глянули в мои примеры? Изменение кажется незначительным, но переворачивает всё с ног на голову.
Rust имеено потому и не имеет аналога Itanium C++ ABI, что не хочет, чтобы «замороженный» раз и навсегда ABI приводил к тому, что подобные вещи нельзя было бы исправить.

Потому «на дальней дистанции» Rust должен бы иметь преимущество.

пока rust не стабилизирует ABI, ни о какой «дистанции» речи и быть не может. А дальше будет иметь значение компромиссы какого из ABI лучше применимо к конкретным приложениям.
пока rust не стабилизирует ABI
А почему вы считаете, что он это, вообще, должен делать?

Движение вообще в другую сторону идёт: clang умеет отказываться от ABI для static функций, к пример, чтобы быстрее было.

Это и gcc/msvc уже очень давно делает, и не только для статик, но и для всех при lto/pgo. ABI он наружу, чтобы статические либы и dll\so не протухали каждые несколько месяцев.

ABI он наружу, чтобы статические либы и dll\so не протухали каждые несколько месяцев.
Тем не менее C++14 ABI и C++17 ABI несовместим, так что с некоторой частотой это всё равно происходит.

Я уже не говорю про разные компиляторы.

В общем в отношении Rust'а моё отношение всё ещё немного скептическое… но уже совсем немного…

Ну это да. И это порождает определенные проблемы, из-за этого даже вводят всякие _GLIBCXX_USE_CXX11_ABI, и оно даже реально пригождается в реальной жизни. И вот представим что на это забьют и это все станет регулярным. Раз в 3 месяца пересобираем весь мир, к примеру (а проблем с пересборкой конечно же ни у кого не возникнет). А проприетарщина вообще, кому она нужна? Давайте исходники, либы не принимаем.

А почему вы считаете, что он это, вообще, должен делать?
представьте, что вы хотите поставлять вашу библиотеку без исходников — совершенно адекватный коммерческий юзкейс. И вам наверно захочется чтобы эту библиотеку можно было использовать не только из-под ubuntu 18.1.2 и rustc 1.39.16 на компьютерах с intel i7 8-ого поколения, которым это всё барахло компилируется у вас. Точнее, компилировалось у вас вчера. А сегодня вы обновили компилятор до rustc 1.40.1 (nightly) чтобы посмотреть очередную классную фичу, а еще обновили librustrt.so, и отныне всем вашим клиентам нужно будет воспроизвести эти шаги чтобы работать с вашими новыми библиотеками.
представьте, что вы хотите поставлять вашу библиотеку без исходников — совершенно адекватный коммерческий юзкейс.
Прикрутите к ней API на C — и поставляйте.

Попытки «заморозить» C++ ABI показали, что пользоваться этим всё равно умеют единицы, а проблемы это вызывает у всех.

То-есть пользоваться растом исключительно через другой язык? Где таки постарались и нарушения ABI редки?


И чем умеют пользоваться, и какие проблемы у всех возникают? C++ ABI умеют пользоваться единицы и у всех с ним проблемы возникают?
Ну так а вы предлагаете еще чаще эти проблемы сделать, а пользоваться умеют все — скомпилил и какой-то ABI получил. И его желательно не замечать, только чинить если у кого-то где-то сломается, а не ломать все время. Вот чинить да, может и единицы умеют. Посмотрите как это в firefox решается для stdc++, и это при редких изменениях, а при нестабильном — будет вообще кошмар.


Ломать его, конечно, приемлимо в некоторых случаях, например если ни либы ни dll\so не нужны. Но в остальном — чем реже тем лучше.

Где таки постарались и нарушения ABI редки?
Проблема не в том, что «там постарались и нарушения ABI редки», а в том, что если вы делаете «широкий», «быстрый» интерфейс, то сделать его эффективно — очень тяжело. А если у вас там сплошные «хенлы» и внутренняя реализация скрыта — то ничего, кроме того, что предоставляет C — не нужно.

Посмотрите как это в firefox решается для stdc++, и это при редких изменениях, а при нестабильном — будет вообще кошмар
Три или четыре раза её меняли полностью несовместимым образом. Несколько раз обнаруживали несовместимость и чнили. Заметьте: libstd++ — это библиотека, разработчики которой прилагают титанические усилия для поддержания совместимости — и всё равно, как вы говорите: это проблема.

Но в остальном — чем реже тем лучше.
Это теория. А на практике — libicu имеет 60 несовместымых версий. Boost — сравнимое число. И это ведь — «золотой стандарт», библиотеки, которыми пользуются огромное количество народу! А в более мелких народ вообще не задумывается о том, чтобы была хоть какая-то совместимость.

Если требуется совместимость — то C++ интерфейс всё равно заворачивается в C интерфейс (как в libandroidicu) и потом «с другой стороны» на него снова вешается C++ обёртка.

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

Все так: glibc — мало проблем, libstd++ — больше, Boost — еще больше; libicu — не сталкивался. Но это не значит что им не пользуются.
У steam runtime, например, достаточно стабильное API/ABI, в пределах дистра благодаря этому куча кода шарится, даже у flatpak/snap идут ссылки на свои core для этих целей. Есть куча более стабильных библиотек что таких проблем не вызывают. И это будет еще только больше проблем если весь этот мир пересобирать раз в несколько месяцев.


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


К слову про производительность. Все последние изменения в stdc++ abi они из-за фич языка, не из-за производительности. Производительность да, это хорошо, и над этим думать надо. Но это редко надо, бесконечность итераций тут не надо для одной архитектуры для стабилизации.

Все последние изменения в stdc++ abi они из-за фич языка, не из-за производительности.
Потому что изменения для увеличения производительности потребуют изменить ABI — а он священен.

Но это редко надо
Там где это не нужно — есть Java, Python… и C интерфейс.

У steam runtime, например, достаточно стабильное API/ABI, в пределах дистра благодаря этому куча кода шарится, даже у flatpak/snap идут ссылки на свои core для этих целей.
Если вы всё собираете одним компилятором из неизменных исходников — то не вижу проблем.

Проблема тут следующая: Я не собираю игру тем же компилером что steam runtime. И когда пакую ее во flatpak, я не собираю core, но ссылку на него указываю. Понятия не имею, что там за компилер использовался, но игра работает.


Ну то-есть ABI нужен везде, где надо что-то с чем-то совместить, компоненты, где твое может быть только часть. Поставить third party, собрать что-то один раз и использовать либу, сделать dll\so и экономить юзеру память, плагины к какому-то софту итд. Когда все это не надо — ну да, тут эффективнее lto и статическая линковка, ктож спорит.

Проблема не в том, что «там постарались и нарушения ABI редки», а в том, что если вы делаете «широкий», «быстрый» интерфейс, то сделать его эффективно — очень тяжело. А если у вас там сплошные «хенлы» и внутренняя реализация скрыта — то ничего, кроме того, что предоставляет C — не нужно.
вы предлагаете даже не пытаться поддерживать совместимость и писать сишные обертки? Вы же должны понимать, что это хуже буквально любой альтернативы?
Это то, что всё равно по итогу делается. Я не знаю ни одной «живой» операционки, предоставляющей из коробки C++ интерфейс хоть к чему-нибудь.

Даже какой-нибудь MFC — он вместе с приложением идёт и вам нужна ровна та версия, которая будет совместима ровно с тем компилятором, которым вы это приложение собираете…

Как это не видели? В винде msvcp*, это хоть и redist и его обычно с приложением поставляют (хотя от старых студий уже вроде в комплекте, от 6й точно), но оно будет шариться между всеми использующими эту стабильную версию приложениями, и ему нужен ABI. Так же directx redist включают, COM интерфейс это тоже не C.


Макось? Полно не сишных интерфейсов, и что печально — сишные opengl и openal депрекейтят.


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


Но вы абсолютно правы, что си интерфейс — наиболее ABI стабильный. А с++ и другие ломаются куда чаще. Именно потому его все любят, когда нужна совместимость. Именно потому — часто ломать ABI плохо, неважно stdc++, rust или go. Это вызывает проблемы в определенных случаях и их придется решать одним из способов.

Именно потому — часто ломать ABI плохо, неважно stdc++, rust или go.
И именно поэтому в ядре Linux специально ломают ABI, ага.

Понимаете — мантра «часто ломать плохо» работает, когда альтернатива не слишком сложна. Если же ABI всё равно регулярно ломается — то проще не делать вид, что его можно как-то «застабилизировать», а признать, что его нельзя использовать, если вы два компонента независимо собирать хотите.

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

Вы опять про все пересобрать. Вы понимаете, что если вы можете все пересобрать, — оно вам не надо. А если Far собран С++, а плагин на дельфи, и его создатель не имеет контроля над сборкой фара, и всех его версий у всех, — то ABI нужно. Да, оно кода-нибудь сломается, но ABI тут всё равно нужен, так что обратная мантра тоже не работает. И чем дольше оно не ломается — тем лучше. В вашем варианте мы просто не делаем плагины фару, всегда пересобираем из какой-то репы те что у вас есть.


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

А если Far собран С++, а плагин на дельфи
… то использовать как C++-строки, так и Delphi-строки в ABI вы не можете. И вам придётся, скорее всего, использовать C-style ABI.

И чем дольше оно не ломается — тем лучше.
Конечно. POSIX-api скоро полвека стукнет — а оно всё ещё «не ломается»

Кейсов где ABI нужен полно.
А где я с этим спорил? Продуманный ABI может жить десятилетиями… А вот ABI, который получился у вас, когда вы завели три класса и десять функций, а потом всё это проэкспортировали — нет.

А если вы уже разрабатываете ABI — то его можно и в C-функциях и в GBus, какой-нибудь, превратить… а если о нём не думать — то ничего хорошего не выйдет.

Нет, на винде ABI у дельфи для dll`ок вполне совместим. Ну то есть вы согласны что ABI иногда нужен, и что когда он долго не ломается — это хорошо?
Вы просто к тому что у других языков это не получится и надо всегда делать обертки на си? Ну хорошо, это же не противоречит тому, что ломать ABI плохо. Просто язык значит никогда не будет самостоятельным и требовать си для кейсов когда нужен ABI, именно по этой причине. И чем чаще происходит поломка ABI — тем более это верно.


Вот только не все спешат бежать делать си обертки для своих библиотек. А как миниум С++ и ObjC/Swift в реальной жизни вполне используют. Под виндой какой-то из стабильных msvcp, под линем stdc++ от какой-то версии и получают шаринг между приложениями и приемлимое время жизни приложения.


Отказ от поддержки ABI по сути будет перекладывание работы с разработчика языка на разработчиков библиотек\приложений для кейсов когда он нужен и он никогда не сможет стать именно заменой C/C++.

Ну то есть вы согласны что ABI иногда нужен, и что когда он долго не ломается — это хорошо?
Конечно. Но такой у Rust уже есть.

Вы просто к тому что у других языков это не получится и надо всегда делать обертки на си?
Зачем вам «обёртки на си». В C++ есть extern "C", в Rust тоже extern есть.

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

Ну тут я согласен. Можно тогда язык использовть через си ABI, раз своего нет. Но конкретно у раста это потребует всякие #[repr(C)] и libc::*, а не все это делают. Язык получается не самостоятельный и это все равно минус. Свой стабильный ABI — это хорошо, или сделать чтобы все нужные конструкции типа #[repr(C)] были автоматом при экспорте в либы\dll\so. Но постойте-ка, это и получился бы совой стабильный ABI.


А иначе — все таки перекладывание этой проблемы с языка на разработчика. Не рассчитана либа на си — придется решать. А если все либы рассчитаны на си (как вы предлагаетте, всем так делать) — ну так вот он, свой стабильный ABI, стандартизируем как его свой и проблема решена. А пока этого нет — будут делать и так и так и проблема останется.

А иначе — все таки перекладывание этой проблемы с языка на разработчика.
А стабильный API без разработчика никак не сделать.

Простейший пример прям из жизни: работаете вы с Xml и назвали вы детей childs (не native speaker, бывает). Потом, в следующей версии, пришёл грамматей и поправил. Стало вместо childs, как и положено childred.

И? Как вы эту проблему на уровне языка решать собрались?

А если все либы рассчитаны на си (как вы предлагаетте, всем так делать)
Нет. Большинство библиотек — вообще не требуют стабильного API. Они поставляются в исходниках и собираются так, как нужно.

А вот «большие» компоненты, поставляемые отдельно — да, здесь C ABI достаточно.

P.S. На самом деле самый разумный и, главное, практически отлично работающий подход к стабильному ABI — у разработчиков ядра Linux. Все знают, что внутри ядра — нет никакого стабильного ABI. Но не все осознают насколько, при этом, стабильно ABI на уровне системных вызовов. Windows отдыхает. Но да — это даром не даётся. Это нужно думать, планировать, моделировать, описывать. Делать это для каждой мелкой библиотеки — глупо и ненужно. Но если вы хотите поставлять библиотеку без исходников… то у вас нет выбора.

Не путайте проблемы API и ABI, я говорил только про ABI. API — да, проблема на стороне разработчика библиотеки.

#[repr(C)] — это не костыль, а механизм, позволяющий отделить ABI от деталей реализации

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

Подавляющее большинство либ на Rust (а на текущий момент — так вообще все) линкуются статически, на кой им стабильный ABI?

Так я про это и говорю: если не нужен ABI, не надо модулей, плагинов, шаринга кода/rdata между независимыми приложениями, удобного интерфейса к JIT и прочего — лучше линковать lto, это эффективнее и сделает каждой функции совой оптимальный ABI.


Речь же про кейсы когда он нужен, а для системного языка — это маст хев. Для потенциального заменителя C/C++ — тоже.

Ну так в этих редких кейсах и надо использовать #[repr(C)].

Это не редкие кейсы. Механизм совмещения — фича очень серьезная и используется повсемесно. Пирчем разные модули разрабытывают разные люди/команды/кампании. Связать все это очень важно. Например либу делает одини люди, а используют — другие, вы гарантируете что все либы это сделают, как предлагает khim? Получается я беру либу — проблема, еще беру — еще проблема. И что мне делать? А если все реально начнут делать #[repr(C)] и прочее необходимое, и других вариантов не будет — так проблемы тогда нет, тогда это и будет стабильный ABI для раста.


Вот вам еще пример: вот скомпилил я Qt под asan/tasn. Вот они у меня лежат и я их иногда использую. Мне вот очень не понравиться если все минорные обновления компилера начнут его ломать. Или если я не смогу слинковаться с либой из дистриба немного другим компилером, у мнея их вообще 2, clang и gcc, а в репе собиралось одним (причем точно не ими, потмоу что вот они обновились, а все остальное — нет). Понимаете масштаб проблемы?

Как это всё относится к Rust? В Rust вы не будете компилить какой-нибудь qt-rs отдельно от своей программы, а значит и ломаться тут нечему.

Я вам пытаюсь показать кейс, когда ABI нужен, вы пытаетесь показать кейс где не нужен. Как это к rust относится? Я же конкретно про него пишу — не все либы используют и будут использовать совместимость с си, потому проблема ABI останется. Надо или на си ABI перейти для любой внешней комоновки, или свой стабильный сделать, и это реально очень надо.
Просто возьмите кейс где он нужен, а не статическую линковку qt-rs только к совему приложению. Например задейтесь целью зашарить его загрузку между несколькоми независимыми приложениями, контроля над которыми вы не имеете.

То есть у вас есть три сторонних программы, которые статически слинкованы с одной и той же библиотекой, а вы пытаетесь её оттуда выдрать и сделать динамической?


Я правильно понимаю ваш кейс?

Не вы, у разработчиков приложений появится такая возможновть, если либа поддержит динамическую линковку. А если динамическия линковка — почти всегда нужен ABI. Вот представьте: обновился в репе компилер — и хоба, прилетает вместе все 40гиг установки, потому что все надо собирать именно с этой версией. Ставите компилер из тестинг репы потому что вам надо? Ваши проблемы. А так, ментейнер возьмет либу, возьмет приложения, скомпилит в пакадж, проверит что все работает. И весь мир в апдейтах прилетать не будет. Вы можете эти приложения локально собрать, подцепив либу из репы, компилер будет другим наверняка, но работать будет и память будет шариться. Это все благодаря ABI и возможности совмещать модули. Все это разношерстная кампания, разные люди, у них разные системы, разные версии и им вот просто очень сильно надо уметь совмещаться внешне без пересборки мира.


К стаитческой линковке это тоже относится — иногда оно тоже надо, пример с Qt я выше приводил (забыл упомянуть что статически собирал). В дистрах статические либы тоже присутствуют. С ними точно так же желательно не ломаться как можно дольше.

А так, ментейнер возьмет либу, возьмет приложения, скомпилит в пакадж, проверит что все работает.
Не возьмёт и не проверит. Все попытки продвижения GNU/Linux на десктоп, так или иначе, в это упираются.

Да, есть небольшое количество библиотек, которые «следят за гигиеной» и выпускают совместимые версии: libstdc++, Qt, в KDE кой-чего, может ещё пяток можно вспомнить… остальные же на это всё равно забивают.

Отсюда — все эти WinSxS, докеры и прочее. Люди всё равно испльзуют программы с «проверенными» библиотеками.

Все это разношерстная кампания, разные люди, у них разные системы, разные версии и им вот просто очень сильно надо уметь совмещаться внешне.
Не нужно. Разработчики дистрибутивов уже лет двадцать пытаются продвинуть эту «нирванну»… а воз и ныне там: разработчики всё равно пакуют библиотеки с приложением. Посмотрите на какой-нибудь Acrobat Reader: он всё равно тащит с собой ICU и OpenSSL, libORBit и libbonobo, libcurl и libJP2K… потому что это — самый просто способ уменьшить количество головной боли у техподдержки.

Да, можно говорить, что это плохо, неправильно и так далее… а толку?

Все верно, я и сам пакую и тащу все статически, для универсальной сборки, которую можно скачать, и запускать неизвестно где. Для винды тоже так делаю, даже с redist не люблю связываться. Вот для flatpak/snap можно указать core вполне, с проблемами пока не сталкивался. А вот в репе — можно и собрать, тянув все динамически. Если говорить именно про ABI — оно просто не соберется иначе, проблему сразу будет видно. И в репах таких приложений тоже хватает.

Если говорить именно про ABI — оно просто не соберется иначе, проблему сразу будет видно.
Если мы уже начинаем говорить про «оно просто не соберется» — значит речь уже не про бинарники, значит и стабильный ABI особо уже не нужен…

Про бинарник, который попадет в репу и будет тащить все динамически из нее, в репах таких хватает. Просто вы в основном API проблемы описываете, а ABI проблема тут крайне редко возникает (хотя я сталкивался), обычно это бывает если в git выкладывают предкомпиленые либы, вручную собранные.

Ну так в этих редких кейсах и надо использовать #[repr©].
вы сами то пробовали так делать? Просто на дистанции в пару десятков лет намного проще победить пару изменений ABI, нежели тянуть сишный интерфейс.

#[repr(C)] — это не обязательно сишный интерфейс, это просто указание не переставлять поля в структуре местами.

Неубедительно. В самом популярной на сегодня OS (а это, как бы, не Windows, а Android) все интерфейсы C-шные. Ни C++, ни COM там нету… и ничего — никто не умер.
Как это не видели? В винде msvcp*

msvcp — это не интерфейс ОС, а рантайм С++.


COM интерфейс это тоже не C

Но это и не С++. Кстати, кто-нибудь знает, он там патентами какими-то закрыт или как? Удобная же вещь, а за пределами продуктов Microsoft почти никем не поддерживается...

Да я вкурсе что такое msvcp. Речь про то что он ставится в систему и будет шариться между всеми приложениями, как stdc++ под линем. Чем не интерфейс не на си? Вот WINAPI — да, там везде си, в .net — уже нет, на маке тоже в основном нет. Но это все не к тому, что си интерфейс нажеднее все равно никто не спорит. Это как раз наглядно показывает что это нужная фича, и сишные интерфейсы часто из-за этого выбирают.


Что COM не С++ я тоже вкурсе, но это и не си, хоть из си и можно работать, из си вообще с чем угодно можно работать.

Речь про то что он ставится в систему и будет шариться между всеми приложениями, как stdc++ под линем.

В данном случае, это не достоинство msvcp, а скорее недостаток линя. Или недостаток винды, зависит от точки зрения.


Так-то и stdc++ можно в теории в нескольких копиях в систему поставить...

Все так. На винде решают проблему, поставляя redist c приложением и держа несколько копий мажорных версий в системе. На лине — поддерживая какую-то минимальную версию stdc++.
Так же на лине теперь доступен вариант и как в винде — указывая разные версии core в flatpak/snap, будем попадать на разные версии stdc++. В системе их тоже может стать несколько, и все еще будем их шарить между приложениями.

Но это обход проблемы стабильного ABI, а не её решение.

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

Если он совсем часто ломается — это так-себе ABI, поддержка усложнится и пользваться станут реже, или станут от чего-то отказываться, это однозначная проблема для кейсов где он нужен.
В Python ABI подерживается, грубо говоря год (в каждой минорной версии он свой). Много народу уже отказались?

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

Кэш он, может, и перекомпилит — но вот расширения на C — нет. А их, в общем, немало…

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

Так-то и stdc++ можно в теории в нескольких копиях в систему поставить...
Более того — нужно. Если вы старые приложения хотите использовать. А если вы используете библиотеки, собранные gcc 4.x и gcc 5+… получите «массу удовольствия» даже несмотря на одну версию libstdc++…
Это то, что всё равно по итогу делается. Я не знаю ни одной «живой» операционки, предоставляющей из коробки C++ интерфейс хоть к чему-нибудь.
а как же винда, знатная часть интерфейсов которой торчит наружу в виде COM-объектов, имеющих сишный и плюсовый интерфейсы?
COM-объекты прекрасно обрабатываются на C, а вот как раз чтобы из C++ с ними можно было работать — там особое расширение, а не стандартный C++
Прикрутите к ней API на C — и поставляйте.
для такой цели c++ подходит лучше раста
Попытки «заморозить» C++ ABI показали, что пользоваться этим всё равно умеют единицы, а проблемы это вызывает у всех.
что вы понимаете под «пользоваться ABI»? И какие «проблемы» у всех это вызывает? Что вы вообще понимаете под «ABI»? Вот например вот здесь:
Тем не менее C++14 ABI и C++17 ABI несовместим, так что с некоторой частотой это всё равно происходит.
вам известно о каких-то breaking changes о которых не известно никому другому?
вам известно о каких-то breaking changes о которых не известно никому другому?
Почему «неизвестно никому другому»? Известно. Ну вот вам простейший пример:
test.h:
struct Foo {
  int value;
};

struct Bar {
  static constexpr Foo foo{42};
};
test1.cc:
#include "test.h"

constexpr Foo Bar::foo;

const Foo* baz() {
  return &Bar::foo;
}
test2.cc:
#include "test.h"

const Foo* qux() {
  return &Bar::foo;
}
main.cc:
int main() {
}


В C++14 всё Ok:
$ g++ -c -std=c++14 test1.cc -o test1.o
$ g++ -c -std=c++14 test2.cc -o test2.o
$ g++ main.cc test1.o test2.o

В C++17 всё Ok:
$ g++ -c -std=c++17 test1.cc -o test1.o
$ g++ -c -std=c++17 test2.cc -o test2.o
$ g++ main.cc test1.o test2.o

А смешивать — нельзя:
him@khim1:/tmp/1$ g++ -c -std=c++14 test1.cc -o test1.o
khim@khim1:/tmp/1$ g++ -c -std=c++17 test2.cc -o test2.o
khim@khim1:/tmp/1$ g++ main.cc test1.o test2.o
/usr/bin/ld: test2.o:(.rodata._ZN3Bar3fooE[_ZN3Bar3fooE]+0x0): multiple definition of `Bar::foo'; test1.o:(.rodata+0x0): first defined here
collect2: error: ld returned 1 exit status


Получите, распишитесь.

Поэтому, в частности, у нас переход на C++17 происходил через flag day.

Эта несовместимость — известна, собственно никто её и не скрывает.

что вы понимаете под «пользоваться ABI»?
Ну вот то, про что вы пишите: выпустить либу — а потом новую версию без пересборки бинарника, который этой либой пользуется. Хорошо работает в случае C интерфейса (как зачастую и делают, даже если «внутри» там сплошной C++), отвратительно — в случае с C++.

Да, это возможно… но надёжнее — интерфейс на C.
Получите, распишитесь.
вот только какое отношение проблемы линковки одного бинаря могут иметь к совместимости ABI между разными бинарями?
А для этих целей есть разнообразные fabi-version (обратите внимание на 12ю и 13е версии). Ну или всякие чудеса типа вот такого.

Увы, единственный способ не поиметь проблем — это использовать C и «узкие» интерфейсы. Всё остальное — от лукавого.
А для этих целей есть разнообразные fabi-version (обратите внимание на 12ю и 13е версии)
«corrects», «corrects» и еще раз «corrects»…
По сути, единственная причина почему сишный ABI не меняется в том, что сам язык практически не развивается. А вот в плюсах иногда ABI приходится расширять по мере изменения языка.
Ну или всякие чудеса типа вот такого.
с каких пор __int128 является стандартным плюсовым типом, обязанным иметь одинаковую реализацию в разных компиляторах?
с каких пор __int128 является стандартным плюсовым типом, обязанным иметь одинаковую реализацию в разных компиляторах?
С тех пор, как он был добавлен в psABI?

А вот в плюсах иногда ABI приходится расширять по мере изменения языка.
А зачем? Мои можно сразу выделить «переносимое подмножество» — а остальное объявить внутренним делом реализации языка.

Вот вы тут COM вспоминали… Много там появилось за последние 10 лет? А C++ таки прилично поменялся…
С тех пор, как он был добавлен в psABI?
1. psABI != стандарт языка с++
2. __int128 не является обязательным к реализации согласно psABI
3. psABI почти никак не описывает эту реализацию
А зачем? Мои можно сразу выделить «переносимое подмножество» — а остальное объявить внутренним делом реализации языка.
оно и так выделено. extern «C» называется. Но еще раз: решать проблемы бинарной совместимости через си — самый болезненный из возможных вариантов.
Вот вы тут COM вспоминали… Много там появилось за последние 10 лет? А C++ таки прилично поменялся…
Ну так благодаря бинарной совместимости COM и не меняется
НЛО прилетело и опубликовало эту надпись здесь
Спасибо за напоминание.

Ситуация с noexcept в чём-то лучше: можно создать библиотеку, которая работает и с C++14 клиентами и с C++17.

А вот с этими переменными — засада полная: сделать так, чтобы модули C++14 и C++17 не конфликтовали нельзя вообще. Никак. Во-всяком случае мне решения найти не удалось…
Достаточно ведь вспомнить, что в C++17 noexcept — часть типа функции (и участвует в мэнглинге), а в C++14 — нет.
Я тоже так думал, но простейшая проверка опровергает это утверждение.
Да уж. Век живи, век учись: Functions differing only in their exception specification cannot be overloaded (just like the return type, exception specification is part of function type, but not part of the function signature).

И такого в C++ много.
cannot be overloaded
очевидно логично
exception specification is part of function type, but not part of the function signature
а это чтобы предотвратить такой юзкейс.
И такого в C++ много.
согласен, в языках без перегрузок не бывает правил, запрещающих перегрузки
НЛО прилетело и опубликовало эту надпись здесь

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


Удобно ведь? Чем раст и занимается, по сути.

Ну вот смысл в том, чтобы без этой подсказки код не компилировался

Нет уж, спасибо :) Может быть, мне действительно нужно получить алиасинг в этом месте для того, чтобы получить byte representation некоего объекта. Не надо за меня думать.

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

При обычном программировании ситуация "массив байт — это реально массив байт" встречается куда чаще, чем ситуация "массив байт — это byte representation некоего объекта".

Фиг его знает. Я вот сейчас пробежался по тем проектам что есть на машине за которой я сейчас сижу, char* в аргументах функций есть только в main(). Но это плюсы. В C по идее должно быть более распространено.

Любая строка (std::string) — это скрытый массив байт. Который заведомо не является byte representation некоего объекта, просто потому что строка владеет этим массивом.


Мне вот интересно, сколько костылей пришлось вставить авторам стандартной библиотеки, чтобы компилятор знал, что содержимое строки не алиасится с другими объектами? И знает ли вообще компилятор об этом?

Я думаю, немного:

godbolt.org/z/XiA6YZ

:) Приватные поля классов, пусть даже и char* — это вам не аргумент функции, который может придти ХЗ откуда, в том числе из другого модуля.

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


Если это исправить, то поведение string::fill окажется таким же, как и int foo(int& i, char *p).

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

А что насчёт вот такого примера? Он что, тоже искусственный? Вроде бы совершенно стандартная ситуация, когда надо посимвольно обойти переданную по ссылке строку...


Обратите внимание, что компилятор даже сам указатель "c" вынужден считывать на каждом шаге цикла, потому что не уверен что тот не алиасится с владеющей им же структурой!


UPD Нашёл пост на Хабре, откуда я узнал об этом эффекте: https://habr.com/ru/company/pvs-studio/blog/475636/

Вот, кстати, пример с аргументом-ссылкой в string::fill():

godbolt.org/z/7oPNmw

Тоже вполне прилично разворачивается. Но да, тут &i тоже не может придти хз откуда. Но такой сценарий все-таки куда более частый, чем ваш.
Но такой сценарий все-таки куда более частый, чем ваш.

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

Там смысл был в том, что оба аргумента могут придти ХЗ откуда.

Карма не позволяет писать ответы слишком часто, поэтому отвечу здесь же и на второй ваш комментарий:

Да, но если эту функцию РЕАЛЬНО ВЫЗВАТЬ, скажем, в main():

godbolt.org/z/BtFJDr

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

Всю программу, к сожалению, в main не заинлайнить; всё равно где-то компилятору придётся проводить границы между функциями...

Там смысл был в том, что оба аргумента могут придти ХЗ откуда.

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


Карма не позволяет писать ответы слишком часто

Подправил этот момент.

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

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

Подправил этот момент.

Благодарю! :)
Ну вот смысл в том, чтобы без этой подсказки код не компилировался, а если вы эту подсказку импользовали чтобы компилятор проверил что вы нигде её не нарушили.
проблемы начинаются когда я хочу чтобы без этой подсказки код компилировался и работал. Так, как я его написал.

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

да, код пет проджекта на 100 строчек кажется очень красивым. Не потому, что его не писали «умные программисты», а потому, что его не писал никто кроме вас.

А вот когда вы лет через 10 заглянете в rust проект, переживший пару сотен «умных программистов», десятки тысяч «срочная фича, дедлайн вчера» и ни одного рефакторинга («всё ж работает!»), но с кучей unsafe костылей (которые неизбежно будут появляться везде, где борроучеккер будет препятствовать изменениям кода), вот тогда вы будете не одну ночь дебажиться, подавляя желание переписать всё с нуля (ибо времени не выделено), и напишете в рабочий чатик «смотрите какую хрень я нашел в нашем проекте». И вам там ответят «да, это надо переписать, там где-то был тикет на это, лет 5 назад, но Вася Пупкин уволился и делать его некому».

Покажите проект с кучей unsafe-костылей. Или вы скажете что они все маленькие и плохие? Даже в rustc ансейфа очень мало, назвать его маленьким язык не повернется. При том что это блин компилятор, с кучей низкоуровневой магии.


Если вернуться чуть ближе к миру смертных и посмотреть на типичную библиотеку вроде actix то там 54 ансейфов на 54750 строк кода. Учитывая что почти все ансейфы — однострочные, получаем сколько? 0.1% ансейф кода? Ну да, это явно можно назвать кучей ансейф костылей.


Возьмем serde_json, 15к строк кода, пять ансейфов
Возьмем parity-ethereum, 200к строк кода, 68 ансейфов.


Продолжать?

а что, популярные открытые плюсовые библиотеки плохо написаны? Типа буста? Или компиляторы?

Я говорю про типичный прод типичного бизнеса, который практически не заинтересован в качестве кода. Тот самый плюсовый код, который вы критикуете, обитает преимущественно в них. И таких проектов на расте сейчас раз-два и обчелся, он попросту слишком молод.

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

Парити — это буст или компилятор?


куча низкоуровневой магии там в миддленде и бекенде

Простите, а MIRI тогда это что?
Задача раста — компилировать в LLVM. С чем он и справляется, будучи написан полностью на расте.


И таких проектов на расте сейчас раз-два и обчелся, он попросту слишком молод.

На них хватает проектов, просто вы о них возможно не слышали.


Решил наш собственный микросервис растовый проверить, 10к строк, два ансейфа.


В общем, порядок виден. десятые доли процента в худшем случае, если мы говорим про прикладные крейты.

Взял вот наш микросервис растовый, 10к строк, два ансейфа.

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

Что-то мне этот разговор напоминает


image


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


Кстати, насчёт молодого раста — ему сейчас столько же лет, сколько было сишарпу в 2006, когда им уже активно пользовались.

Не, ну честно, одно дело — разработка кучкой энтузиастов, которые болеют за идею, другое — людьми из бодишопов, которые фигак-фигак и в продакшн. Думаете, там кто-то будет стесняться влепить десяток unsafe, если «сроки горят»?

Ну, во-первых даже если влепят, когда всё развалится по ним можно будет грепнуть.


А во-вторых написать сейф вариант часто короче и быстрее написать чем ансейф, посмотрите тот же пример выше с get_unchecked против [], поэтому ленивый разработчик скорее словит панику чем уб.


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

Что-то мне этот разговор напоминает
одумайтесь. Вы сравниваете заведомо плохой код на плюсах с заведомо хорошим кодом на расте и приводите это в аргумент того, какие плюсы плохие. До этого вы сравнивали раст с плюсами приводя в пример вообще сишный код.
Остальные могут пользоваться бенефитами, которые гарантированно есть на проектах до миллион строк.
да вы бы хоть такой пример привели. Просто любой большой проект, который не энтузиасты в свободное время делают, и не разработчики языка, а обычные программисты из тех самых 99.9%, которые перекладывают json'ы, закрывают тикеты и у которых нет времени рефакторить код на каждую хотелку.
Зачем далеко ходить? Можно взять сабж Бенчмаркинг гейма и посчитать % ансейфов.

Хотя впрочем я не считаю, что без ансейфа было бы существенно медленнее. Разве что громкого заголовка могло не получиться )

Да и вообще, Расту надо доказывать не производительность кода (тут сомнений мало), а производительность написания.

Зависит от целей. Написать быстрый код на расте проще, чем на жс или на java. То есть если буквально есть цель написать сервис, который держит миллион рпц и не выжирает 300% памяти сервера, то на расте получится написать проще и быстрее.


А если вам не нужен быстрый код то и раст не нужен.

Для того, чтобы подобного ужаса не случилось, в Rust достаточно одного правила: в основном проекте не должно быть никакого unsafe! Если требуется что-то хитрое — пиши абстрактную библиотеку.


В С++ аналогичный набор правил намного сложнее.

ну вот простейший (хотя и довольно-таки патологический) пример… внушает, да
нет. в случае с make_unique дополнительно выполняется явная инициализация (0 для инт, вызвался бы конструктор для пользовательского типа)
Я предпочитаю иметь свою т.зр. в своих программах:
1. Использую new/delete — ручное управление
2. Использую unique_ptr — RAII
3. Выбираю shared/weak_ptr — ARC
то, что язык поддерживает RAII а библиотеки с его помощью автоматизируют ручное освобождение памяти (и иных ресурсов, btw), никак не отменяет того факта, что управление памятью в с++ ручное.
4. Подключаю boehm — оппа — программа с GC
зачем?
Похоже, у нас разное понимание автоматического управления памятью. Приведите пжл, ваше определение.
зачем?
пример просто для полноты картины
RAII поддерживается и в Rust (при этом более гибко, чем в C++, позволяя, если необходимо, освободить ресурсы досрочно). То есть в нем ручное управление памятью?
РАИИ это да, но потом нужно вручную носиться с этой писаной торссылкой, передавая из рук в руки.
Заимствование и владение в Rust большенство проблем решает.
Ахах, так это и есть эта проблема… Носиться = заимствовать [под роспись и по очереди]

Кроме непонятных эмоциональных аналогий какие-то аргументы будут?

Никаких эмоций — ОВ это ручное управление циклом жизни переменной под дулом компилятора. Весьма неудобно
под дулом компилятора

Снова аргументация от эмоций.

GC удобнее, но не везде применим.
Под дулом компилятора такими вещами заниматься лучще, чем без него. Вооюще пора понять, что в современных развитых языках компилятор друг, а не враг программиста.
Это существенно более гибкий механизм, чем чистая RAII. При этом столь же безопасный.
Язык с ручным распределением регистров быстрее, чем язык с автоматическим при прочих равных. Ваш К.О.

Посмотрел первый тест reverse-complement. Прямо с самого начала идут различия.
В Расте:


stdin.lock().read_to_end(&mut data).unwrap();

В С++:


// read line-by-line, according to game rule. Should be replaced by fread()
while (fgets_unlocked(cur_pos, remainbytes, stdin) != 0)

Разве это можно напрямую сравнивать на большом количестве строк? Дальше там еще больше различий идет. Если omp c thread::spawn еще можно сравнить, то в треде там совсем по разному обработка идет.

Растик по ходу сжульничал. В описании задачи особо упомянуто, что «Each program should: read line-by-line a redirected FASTA format file from stdin (grow the data, buffered-read by buffered-read; don't get the size and make a single allocation.) [...]». Интересно, много ли там такого.
Обе реализации используют одинаковое количество памяти – 1MB.

Вы же не думаете, что когда вы читаете в С++ «до конца строки», стандартная библиотека не буферизирует чтение?

Более того, С++ версия вычисляет общий размер ввода, что формально нарушает правила.

По поводу этих условий есть определенная дискуссия. Предлагается по меньшей мере переформулировать их для ясности.
Я ничего не думаю, я просто вижу, что реализация, в которой все читается одним куском, «немножко» нарушает правила. В реализации на C++ буфер тоже выделяется один раз, это так, но там хотя бы чтение в него происходит line by line в соответствии с правилами.

Честно говоря, пока все, что я вижу — это набор реализаций, которые не придерживаются оговоренных правил, ну или придерживаются их отчасти: какие-то больше, какие-то меньше. Я не в курсе, есть ли там какая-то модерация, но по-хорошему эти реализации не должны были ее пройти. Полностью согласен с ilynxy что эти бенчмарки сравнивают непонятно что непонятно с чем.
Конечно модерация есть, и решения которые читят по-черному не принимаются.

Из внутренней дискуссии, конкретному по тому вопросу, который вы подняли:
The description has said «read line-by-line» since Dec 2004, in-spite of the fact that even back then we understood that what seemed to be «read line-by-line» in-say CPython defaults to a buffered read.

I think «read line-by-line» actually meant don't write a program that will fail when the workload increases 40x.

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

Создатель этой игры не смог сформулировать что значит line by line строго поэтому забанил половину решений как ему захотелось. И это только один момент почему этот бенчмарк ниочем.

Я согласен с вашим замечанием. Но лучше бенчмарка, для которого сообщество потрудилось бы написать достаточное количество эффективных реализации на разных языках, у нас нет.
Написал я под этот бенч решение на nodejs, которое превосходило в два раза текущее. Его выпилил за «нарушение постановки задачи», хотя на любых тестовых данных оно выдавало правильные результаты.
Данный бенч (или его владелец) очень непоследователен в правилах: половина решений в некоторых тестах использует ассемблерные вставки. Что в итоге мы этим показываем: как крут ассемблер или как крут тестируемый язык?
Сравнил гошный мандельброт с растовским, показалось, что там мало того, что алгоритмы существенно отличаются, так ещё и входные параметры разные.
Этак если посмотреть на реализацую бенчмарков-победителей на разных языках, то можно увидеть, что там соревнуются в основном «кто лучше распараллелил да применил нужные интринсики SSE/AVX/...» (писать на фортране можно на любом языке). В pidigits все используют gmp для арифметики, в reverse-complement rust использует unsafe memchr из libc и т.д. и т.п. Всё это далеко от идиоматического C++ или Rust. Поэтому конкретно эти бенчмарки сравнивают непонятно что с непонятно чем.

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

В исходниках, кстати, есть идиоматические реализации алгоритмов без хитрых трюков, но так не победить =)

Ну интринзики можно было бы и в C++ и в Раст добавить, было бы понятно как модифицировать другой пример чтобы сравнять, сравнивались бы уже сами компиляторы. Хотя clang это и C++ и Раст и так =)
Про преимущества оптимизации у мемори-сейф, ну так в С++ тоже есть всякие restrict и расширения, которые теоретически эту разницу нивелируют.
Тут же Раст прямо нарушает условия, так что С++ даже нельзя в ответ модифицировать.

Формально именно в стандартном C++ restrict нет (по крайней мере пока), это из C99. Но фактически AFAIK тот же g++ в него умеет:

godbolt.org/z/TGuECo

В случае с restrict он вычисляет результат в compile time.

Ну это да, потому и пишу расширений. У gcc есть еще __attribute__ с кучей всего полезного не стандартного. У msvc есть __assume() итд.

Ну интринзики можно было бы и в C++ и в Раст добавить, было бы понятно как модифицировать другой пример чтобы сравнять, сравнивались бы уже сами компиляторы. Хотя clang это и C++ и Раст и так =)
См. исходники — интринсики используются и в Расте, кстати почему то их вызов unsafe.
Еще раз — gcc это не clang — а в этом тесте C/C++ использует gcc

Да, я видел. Я пишу немного на расте и я к тому что если ипользоваь clang — у меня всегда выходило примерно "сравнять" оптимизации. Разница была лиш в том что раст вставляет проверки, а компилер по сути один и тот-же.
Это правда могло вызывать дополнительный спиллинг регистров, что на асме может сильно сказаться. Так же на расте иногда сложно сравнять даже с интринзиками, в специфических случаях, например из-за alloca https://github.com/rust-lang/rfcs/issues/618 https://github.com/rust-lang/rust/issues/48055, асм вставка тут не поможет т.к. надо со стеком корректно играться (ну или памятью пожертвовать, если там не гигабайты в худшем случае). Когда Unsized Rvalues допилят — одна из пробелм уйдет. А вот в обратную сторону — на С/С++ можно сделать все что позволяет AST clang`а.

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

Как правило, 99% производительности зависит от 0.1% кода. Считайте что бенчмарки – ответ на вопрос, на чем и как могут быть написаны эти 0.1%.

В исходниках, кстати, есть идиоматические реализации алгоритмов без хитрых трюков
Более того, на сайте прямо говориться – сравнивайте не только результаты измерений, но и исходники.
Ну то есть пишем либу на Си и вызываем её функции из <язык по вкусу>. Тем самым демонстрируя прямоту рук.
Обычно у языка есть декларируемая сфера применения и практическая (историческая). Rust, если я правильно понимаю, предназначен для тех же целей, что и C++. Не вижу никаких причин быть медленней любого другого языка, кроме молодости. Опять же, компилируемый язык, позволящий использовать особенности целевой архитектуры (лучше неявно) будет претендовать на звание быстрейшего. Однако скорость это не главное, хотя и важное. Главное чтобы эта скорость была доступна искаропки средней руки программисту. Этак можно скомпилировать программу на C компилятором C++ (ониж совместимы) и заявить, что C++ рвёт Rust.
А в соревновании кто напишет код ближе к Си победителей не будет.
Ну то есть пишем либу на Си и вызываем её функции из <язык по вкусу>. Тем самым демонстрируя прямоту рук.
Внезапно, Rust зависит от libc, а половина стандартной библиотеки Python – обертки на сишными либами. Так что в реальном мире все именно так и работает, только вот скорость C теряется на переходах FFI и из-за всяких GIL.

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

Не вижу никаких причин быть медленней любого другого языка, кроме молодости.
И тем не менее, Ada, Fortran и Pascal почему-то заметно медленнее C/C++/Rust.

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

Почему же непонятно? Эти бенчмарки сравнивают сильно заоптимизированный код на различных языках программирования
и какой смысл сравнивать, скажем, написанный на интринсиках код на си и написанный на интринсиках код на с++, если их отличия будут минимальны, но при этом ни один ни второй код не будет отражать 99.9% кейсов реального программирования на языке? А если я просто напишу asm-вставку, это будет считаться?
Сейчас — считается
Разве это можно напрямую сравнивать на большом количестве строк? Дальше там еще больше различий идет.
Сравниваются наиболее эффективные подходы, а не идентичные реализации, переписанные с одного языка на другой. Если вы посмотрите на различные реализации в одних и тех же языках, то увидите, что бывают очень разные подходы, особенно в вопросах распараллеливания. Для сравнения языков выбирается наиболее эффективная реализация для каждого языка.

Ну это понятно, я уже выше писал что принципиально разные omp и thread::spawn — это можно сравнивать, будем сравнивать эффективность crt. Но там где сравнивается участки с принципиально большим количеством действий (fgets + strlen) и когда их можно привести к одному виду (там даже есть комментарий про fread) — так сравнивать нельзя. Как я понимаю в С++ используется не эффективный подход из-за правил, а в Раст коде правило нарушено.

Сравниваются наиболее эффективные подходы

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


Для сравнения языков выбирается наиболее эффективная реализация для каждого языка.

Как мы только что выяснили, не наиболее эффективная, а произвольная.


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

И что такие сравнения должны продемонстрировать? Ну вот есть цифры бенчмарков произвольных реализаций произвольных алгоритмов, решающих одну и ту же задачу на двух разных языках. О чем эти цифры говорят? О том, что язык А быстрее языка Б? Нет, алгоритмы же разные использовались. О том, что алгоритм А быстрее алгоритма Б? Нет, реализации же разные. Может хотя-бы о том, что реализация А быстрее реализации Б? Нет, компиляторы же разные, с разным набором оптимизаций.


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

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

Как я уже писал выше – не столь важно, как именно вы прочитаете 1MB данных из stdin на фоне 2 секунд вычислений. Тем более, что мы даже не можем дать гарантий, что компилятор не с оптимизирует «построчное» чтение. Тем более, что stdin все равно буферизируется ОС.

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

Ну и что показывает факт что си быстрее си++ если си код скорее всего можно скопировать g++? Признайтесь, заголовок желтый, написали бы "раст показывает производительность на урвоне си и си++". В результате на чем писать simd инструкции не важно, лишь бы язык поддерживал в любом случае приходим к ручной возне с указателями и unsafe. А если сравнивать с наворотами которые помогают, то в расте пока не хватает например ручного unroll поскольку нету const generics.

Признайтесь, заголовок желтый
Не признаю, потому что я вполне сознательно прямо в заголовке указал «согласно результатам Benchmarks Game». Так что читатель вправе доверять утверждению в заголовке ровно настолько, насколько доверяет бенчмарку, и это нормально.
В результате на чем писать simd инструкции не важно, лишь бы язык поддерживал.
Если бы это было так, Rust и C показывали очень близкие результаты, а для некоторых задач это совсем не так. SIMD можно использовать и в Python, но вряд ли вам удасться добиться результатов близких к С/Rust и вряд ли вам захочется переписывать каждый цикл так, чтобы SIMD использовался.
в любом случае приходим к ручной возне с указателями и unsafe
Вы не правы. Только для 3 задач из 10 самые быстрые реализации на Rust используют unsafe.

Ну да как раз скрывать unsafe абстракции за safe интерфейсом и есть киллер фича раста, так что в других 7 там уровнем ниже :)


А по поводу заголовока вы укавите, т.к. Benchmark Game нигде не утверждает что язык а превосходит язык б по производительности. О чем у вас написано в тексте. Но это обычное дело для заголовков ;)

Хотя я согласен с вами что в приведенном месте есть проблема с формулировкой задачи, я считаю что вы слишком придираетесь там где это не вполне оправданно.
а почему вы считаете что проблема в формулировке задачи, а не в реализации её на расте? Которая по факту нарушила правила конкурса, даже если такого правила не должно было существовать.
Потому что правила нарушает не только одна реализация, а почти все, в том числе на C/C++. И потому что так утверждает мейнтейнер проекта — ему виднее как исправлять ситуацию.
Потому что правила нарушает не только одна реализация, а почти все, в том числе на C/C++
если верить коду, это неправда

То C++ решение нарушает другое правило. Оно читает размер файла и выделяет буфер нужного размера сразу. Этого тоже нельзя делать. Но если хочется, то можно. Ведь автор правил писал одно а подразумевал, оказывается, совсем другое.
Так что проблема все таки не в расте и не в решениях на нем, а в самом конкурсе, где нет четких требований, а те требования что есть — каждый понимает как хочет.

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

И снова нет. "Используя весь арсенал доступных оптимизаций" программы на C++ были бы копипастом программ на C и работали бы со скоростью программ на C. А в случае, если бы какой-то язык обогнал си (неважно, Rust это будет или, скажем, Python) — все решения на языках, поддерживающих asm вставки, превратились бы в одну большую asm вставку с дизассемблированным кодом этого более быстрого решения.


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

Не вводите в заблуждение. Сравниваются именно идентичные реализации, о чём указано в правилах: например нужно ОБЯЗАТЕЛЬНО использовать хэш-мэп (https://benchmarksgame-team.pages.debian.net/benchmarksgame/description/knucleotide.html#knucleotide), хотя он реально ухудшает производительность
Ну да, а еще обязательно использовать input. Потому что иначе самой быстрой реализацией будет вывод статического output.

Наличие ограничений на реализацию != идентичные реализации, очевидно.
Никтож не спорит, что может родиться низвергатель престолов, только вот теперь бы еще столько же кодовой базы наработать как в плюсах за проедшие десятилетия…

<--> вы находитесь здесь <-->

Да, охотно допускаю.
Вариант для голосования «нет, but who cares» еще бы
Осталось привести в порядок borrow checker и можно будет пользоваться, а то очень расстраивают «особенности» вроде «нельзя создать два мутируемых референса на элементы одного массива», даже если это разные элементы.

По вашему хардкорному примеру трудно понять, какого поведения вы хотите добиться. Можете объяснить словами?

Как известно, для массивов реализация IntoIterator бланкет-реализация обычного Iterator, из-за чего iter аналогичен into_iter и производит итерацию только по ссылкам.


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


Иными словами, есть такой код:


let x = vec![F(1),F(2),F(3)];
let squardVec: Vec<i32> = x.into_iter().map(|x| F(x*x)).collect();

где реализацию F можно увидеть по ссылке. Задача — сделать так, чтобы оно работало для массива let x = [F(1),F(2),F(3)];. Вторую строчку трогать нельзя.

Уже можно, но еще не так красиво, как хотелось бы:

#![feature(array_value_iter)]
use std::array::IntoIter;

struct F(u32);

fn main() {
    let x = [F(1), F(2), F(3)];
    let y: Vec<F> = IntoIter::new(x).map(|x| F(x.0 * x.0)).collect();
}

Ждем #65819.

Ну если вы посмотрите PR то в реализации там написано практически то же что и по моей ссылке.


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

А если мне нужно пройтись по массиву элементов и сделать некоторые операции каждого с каждым? Создавать 1e6 сплитов?
А если мне нужно пройтись по массиву элементов и сделать некоторые операции каждого с каждым?

Напишете for:


fn main() {
    let mut arr = [1,2,3];
    println!("{:?}", arr);

    for i in arr.iter_mut() {
        *i += 10;
    }

    println!("{:?}", arr);
}
а если мне нужен фурье, для которого в первом проходе нужен i-й элемент с reverse-bit-indexed-i-м?

тогда делаете for i in 0..arr.len() и дальше как в старом добром си по индексам пишете что хотите.

Я считаю, что какой-нибудь вот такой код должен компилироваться и работать. Но не компилируется. Сколько кода я должен дописать, чтобы получить рабочий и безопасный эквивалент без ущерба в производительности?

Забавно что в расте он развернул цикл на 2, но только в unsafe случае. Интересно, с чем это связано?

Скорее всего увидел, что итерации независимы. Раньше была зависимость в виде паники ведь. А теперь итерации можно делать полностью свободно.

Я нашел, вот так код 1 в 1 получается: https://godbolt.org/z/vitnPr


Почему-то .size() у вектора сбивает анализ, как и проверка в сейф версии раста, хотя по идее это не должно влиять. Кажется небольшой недочет в оптимизаторе.

а во-вторых почему сравниваем неэквивалентный код?
Код реализует одинаковый алгоритм и не существует таких входных данных на которых его выход будет отличаться. Вопрос: почему он неэквивалентен?
Ну во-первых так уже лучше
а вот это уже неэквивалентный код
Код реализует одинаковый алгоритм и не существует таких входных данных на которых его выход будет отличаться. Вопрос: почему он неэквивалентен?

Ну потому что в плюсах у вас используется get_unchecked, а в расте — get_checked.


Неэквивалентность в том, что оператор [] делает разные вещи в разных языках, и это надо учитвать.


Точно так же, как Foo<T> в С++ и в расте означает разные вещи.


Эквивалент для сишного [] это растовый get_unchecked/get_unchecked_mut, поэтому если и писать "эквивалент", то надо их использовать, а не то, что "выглядит похоже".

По сути, неэквивалентность в вашем понимании состоит только в наличии заведомо ненужной проверки — i и (v.len() — 1 — i) всегда будут попадать в диапазон 0..v.len(), по которому идет итерация.

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

Эквивалент для сишного [] это растовый get_unchecked/get_unchecked_mut, поэтому если и писать «эквивалент», то надо их использовать, а не то, что «выглядит похоже».
у вас постоянно скачет мнение — то вы доказываете что эквивалентный код на расте безопасен, то утверждаете что безопасный код на расте неэквивалентен. Определитесь уже.
По сути, неэквивалентность в вашем понимании состоит только в наличии заведомо ненужной проверки — i и (v.len() — 1 — i) всегда будут попадать в диапазон 0..v.len(), по которому идет итерация.

Неэквиалентность в моем понимании состоит в том, что оператор [] делает разные вещи в разных языках. Если проверка "ненужная", то пишите код который проверку не делает. Если нужная — делайте проверку в обоих случаях.


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


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

Растовый оптимизатор точно так же оптимизирует форыч и фор от 0 до len. Если вы заметили, я их оставил как есть, и компилятор вывел что там проверять ничего не нужно. Для len() - i - 1 он такого вывести не смог, ну что ж, когда-нибудь ему этому научат. Но это мало чего меняет: если мы сравниваем операции без проверки, пишем в обоих случаях без проверки. Сравниваем с проверкой — пишем так в обоих случаях. Писать с проверкой в расте а потом говорить что он генерирует плохой код — ну извините.


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

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


Разница в поведении в таком варианта действительно незаметна (в оригинальном сравнении). Но если мы воспользуемся какими-нибудь мутационными тестами, которые — 1 на + 1 заменять например то разница не заставит себя ждать.

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

Но если мы воспользуемся какими-нибудь мутационными тестами, которые — 1 на + 1 заменять например то разница не заставит себя ждать.
тогда мы будем сравнивать не код A с кодом B, а код X с кодом Y. По сути, этот ваш аргумент — чистейшая контрпродуктивная демагогия.

Я не понимаю о чем спор.


  • Можно ли написать на расте один в один как на си? Ну да, обмазываемся ансейфом и имеем то же самое с той же производительностью. Получится чуть длиннее из-за ансейфов и того, что функции намеренно длинными названы.
  • Можно ли написать на расте так же коротко как на си? Ну да, можно, но тогда компилятор будет расставлять проверки что никто не стреляет по ногам.
  • Умеет ли компилятор раста элидить все проверки которые мы как люди видим не нужны? Ну, видимо не умеет, надо ждать пока научится. А пока не умеет — либо писать руками, либо забить на наличие этой проверки.

В чем утверждение-то? Что код без проверок получается быстрее? Ну получается, что в расте, что в си. Так что, что доказывали — непонятно.


Сколько кода я должен дописать, чтобы получить рабочий и безопасный эквивалент без ущерба в производительности?

Вот столько:


pub fn foo(v: &mut [f32]) {
 for i in 0..v.len() {
  v[i] = v[i] * unsafe {v.get_unchecked(v.len() - 1 - i)};
 }
}
Можно ли написать на расте один в один как на си? Ну да, обмазываемся ансейфом и имеем то же самое с той же производительностью...
вы готовы признать что безопасность и производительность раста часто противоречат друг другу? То есть то, что вы только что написали, но в однозначной формулировке. Мне лично это принципиально — это утверждение стоило мне всей кармы
Вот столько:
а аналог вот этого кода?
вы готовы признать что безопасность и производительность раста часто противоречат друг другу? То есть то, что вы только что написали, но в однозначной формулировке.

Иногда — противоречит, конечно. Иногда — наоборот, помогает. Какая пропорция помощи и противоречия не готов сказать.


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


Мне лично это принципиально — это утверждение стоило мне всей кармы

Лично моя позиция по этому вопросу такая


Я вообще в карму никогда не минусую, если и несогласен, ограничиваюсь комментарием.


а аналог вот этого кода?

В сейф расте такой код написать невозможно.

Ну во-первых так уже лучше, а во-вторых почему сравниваем неэквивалентный код?
Прекрасный пример, который даёт однозначный ответ на вопрос голосовалки =)

Ждём Verona от Microsoft.

Они же говорили, что проект совершенно о другом и в зачаточном состоянии.


Конечно, было бы неплохо если бы они выпустили крутой раст с исправленными ошибками, он не уверен что это будет оно. Плюс фрагментация экосистемы.


Будем посмотреть, но не надо завышать ожидания. Лучше наоборот, их занизить, а потом порадоваться что ошиблись.

скорее всего верона выльется в очередной EEE — они сделают компилятор чего-то похожего на раст (с проприетарными расширениями и закрытым исходным кодом) и встроят в MSVC. Ничем хорошим для сообщества это не обернется
Как вам там, в 2001? У нас в 2019 Microsoft все свои новые инструменты для разработки открывает. У вас там же недавно .NET/C# вышел, и оно только под Windows? Тут .NET открыт под самой свободной лицензией и поддерживает Linux, MacOS, Android и iOS. Да, нам тут тоже сразу не верилось.
Тут .NET открыт под самой свободной лицензией и поддерживает Linux, MacOS, Android и iOS. Да, нам тут тоже сразу не верилось.
это сколько лет спустя? И на чем основана ваша уверенность что завтра не появится какой-нибудь Rust/CLI?
Как только компания изменила свою стратегию, так сразу. В 2014 открыли исходники нового компилера, через год-два открыли среду исполнения, потом и остальне компоненты, которые не легаси и они до сих пор поддерживают — тоже открыли.
И на чем основана ваша уверенность что завтра не появится какой-нибудь Rust/CLI?
Почему я должен переживать, что появится Rust/CLI?

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


Я думаю одно только исправление бага LLVM связанным с NOALIAS поднимет раст на первую строчку. Что до реальных приложений — то там есть еще и ограничение по времени. Написать быстрый корректный код на расте имхо займет куда меньше времени чем на си.

Про noalias не знаю, зависит от задачи (в C, как выше справедливо написали, тоже есть restrict), а вот что касается реальных приложений, то тут немаловажную роль ещё играет возможность пользоваться готовыми библиотеками, написать к которым раст-обёртку может оказаться нетривиальной задачей из-за их «неидиоматичности» с точки зрения раста. Не говоря уж о том, что из-за чрезмерных требований раста к идиоматичности подхода даже с растокодом как таковым вполне может получиться бида. Так вижу (С) :)
Про noalias не знаю, зависит от задачи (в C, как выше справедливо написали, тоже есть restrict)

Не встречал си разработчиков которые в программе расставляют restrict у каждого указателя, которых могут быть миллионы. Я понимаю правило про 80/20, но всё же.


а вот что касается реальных приложений, то тут немаловажную роль ещё играет возможность пользоваться готовыми библиотеками, написать к которым раст-обёртку может оказаться нетривиальной задачей из-за их «неидиоматичности» с точки зрения раста.

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


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

На самом деле это больше желание коммьюнити иметь чистый код, но никаких обязательств тут нет. Судя по тому что я видел, довольно часто люди применяют подход "генерирует раст код через c2rust (или похожую тулзу), а потом постепенно рефакторим и переписываем". Подход, успешно применяющийся на TS/JS проектах.


Так вижу (С) :)

Благодарю. С вами приятно вести диалоги.

НЛО прилетело и опубликовало эту надпись здесь
В Си нет никаких накладных расходов на абстракции, т.к. нет абстракций. С этим невозможно конкурировать.

Впрочем, на практике 30% потерь это ни о чем, и даже до 100% еще терпимо, так что спор скорее теологический.
В Си нет никаких накладных расходов на абстракции, т.к. нет абстракций. С этим невозможно конкурировать.

Во-первых в си есть абстракции.
Во-вторых у си есть семантика которую компилятор вынужден соблюдать даже когда выгоднее было поступить иначе. Gcc выполняет поистине титаническую работу чтобы заставить код работать быстро на железе, семантика которого сильно отличается от PDP-11.


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

Да поэтому вопрос в том как можно сгенерировать оптимальный асм код за оптимальное время разработки и поддержки. Так то в бенчмарке можно вообще влепить асм вставку а потом говорить что язык самый быстрый :)

Gcc выполняет поистине титаническую работу чтобы заставить код работать быстро на железе, семантика которого сильно отличается от PDP-11
Хотелось бы увидеть примеры. Насколько я знаю asm pdp-11, от x86 принципиально не отличается.
Абстракции часто помогают ускорить программу, а не замедлить. Плюсовики со стажем думаю могут рассказать, как магия на шаблонах оказывается в рантайме быстрее голого си.
Абстракция абстракции рознь. Шаблоны — zero-cost абстракция, чистый inline, а вот лямбды — нет.

Не уходим с темы С — какие абстракции в С есть?

А с фига ли лямбды — не zero-cost? Или вы путаете лямбды и std::function?

Лямбда это создание и вызов ф-ции класса. Проверил — в С++ это оптимизируется в пыль, значит тут zero-cost.

Но вот в C# это уже не так. Значит зависит от языка и надо смотреть каждый случай.
Хотелось бы увидеть примеры. Насколько я знаю asm pdp-11, от x86 принципиально не отличается.

Ну три самых очевидных:


  1. с точки зрения сишного кода регистров 8, на практике — намного больше
  2. с точки зрения сишного кода память линейная, на практике — иерархическая
  3. с точки зрения сишного кода исполнение инструкций последовательное, на практике это не так.

Раст тут ничем не поможет, в этом плане он копирует семантику сишки, но в некоторых случаях — нет:


  1. сложение знаковых числел не является UB
  2. уникальные ссылки не алиасятся
  3. итераторы позволяют безопасно и бесплатно делать преобразования коллекций
  4. ...

Не уходим с темы С — какие абстракции в С есть?

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


You may have heard another slogan when talking about C: “C is portable assembler.” If you think about this slogan for a minute, you’ll also find that if it’s true, C cannot be how the computer works: there are many kinds of different computers, with different architectures. If C is like a version of assembly language that works on multiple computers with multiple architectures, then it cannot function exactly how each of those computers work simultaneously. It must hide details, or else it wouldn’t be portable!

That said, I think this fact is sort of irrelevant, because I don’t think people mean the phrase “C is how the computer works” literally. Before we can talk about that, let’s talk about the C abstract machine, and why many people don’t seem to understand this aspect of the C language.

В итоге он не может не быть абстракцией, иначе он не мог бы быть портабельным между архитектурами.


Но вот в C# это уже не так. Значит зависит от языка и надо смотреть каждый случай.

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

это такой философски-софистический прием?
вместо примера _конкретной_ языковой абстракции, сказать «да весь мир — абстракция» =)

спс. поговорили

комментировать предыдущий бред про регистры и внезапно возникшую не-неймановскую архитектуру в компьютерах даже не буду
Хотелось бы увидеть примеры. Насколько я знаю asm pdp-11, от x86 принципиально не отличается.
А причём тут ASM? Отличие в архитектре процессора, а не в ASM.

Вернее на RISC — и в ARM, тоже, но на x86 — отличие скрыто… что не значит, что его нет.

Обращение в память на современных процессорах занимает в сотни раз (если зыбыть про кеши и прочий пайпланинг) или десятки раз (если про кеши и пайпланинг вспомнить) больше ресурсов, чем обращение к регистру.

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

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

Именно так, кстати, работали ранние компиляторы для 8086 (какой-нибудь славный Turbo C 2.01 или Microsoft C 5.0).

Однако на современных процессорах это даст отвратительную производительность — потому да, разработчики из кожи вон лезут, чтобы автоматически перенести данные из памяти в регистры (чего изначальный C не предполагал). А пометку register вообще вроде в C++17 отменили (вроде — потому что компиляторы всё равно её ещё поддерживают… и игнорируют).
И какой отсюда следует вывод?

Сейчас разработчики процессоров совместно с разработчиками компиляторов добиваются производительности собранных программ. Но как это должно влиять на язык и вообще должно ли — вот вопрос?
Соотвественно весь язык построен, снизу доверху, на идее, что все данные лежат в памяти
ИМХО язык привязан к логической архитектуре, для квантовой нужен новый. Но пока что у нас фон-неймановская и она внезапно с однородной памятью и последовательным выполнением команд.

С трактовкой register не согласен. Это скорее был хинт компилятору, т.к. оптимизаторов тогда еще не было.
ИМХО язык привязан к логической архитектуре, для квантовой нужен новый.

Хватит пожалуйста лепить слово "квантовый" на всё без разбору. "не как в си" и "квантовый" это не одно и то же.


Если "как в си" для вас кажется эквивалентным "единственно логичное" то вас пора немного оглянуться и посмотреть на мир вокруг.


Но пока что у нас фон-неймановская и она внезапно с однородной памятью и последовательным выполнением команд.

Что с последовательностью команд и фон-неймановскостью какого-нибудь SQL? Да, язык для запросов делался, но как пример пойдет.

пока что у нас фон-неймановская и она внезапно с однородной памятью и последовательным выполнением команд

Лол. Привезите мне сычуанский соус из прошлого в котором вы оказались пожалуйста.
Но пока что у нас фон-неймановская и она внезапно с однородной памятью и последовательным выполнением команд.
Выключите кеши и после этого будете рассказывать сказки про «фон-неймановскую» архитектуру.

Фон-Неймановская архитектера у нас была полвека назад. Какой-нибудь C64 — можно считать фон-неймановским с той или иной степенью натяжки.

И вот уже 386й с кешом — это уже не фоннеймановская архитектура, двуядерный проц — тем более.

И да, 90% ресурсов в IT уходят на то, чтобы поддерживать иллюзию фон-неймановской архитектуры с однородной память и последовательным выполнением команд. Хотя ни того, ни другого и в помине нет.

С трактовкой register не согласен. Это скорее был хинт компилятору, т.к. оптимизаторов тогда еще не было.
Это он в XXI веке хинтом стал. В оригинальной версии это не было хинтом. Это было требование.
Наличие кэша не влияет на тип архитектуры, см определение. Да, мы живем в несовершенной иллюзии, но каждое ядро x86/amd64- отдельный экземпляр той же архитектуры.

Аналогично, про register советую сначала почитать в оригинале стандарт С89
3.5.1 Storage-class specifiers....A declaration of an identifier for an object with storage-class specifier register suggests that access to the object be as fast as possible. The extent to which such suggestions are effective is implementation-defined.[49]
Какие то утверждения у вас… безосновательные.

Просто я Си учил как раз по оригиналу K&R
Наличие кэша не влияет на тип архитектуры, см определение.
А вы сами-то на него пробовали смотреть?

Вот на этот вот пассаж, к примеру:
Команды и данные хранятся в одной и той же памяти и внешне в памяти неразличимы. Распознать их можно только по способу использования; то есть одно и то же значение в ячейке памяти может использоваться и как данные, и как команда, и как адрес в зависимости лишь от способа обращения к нему. Это позволяет производить над командами те же операции, что и над числами, и, соответственно, открывает ряд возможностей. Так, циклически изменяя адресную часть команды, можно обеспечить обращение к последовательным элементам массива данных.


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

Аналогично, про register советую сначала почитать в оригинале стандарт С89
А C89 — в каком году вышел? В названии год вам о чём-нибудь говорит? А ничего, что Wikipedia пишет про C «появился в 1972»?

Изначально register было требованием, но когда Unix (или может он тогда был ещё Unics) начали портировать на разные архитектуры — это требование отменили, так как нельзя было заранее сказать сколько где регистровых переменных доступно (на MS-DOS их обычно было всего лишь две).

Просто я Си учил как раз по оригиналу K&R
Это прекрасно, но если вы про знаменитую книжку — то это уже 1978й год. То есть уже после того, как было решено «отвязать» Unix (и, соответсвенно C) от архитектуры PDP (Interdata 8/32 была закуплена AT&T как раз с этой целью в 1977м году).

Если вы всё ешё судите о прошлом по принципу «ну я же там был, всё же помню, зачем мне книжки» — то советую от него отказаться. Ибо, увы и ах, человеческая память — это аналоговая DRAM. Считываение воспоминания уничтожает его, а на это место записывается «воспоминание о воспоминании». Через 3-5-7 лет то, что вы помните о событии и то, что там происходило в реальности — начинают отличаться довольно-таки изрядно. А о вещах полувековой давности если вы чего и вспомните — то это будет больше похоже на легенду, чем на быль… даже если «вы лично там были и всё видели».
Пробовал. Читать надо абзац «Принципы фон Неймана»

Ссылка из K&R про регистровые. Что было до K&R не знаю, кажется BCPL

Воды много, а вот пруфов мало.
Пробовал. Читать надо абзац «Принципы фон Неймана»
Ну да, «тут читаем, тут не читаем, тут рыбу заворачивали». Натягивание совы на глобус продолжается.

Что было до K&R не знаю, кажется BCPL
До K&R были Комментарии Лайонса к 6-й версии UNIX, с исходным кодом. Включающая в себя, помимо всего прочего, и описание и исходники C-компилятора.

И вот в этом самом конкретном исходном коде есть конкретная такая функция decl1 в конкретном файле c03.c которая конкретно так включает вот такой вот конкретный участок:
	} else if (dsym->hclass==REG) {
		if ((type&TYPE)>CHAR && (type&XTYPE)==0
		 || (type&XTYPE)>PTR || regvar<3)
			error("Bad register %o", type);
		dsym->hoffset = --regvar;
	}
Который конкретно так ругается на конкретную такую попытку завесьти четвёртую регистровую переменную в одной функции (regvar инициализируется в файле c02.c в функции с названием cfunc и его начальное значание равно 5).

Воды много, а вот пруфов мало.
Будем дальше тянуть сову на глубус или признаем, наконец, что облажались?

Или опять будете петь, что то, на чём писали Unix его разработчики — это не C, а так, поделка, раз оно священной водой комитета по стандартизации не окроплено?
Принципы фон Неймана это как раз ключевые признаки архитектуры, остальное менее важно.

Сеанс некромантии это конечно интересно, хотя я уже и сказал что не в курсе тех времен в С. Завтра посмотрю, а пока просто замечу, что в PDP-11 было 6 общих регистров, а не 3 как вы тут утверждаете.
а пока просто замечу, что в PDP-11 было 6 общих регистров, а не 3 как вы тут утверждаете.
Где я это утверждаю, извините?

Именно потому что в PDP-11 было 6 общих регистров regvar и устанавливается изначально в 5. Потому что R6 и R7 трогать было нельзя. А вот чтобы регистровые переменные не заняли все регистры и осталось что-то для работы с обычными переменными — их количество и ограничивается тремя… жёстко ограничивается заметьте, никаких хинтов тут нету… это уже когда Unix начали портировать с PDP-11 оно хинтом стало…

Сеанс некромантии это конечно интересно
Мы сейчас спутились во времена когда я ещё не родился, на самом деле. Ну люблю я это дело просто. Когда разбираешься в разных вещах в IT, то заглянуть в историю (настоящую, а не то, что в ваших воспоминаниях осталось) часто бывает очень полезно.

Многие вещи, которые вызывают недоумение — становятся вдруг резко понятными, почти очевидными. Потому что в те далёкие времена, когда они были сделаны — они действительно имели смысл!

А сегодня, очень часто — они просто мешают…
Ну ОК, мое утверждение про register верно только для версий С, начиная с K&R 1978г (а до этого неверно). Так годится?
Так, я думаю, годится. Как только ушли от архитектуры PDP-11 — так концепция начала рассыпаться. И чем дальше уходили — тем больше требовалось «подпорок»…
Я тоже долго хейтил С++ и топил за Си. В итоге согласен с тем, что сейчас писать на Си практически нет смысла, особенно если нужен быстрый код — у С++ реально получается быстрее (и даже меньше по размеру). У него только один минус — это дьявольски раздутый и сложный язык. Но возможно оно того стоит.
Моё мнение полностью опрокинул вот этот доклад:
youtu.be/PDSvjwJ2M80
НЛО прилетело и опубликовало эту надпись здесь
Написать быстрый корректный код на расте имхо займет куда меньше времени чем на си.
Меня удивило, что конкретно в Benchmarks Game исходники на Расте не короче, чем на С++. Потому я сильно сомневаюсь в этом утверждении.

При чем тут размер исходника? Если взять мою статью про сравнение го с хаскеллем то однопоточный вариант в 100 строк на го занял 15 минут, а хаскельный на 20 строк — больше получаса.

на расте сложнее писать, чем на с++. да и в принципе он весьма, хм, минималистичен — потому я ожидал увидеть код покороче

именно так же, как с го и хаскелем — проще язык — быстрее писать
на расте сложнее писать, чем на с++

А вот с этим я категорически не согласен.

Вроде же можно в Найтли флагом включить альясинг и таки проверить насколько он круто перфоманс поднимает.

Было бы интересно сравнить максимально оптимизированный код для всех языков.
Он будет одинаков. И будет состоять из одной (но длинной) строчки asm.

Предельный случай тут как раз неинтересен — на практике так никто писать не будет…
И да, и нет.

На самом деле, просто нет. Особенно на Benchmarks Game

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

Вот например, угадайте язык программирования:
                    _mm_sub_pd(
                        _mm_mul_pd(distance, _mm_set1_pd(1.5)),
                        _mm_mul_pd(
                            _mm_mul_pd(_mm_mul_pd(_mm_set1_pd(0.5), dsquared), distance),
                            _mm_mul_pd(distance, distance),
                        ),
                    )

Правильный ответ: почти все бенчмарки «n-body» на этом сайте имеют этот кусок кода.

К несчастью в бенчмарках все языки на горячем пути как правило только вызывают C функции (см _mm_* и unsafe). Поэтому бенчмарки сравнивают как оптимизаторы разных версий GCC и Clang умеют оптимизировать C код, вместо того чтобы действительно сравнивать языки.

Ну и ничего удивительного, что bleeding edge версия clang-9 (2019), побеждает GCC-9 (2018) на числодробильных задачах.

Тут уже целые диссертации пишут по теме производительности (тыц). unsafe rust уже чуток быстрее Си

Насколько я понимаю у Rust есть в перспективе преимущество с алиайсингом. Сейчас Rust может выигрывать в задачах потому как к этому языку есть интерес скиловых разработчиков в плане свободного времени. Однако на мой взгляд в плане разработки Rust имеет ряд тенденций создание менее производительного кода чем скиловый C++ разработчик. Вот что мне не хватало:


  • отсутствие placement new
  • отсутствие указателей на методы
  • отсутствие возможности использовать константы в растовых шаблонах (generics)
  • interior mutability чтоб написать safe код — это дополнительная runtime проверка
  • борьба с borrow checker решается при помощи clone() или штук вроде Rc
  • встроенная проверка выхода за границы массива

Вообще borrow checker с одной стороны крутая штука, с другой стороны она довольно слабая в плане того что очень много вещей ей не поддаются на этапе компиляции и чтобы писать идиоматический rust приходится использовать штуки которые имеют runtime-cost либо делать их unsafe (а это вроде как считается стрёмным). Если использовать unsafe то вроде принципиально Rust и C++ должны быть теоретически одинаковыми. Не знаю как на это влияет тот факт что нарушение aliasing является UB в расте. С одной стороны знания об aliasing позволяет компилятору лучше оптимизировать, с другой стороны наличие мутабельных ссылок на один и тот же объект может давать преимущества в производительности?


Не знаю что за задачки у benchmark game, если это числодробительные алгоритмы, то это не очень показательно. Допустим у вас проект 1 миллион строк кода. Мое мнение что производительность Rust будет "распухать" там, поскольку вы вряд ли будете сильно оптимизировать как число-дробилки, так что код будет содержать дополнительные runtime-косты равномерно размазанные по проекту. С другой стороны у вас таком большом проекте появится большая уверенность в memory safety, частично за счет compile-time, частично за счет рантайм.

отсутствие placement new

Вроде же ещё в прошлый раз разобрались, что это не placement new отсутствует, а возможность статического выделения памяти для не-Sized объектов.


отсутствие указателей на методы

А куда они делись?


борьба с borrow checker решается при помощи clone() или штук вроде Rc

С borrow checker не надо "бороться", им надо пользоваться. И при чём тут вообще clone()? С чем вы боретесь в плюсах когда передаёте структуру по значению?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории