Comments 51
Статья должна называться: еще один аргумент в пользу перехода на Rust.
Вообще-то в данном примере происходит не оптимизация цикла, а назальные демоны из-за UB, и все рассуждения автора можно выкинуть.
Rust умеет оптимизировать циклы. В данном случае компилятор Rust запретит работу с переменной в другом потоке без явного использования примитивов синхронизации.
Кстати, заинтересовало, как эти проверки выполняются для переменной, которая используется в уже скомпилированном коде. Или такой ситуации вообще не возникает?
как минимум атомарными
Не атомарными, а под защитой примитивов синхронизации. Это могут быть атомики, мьютексы и прочие вещи.
Тогда это отличная причина не переходить на Rust.
Ну если вы хорошо знаете C++, то должны знать, что в стандарте C++ сказано, что гонки данных есть UB. Либо вы плохо знаете стандарт C++, либо вы не понимаете, что такое UB, следовательно тоже плохо знаете C++.
как эти проверки выполняются для переменной, которая используется в уже скомпилированном коде. Или такой ситуации вообще не возникает?
Эти проверки выполняются только во время компиляции с помощью типажей Send & Sync. Следовательно во время выполнения вы не несете никаких пенальти за безопасность.
Для примера можно взять очередь на массиве, в которую пишет один поток, и из которой читает другой. Синхронизация памяти очереди происходит неявно с помощью индексов на первый/последний элементы очереди. Но, насколько я понимаю, в Rust'е все равно придется ставить мьютекс, что бы считать/записать данные?
Синхронизация памяти очереди происходит неявно с помощью индексов на первый/последний элементы очереди.
Такая неявность с современными процессорами не пройдёт. Если один поток записал в элемент массива и увеличил индекс, другой поток может увидеть увеличенный индекс, но не увидеть новое значение в массиве. Почитайте про memory barriers.
Но, насколько я понимаю, в Rust'е все равно придется ставить мьютекс, что бы считать/записать данные?
В Rust можно делать то же, что и во всех других языках. Но код, который компилятор не может проверить на корректность (разыменование указателя, обращение к глобальной мутабельной переменной, вызов unsafe функции и что-то ещё), нужно отмечать как unsafe.
Почитайте про memory barriers.Да, подзабыл про них. Но насколько я знаю, барьеры — это сильно легче чем мьютексы (в плане производительности).
нужно отмечать как unsafe.Ну так то да, но обсуждение началось с утверждения будто необходимость ставить volatile и пользоваться атомарными операциями в плюсах — это повод переходить на Rust. А я привел пример задачи, с которой мирок Rust'а не справляется, и нужен мирок C++.
А я привел пример задачи, с которой мирок Rust'а не справляется, и нужен мирок C++.
Собственно это не мирок С++, а мирок железа. В Rust нужно сказать "Да, я знаю что делаю" и поставить unsafe, в C++ — нет.
Ну или можно взять модуль std::sync::mpsc.
Rust не даст возможность мутировать переменную таким образом в принципе.
На самом деле существуют правила, которые даже в unsafe нельзя обойти.
Это какой-то миф про unsafe. В книге черным по белому написано что можно делать в unsafe, это 4 вещи:
- Разыменовывать сырой указатель
- Вызывать unsafe функцию или метод
- Обращаться к или модифицировать статическую переменную
- Реализовывать unsafe trait
Всё. Борроу чеккер и все остальные проверки там никуда не уходят.
ну, без transmute
никак.
Позвольте мне поправить Вас: «Ещё один аргумент в пользу перехода на Rust или другой безопасный язык для тех, кто не знает, что такое Undefined Behaviour».
Очень пошлая тенденция поклонников этого нового языка влазить в темы связанные с C/C++ со своими умозаключениями.
Undefined Behaviour
которое во многом способствует хорошей оптимизации, которая характерна для C/C++, где-то тут на ресурсе была об этом статья, да не одна а целых три. Я считаю, что каждый rust-аман должен ознакомится с этими текстами, прежде чем лезть в дискуссию о С/С++
Чем-то это напоминает ситуацию с современным истребителем — за высокую маневренность мы платим полным отсутствием статической устойчивости. Для плюсов сие нормально, и раст тут не при чем
P.S.: Вангую очередной срач C/C++ vs Rust…
Из истории известно, что те кто предлагают средство для решения всех проблем, заводят людей в тупик. А упоминание rust-a в статьях о о с++ вызывает отторжение, как и любая навязчивая реклама.
Надеюсь что язык в этом не виноват.
упоминание rust-a в статьях о о с++ вызывает отторжение, как и любая навязчивая реклама
упоминание rust-a в статьях о о с++ вызывает отторжение, как и любая навязчивая реклама
Понимаете, отторжение вызывает не упоминание языка Rust в статьях про С++, а упоминание более подходящего инструмента, который может решать проблемы на таком уровне, на котором С++ никогда не сможет этого сделать. Вы обижаетесь, минусуете, считаете себя профессионалом, который может решить эти проблемы только лишь силой своего ума, но это не так. Читайте статью https://www.parity.io/why-rust/ .
Я понимаю, что это вызывает у вас ломку, вам больно на это смотреть, но я готов платить за это своей кармой. Пусть так. Лишь бы хоть 1 из сотни решился посмотреть в сторону Rust.
Да я не про вас. Я про ситуацию в целом.
Долгое время умение программировать на C/C++ считалось вершиной мастерства. Считалось, что самые крутые программисты не допускают ошибок.
Компиляторы C/C++ скрывают UB, они используют его для оптимизации кода. Поэтому ошибка может жить десятилетия, ждать свое часа. Либо наоборот, некоторые оптимизации компилятора с использованием UB считаются "очевидными", типа можно вызвать функцию-член у нулевого указателя, если внутри этой функции-члена не происходит вызова полей/методов this.
Лишь бы хоть 1 из сотни решился посмотреть в сторону Rust
Я слежу за развитием Rust. Но он, а точнее сказать, связанная с ним инфраструктура, пока не удовлетворяет тем задачам, которые я решаю
Код скопипастен из времен обработки прерывания одноядерного процессора и это типичный стиль написания для микроконтроллеров и сегодня., когда все события в системе контролируется вручную.
На микроконтроллер и сегодня можно писать в таком стиле, немного поправив код.
volatile int waitFlag; // не нужен тут указатель от слова совсем
И опять таки, исключительно для случая небольшого тупого однопроцессорного контроллера, где нет места и необходимости в операционной системе ( впрочем там и thread-ов нет, но может быть обработчик прерывания меняющий флаг)
Однако проведенный пример в много-ниточной среде вызовет тупое ожидание в цикле проверки до переключения контекста процессора, впустую занимает время и не дает операционной системе оптимизировать загрузку процессора. В многопроцессорной системе все еще хуже с данным кодом. (синхронизация кеша и прочая весьма дорогая фигня)
Корректный код должен использовать мютекс ( в posix нотациии ) поручить его ожидание операционной системе, для чего существуют специальные функции ОС.( wait триггер или waitFor… в Winapi ) С ++ последних версий имеет платформонезависимый интерфейс к этим функциям ( видимо надоело ждать когда же микрософт удосужится реализовать полноценный posix)
Atomic почти не меняет ничего из вышесказанного. В тупую ждать в вышеуказанном стиле — плохо.
В общем как и сказано статье — так писать не надо, а кому так надо они и сами об этом знают. :)
Код скопипастен из времен обработки прерывания одноядерного процессора и это типичный стиль написания для микроконтроллеров и сегодня., когда все события в системе контролируется вручную.
Чёрта с два.
Для современных мк применяется те-же алгоритмы компиляторов что и для больших машин. Более того — для программы мк необходимо создать объединение из const volatile и volatile. При этом const volatile проверяется, а volatile — меняется. Иначе компилятор может (должен) заоптимизировать код до нуля, ну или закинуть переменные в регистры.
В любом случае тупая проверка в цикле — дурной тон.
Для одного потока (без ос) — применяется уход в сон. Для работы в составе ос — проверяющий поток должен использовать средства синхронизации. Например уход в зависимость — где его будит управляющий поток, или добровольная отдача машинного времени соседним потокам по условиям проверки.
Без этих малозначительных телодвижений — любой современный мк будет жрать ток как не в себя. Отчего ваши финансовые скрепы могут заметно пошатнуться.
В а целом — совсем недавно была статья habr.com/post/423889 Моё разочарование в софте.
Где достаточно много народу возмущалось сложившейся ситуацией, грозно сотрясали воздух, и дружно вспоминали высокие деревья. Где теперь все эти люди???
Так что можно спокойно пройтись по коду функции main и сократить код.
у вас в коде переменные выставляются в дефолное сосотояние
int x = 0;
дальше перед циклом идёт присваивание адреса
ptr = &x;
Учитывая что *ptr не изменяется в теле цикла — значит условие цикла это константное выражение и можно упростить.
*ptr==x => *ptr==0 => true
Поэтому цикл превращается в
while (true) { }
И из-за любви к С++ только, замечу, что в коде нет thread.join() — даже если из цикла будет выход, программа упадет.
Это уже начинает выглядеть откровенным анахронизмом, как если бы в автомобиле вместо руля был бы манипулятор типа вожжи, а вместо педалей газа нужно было бы хлестать хлыстом по приборной панели. Ну, у нас так принято, деды так завещали, что вы хотели?
Ладно, вообще я люблю C++, но согласитесь, доля истины в этом есть…
Проблема с теорией параллельных вычислений нерешенных в принципе. Пока не существует общего метода трансформации последовательных алгоритмов в эквивалентные параллельные, будет необходимость языках и архитектурах подобных с++ и х86 сориентированных в первую очередь на максимальную эффективность в одном потоке. (не уверен что такой метод изобретут, а если и изобретатут о возвращаемся опять в один поток, так проще формулировать)
А в практической плоскости, для алгоритмов с известным распараллеливанием си подобные языки неплохо работают. Такие как glsl, opencl, cuda и прочие языки shader-ов. Более того, они все больше похожи на с++, чем были раньше.
Модель однопоточного выполнения «натягивается» на строго многопоточное, да ещё и с неоднородным доступом к памяти. Ломаются все языковые концепции, которые работали для более примитивных архитектур, не слишком далеко ушедших от абстрактной машины Тьюринга. Внезапно и условный оператор уже не то чем кажется, и порядок выполнения не такой как в исходном коде, и стоимость (казалось бы) одних и тех же операций начинает отличаться в разы. А уж про отладку я вообще молчу. В итоге сгенерированный ассемблерный код оказывается абсолютно несопоставим с исходным. В отличие от x86 архитектуры где всё ещё поддерживается иллюзия последовательного выполнения, тут и близко такого нет.
И весь этот C-синтаксис начинает вредить. Его использование для вычислительных шейдеров — скорее маркетинговый ход, чтобы привлечь закостенелых программистов у которых алгоритм приравнен к коду на C/C++, а вычислительное устройство — к машине Тьюринга. Крутится себе лента с программой, ЭВМ последовательно считывает и выполняет по одной операции — красота! Только неправда.
Дело привычки. Если учесть, что архитектура GPU была (и сейчас) заточена под обработку графики, то ничего удивительного. В данном случае опыт на х86 скорее мешает, чем помогает.
Мне лично не сильно мешает, хотя иногда забавно что битовые операции могу оказаться на порядки медленнее float. Мы избалованы современными процессорами и компиляторами.
Хотя для эффективной программы, все равно в голове нужно держать архитектуру и особенности целевой платформы. Впрочем все зависит от класса решаемых задач. И общих методов для все задач не существует. Компилятор не может догадаться, что именно мы хотим получить и выбрать правильные структуры данных. Хотя бы по той причине, что он не имеет представления в каком контексте будет выполняться программа и какие данные она получит
Implications of C++ Memory Model Discussions on the C Language 2005-08-26 (см. пример)
Memory model (programming) — изучаем внимательно, втч References., например Threads and memory model for C++
Почему компилятор превратил мой цикл с условием в бесконечный?