Текущий проект на 37cloc, компиляция после изменения нескольких строк кода: 1-2 секунды компиляция, 5 сек линковка. Долгая линковка из-за LTO, без него 1 сек.
Компиляция с нуля: ~35 секунд, 5 сек линковка. Но с нуля я запускаю раз в месяц наверное. Вот для CI/CD в некоторых случаях нужна компиляция с нуля, там немного напрягает.
Гарантировать - никак. Предотвратить опасные последствия - правильным использованием watchdog и учитывая в коде, что мой поток может не успевать за DMA и у меня будут пропуски сэмплов.
Ну вот мы и вернулись к началу. Это не возможно написать надежно на любом языке, даже на ассемблере. И watchdog вам не поможет, он только добавит вероятность, что чип перезагрузится при ошибке, но гарантий нет.
Впрочем, необходимость увеличения кольцевого буфера DMA или производительности потока чтения, обычно, выявляется ещё при отладке и стресс-тестах.
И вы только подтвердили мои слова: все-равно придется взять чип помощнее или ПЛИС.
А я вообще не вижу Вашего ответа про чипы на ядре Cortex-M со встроенным WiFi и BLE. Так что давайте двигаться последовательно )
Много, 7 семейств МК на Cortex-M3 или M4. Выбор зависит от задачи, а вы сейчас пытаетесь меня убедить, что если нужен WiFi, то только ESP32.
Только если Вы компенсируете возросшую в три раза себестоимость готового изделия из собственного кармана.
Или Вы только чужими деньгами так вольно распоряжаться готовы?
Внезапно себестоимость состоит только из чипа МК? А стоимость разработки уже не надо учитывать? Которая будет гораздо выше, пока вы пытаетесь впихнуть в чип невпихуемое.
Кстати, а почему в 3 раза? Даже в STM32F0 не один порт GPIO, и стоят они 77 руб на элитане.
Для SPSC кольцевого буфера AtomicPtr и правильный подбор Ordering позволяют это сделать быстро и безопасно, и без циклов CAS.
Даже между прерыванием и главным циклом. API примерно такой, как писал mayorovp выше. В понедельник доберусь до работы, могу вам скинуть код, если интересно.
Не надеяться на компилятор, а воспользоваться собственной головой. В общем случае, при работе с bare metall компилятор не помощник для получения безопасного кода.
Это не ответ, а отписка. Мне интересно, как вы будете гарантировать, что DMA не перезапишет еще не прочитанные данные, в тот самый момент, когда вы с ними работаете (без копирования).
что если на GPIO нужен параллельный 8-битный интерфейс
Выше мы обсуждали 3-битный интерфейс, и мой ответ был про него.
Для 8-битного интерфейса ответ прежний: взять другой МК, в котором больше 1 порта GPIO. Просто не вместо, а в дополнение к ESP32. И SPI между ними.
Тоже приводил пример выше с управлением более, чем одной матрицей WS2812.
Так в этой задаче вам нужно только 2 ножки GPIO, но достаточно быстро. На таймерах и DMA не вариант?
P.S. Я совсем не против ESP32, для WiFi и BLE ничего лучше нету. Но обычная периферия МК у них бедная, согласитесь.
Не я хочу без копирования, а жесткие ограничения на доступную память и производительность при разработке для встроенных систем требуют избегать копирования.
Тогда объясните, как это сделать без копирования и безопасно. На любом языке.
При чем тут количество "ножек", если речь идет о портах? Мне даже интересно стало, что же Вы предложите вместо ESP32-C2/C3/H2/C6, у которых всего один порт? Вообще отказаться от них, если нужно управлять более, чем одним пином GPIO? А если нужно управлять тремя пинами GPIO, то вообще отказаться от продукции Espressif, так как я не знаю у них ни одного МК, имеющего более двух портов?
И вообще, это очень узкая задача. Обычный цикл выкл. прерывания - read-copy-update регистра - вкл. прерывания займет около 20 тактов, зависит от архитектуры. Это достаточно быстро для абсолютного большинства задач.
Если не секрет, что за проект, который жестко завязан на Espressif, и одновременно такие жесткие требования к GPIO?
Мы обсуждали кольцевой буфер, в который пишет DMA без остановок. Перечитайте тред выше.
Особенность DMA в том, что он не будет проверять указатель чтения, только смещает указатель записи. Если же обе стороны - программы, то можно проверять оба указателя, чтобы не перезаписать еще не прочитанные данные.
Туда вообще не CPU может писать, а DMA. Но исключить при этом ошибку, когда оттуда читается до записи нельзя. Вот и приходится верифицировать весь код.
Практика показывает, что в Rust можно написать чтение из кольцевого буфера DMA, внутри MaybeUninit. Минус такого подхода - копирование байт при чтении. Но без копирования я не вижу, как это написать безопасно даже в С.
Например, изменять значения пинов у MK на конкретном порту из разных потоков безопасно никак не получится, потому что значение конкретного пина изменяется накладыванием маски на весь порт.
Похоже это невозможно сделать безопасно на любом языке программирования. Единственное решение - разнести логически связанные пины по разным портам GPIO.
Есть большая разница между "невозможны принципиально" и "возможны при каких то условиях".
там нет ни одной НЕ unsafe функции))
Если для вас неприемлем любой код, который внутри содержит unsafe, то как тогда пользоваться стандартной библиотекой в любом языке? Её реализация, как и любое FFI, по определению unsafe.
Как раз для зонда продолжает работать с "отравленными" данными может привести к печальным последствиям. Например он перестанет отвечать на внешние команды и будет потерян.
В коде для зондов стараются обработать все возможные ошибки. Но если пропустили, то лучше бы упасть чем UB.
Причем тут "развернуть и против вас"? Я опираюсь на факты, а не риторическими утверждения.
В таком случае вам нужно было под той статьей написать дополнение, например что "вы имели ввиду оптимальную реализацию". Но что-то вам помешало принять факты, например вера)
Этот код ничем от отличается от кода на С++ и не дает гарантий безопасного управления памятью, о чем я и писал выше.
Ну да, слабые ссылки в std Rust такие же как в std С++. Но и применяются они крайне редко (я вот ни разу не видел). Вместо них применяют готовые алгоритмы с множественным владением. Как и в С++.
А вот о чем вы умалчиваете: другие виды утечек памяти не возможны, как и use after free. Если вы не используете слабые ссылки, никогда память не утечет.
Лучше какая-то (и очень неплохая) защита на уровне языка, чем никакой.
Например, вот в этом комментарии вам прямо пишут, что написать двусвязный список можно без unsafe (с помощью счетчиков ссылок и слабых ссылок). Но нельзя написать оптимальную реализацию без unsafe. Аналогично можно написать и более сложные алгоритмы (например деревья).
Но вы и тогда, и сейчас говорите что это не возможно. А, между тем, больше половины вашей статьи опирается на этот тезис, который очевидно ошибочный. Так кто из нас обратился к религии?
А от тех, кто писал на нем прод, и ушел в другие языки, претензии принимаются?)
Разве то, что они пишут новый код на Rust, и не пишут его на С++, не доказывает первоначальный тезис?
А легаси был, есть и будет. И его нет смысла переписывать с языка Х на язык Y.
Это распространенное заблуждение. В C++ нет restrict, массивов переменной длины:
struct X { int m; int n; char bytes[]; };Неявный typedef в С++, из-за которого этот код на С не скомпилируется в С++:
enum Bool { FALSE = 0, TRUE = 1 }; typedef int Bool;Полный список из 23 пунктов можно прочитать здесь.
Проблема во многом преувеличена.
Текущий проект на 37cloc, компиляция после изменения нескольких строк кода: 1-2 секунды компиляция, 5 сек линковка. Долгая линковка из-за LTO, без него 1 сек.
Компиляция с нуля: ~35 секунд, 5 сек линковка. Но с нуля я запускаю раз в месяц наверное.
Вот для CI/CD в некоторых случаях нужна компиляция с нуля, там немного напрягает.
Асссемблерные вставки.
FFI, но чтобы в нем разобраться, нужно прочитать весь Rustonomicon, т.к. модель памяти отличается от С.
Я не знаю, какие именно прерывания вы имеете ввиду. Но на микроконтроллерах прерывания реализуются библиотеками рантайма, например cortex-m-rt.
Ну вот мы и вернулись к началу. Это не возможно написать надежно на любом языке, даже на ассемблере.
И watchdog вам не поможет, он только добавит вероятность, что чип перезагрузится при ошибке, но гарантий нет.
И вы только подтвердили мои слова: все-равно придется взять чип помощнее или ПЛИС.
Много, 7 семейств МК на Cortex-M3 или M4.
Выбор зависит от задачи, а вы сейчас пытаетесь меня убедить, что если нужен WiFi, то только ESP32.
Внезапно себестоимость состоит только из чипа МК? А стоимость разработки уже не надо учитывать? Которая будет гораздо выше, пока вы пытаетесь впихнуть в чип невпихуемое.
Кстати, а почему в 3 раза? Даже в STM32F0 не один порт GPIO, и стоят они 77 руб на элитане.
Пишете через DMA в порт GPIO, события для DMA выдает таймер. Кстати ваша ссылка не открывается.
Один порт GPIO, мало ножек, в большинстве чипов нет FPU, мало UART, только один SPI и т.д.
https://en.wikipedia.org/wiki/ESP32
Кстати, какую цену вы для них считаете?
Для SPSC кольцевого буфера AtomicPtr и правильный подбор Ordering позволяют это сделать быстро и безопасно, и без циклов CAS.
Даже между прерыванием и главным циклом. API примерно такой, как писал mayorovp выше. В понедельник доберусь до работы, могу вам скинуть код, если интересно.
Это не ответ, а отписка.
Мне интересно, как вы будете гарантировать, что DMA не перезапишет еще не прочитанные данные, в тот самый момент, когда вы с ними работаете (без копирования).
Выше мы обсуждали 3-битный интерфейс, и мой ответ был про него.
Для 8-битного интерфейса ответ прежний: взять другой МК, в котором больше 1 порта GPIO. Просто не вместо, а в дополнение к ESP32. И SPI между ними.
Так в этой задаче вам нужно только 2 ножки GPIO, но достаточно быстро. На таймерах и DMA не вариант?
P.S. Я совсем не против ESP32, для WiFi и BLE ничего лучше нету. Но обычная периферия МК у них бедная, согласитесь.
Тогда объясните, как это сделать без копирования и безопасно. На любом языке.
Взять любой чип на ядре Cortex-M3 или M4, и использовать аппаратный bit-banding.
И вообще, это очень узкая задача. Обычный цикл выкл. прерывания - read-copy-update регистра - вкл. прерывания займет около 20 тактов, зависит от архитектуры. Это достаточно быстро для абсолютного большинства задач.
Если не секрет, что за проект, который жестко завязан на Espressif, и одновременно такие жесткие требования к GPIO?
Мы обсуждали кольцевой буфер, в который пишет DMA без остановок. Перечитайте тред выше.
Особенность DMA в том, что он не будет проверять указатель чтения, только смещает указатель записи. Если же обе стороны - программы, то можно проверять оба указателя, чтобы не перезаписать еще не прочитанные данные.
Вы точно открывали мою ссылку? Там конкретный пример, как это можно написать безопасно.
Если же вы хотите без копирования, то тут не получится безопасно на чем угодно, хоть на ассемблере.
Это очень специфическая задача, и под нее можно выбрать МК с большим количеством ножек, и не решать неразрешимые проблемы в коде.
Он не потокобезопасный не смотря на Send и Sync. Смотрите API RingBuffer, который требует &mut self, и комментарий в исходниках:
// SAFETY: all methods that require mutable access take &mut,// being send and sync was the old behavior but broke when we switched to *mut T.
И, как верно заметили ниже, в реализации нет AtomicUsize либо AtomicPtr. Но язык и не позволит его использовать из разных потоков.
Вот например настоящий потокобезопасный кольцевой буфер, я его регулярно использую на микроконтроллерах. В теории можно и под ОС использовать.
Практика показывает, что в Rust можно написать чтение из кольцевого буфера DMA, внутри MaybeUninit. Минус такого подхода - копирование байт при чтении. Но без копирования я не вижу, как это написать безопасно даже в С.
Похоже это невозможно сделать безопасно на любом языке программирования. Единственное решение - разнести логически связанные пины по разным портам GPIO.
Прошу ссылку на сайт языка или его документацию, где это утверждают.
Есть большая разница между "невозможны принципиально" и "возможны при каких то условиях".
Если для вас неприемлем любой код, который внутри содержит unsafe, то как тогда пользоваться стандартной библиотекой в любом языке?
Её реализация, как и любое FFI, по определению unsafe.
Возможны. Есть и другие библиотеки.
Может вы имели ввиду "невозможны без unsafe"?
Как раз для зонда продолжает работать с "отравленными" данными может привести к печальным последствиям. Например он перестанет отвечать на внешние команды и будет потерян.
В коде для зондов стараются обработать все возможные ошибки. Но если пропустили, то лучше бы упасть чем UB.
В таком случае вам нужно было под той статьей написать дополнение, например что "вы имели ввиду оптимальную реализацию". Но что-то вам помешало принять факты, например вера)
Ну да, слабые ссылки в std Rust такие же как в std С++. Но и применяются они крайне редко (я вот ни разу не видел). Вместо них применяют готовые алгоритмы с множественным владением. Как и в С++.
А вот о чем вы умалчиваете: другие виды утечек памяти не возможны, как и use after free. Если вы не используете слабые ссылки, никогда память не утечет.
Лучше какая-то (и очень неплохая) защита на уровне языка, чем никакой.
Такой подход можно развернуть и против вас.
Например, вот в этом комментарии вам прямо пишут, что написать двусвязный список можно без unsafe (с помощью счетчиков ссылок и слабых ссылок). Но нельзя написать оптимальную реализацию без unsafe. Аналогично можно написать и более сложные алгоритмы (например деревья).
Но вы и тогда, и сейчас говорите что это не возможно. А, между тем, больше половины вашей статьи опирается на этот тезис, который очевидно ошибочный. Так кто из нас обратился к религии?
От того что аргументы уже озвучены, они стали менее актуальными? Ведь вы привели свои аргументы еще раз, почему я не могу?
P.S. Клонов у меня нет.