Pull to refresh

Comments 24

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

У сигнальных микроконтроллеров dsPIC33 есть режим «Modulo addressing», где процессор помогает аппаратно, без необходимости тратить машинные такты на логику кольцевого буфера.

Бенчмарк не совсем корректен, размер сообщения кратен размеру буфера, значит в медленном варианте один из вызовов memcpy будет всегда з нулевым размером.

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

Это единственный плюс, никакой другой оптимизации там нет.

Добавить c++ и с помощью лямбд сделать zero-copy

Я бы померил версию без оператора % (сделать размер буфера кратным степени 2 и использовать известный трюк, либо просто if).
А также cделал бы lock-free и разнес указатели read/write в разные cache lines (иначе MESI эту line будет постоянно дергать туда-сюда).
И как ksergey01 написал (плюсанул бы но кармы мало), лучше на С++. У меня в песочнице есть ringbuf.h и более общий pipeline.h для примера.


Трюк с двойным отображением прикольный, но интересно сколько он добавит если сначала сделать всё, что я выше описал. Я понимаю, что там дело не только в скорости но и в удобстве — не надо поддерживать специальный случай двух сегментов если пишешь в сеть (хотя с сетью scatter/gather и iovec сглаживают неудобство почти полностью).

Размер буфера и так должен быть кратным степени двойки, т.к. кратен размеру страницы. Еще можно задействовать huge pages.

Еще отлично ляжет интерфейс как в boost::beast::flat_buffer
А зачем тут lock-free делать? О многопоточности речи вообще не идет.

В коде на гитхабе по ссылке из статьи, конечно же, есть про многопоточность с posix threads/mutex. Иначе зачем вообще ring buffer делать, если нет речи о конвейерной обработке (=многопоточнось)?

ring buffer вроде бы и так является одной из основных lock-free структур данных. Его можно без довесок использовать для передачи информации из одного потока в другой (но не более одного записывающего и не более одного читающего).

Только если он написан как lock free. Никто не мешает просто взять массив и обернуть во mutex — будет не lock feee. Или вообще не делать синхронизацию если писатель и читатель - один и тот же поток (так тоже бывает).

Если не накосячить с реализацией — то ring buffer «из коробки» пригоден для передачи данных между разными потоками без mutex или других примитивов синхронизации. Использовался для этой цели с незапамятных времён. О чём вообще дискуссия? Кто-то не умеет реализовывать ring buffer для читателя и писателя из разных потоков? Кто-то пихает туда mutex?

Вы в каком языке нашли коробки с ring buffers? Или вы под "из коробки" имеете ввиду boost, disruptor, и т.п.? Статья, по-моему, о том как их писать а не как их использовать. На С++ и Java без правильной happens-before семантики ничего работать не будет (в машинных кодах тоже, но по меньшему количеству причин).

А можно подробнее, что вы имеете в виду? Какая в C++ и в ассемблере правильная happens-before семантика?

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

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

Зависит от use case. Вам зачем?

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

Вот прошу уточнить.
Спасибо, посмотрел. Библиотека довольно объёмная. Хоть её и можно приводить в пример в качестве профессиональной реализации, но всё же остаётся открытым вопрос: что из этого кода является существенным, а что — несущественным для реализации кольцевого буфера для обмена данными между потоками?

Вы не можете что-нибудь пояснить по этому поводу?

Это было в ответ на — "чего там знать-то". Я привел ссылку на header file и поля, на которых весь concurrency SPSC ring buffer основан. У меня точно такая-же, но обобщенная на N участников (конвейер). Там барьеры надо расставить правильно везде (в терминах C++ выбрать правильный memory_order), и данные в памяти разнести, чтобы строчку в кэше туда-сюда не дёргать.


Вариантов реализации рабочих есть несколько, у всех описание почему они single/multi producer/consumer wait-free/lock-free будет на абзац как минимум, в идеале и картинку надо. Целая маленькая статья будет и скорее всего их уже миллион хороших в Интернете. Всё что я хотел сказать — concurrent ring buffer это офигеть как не просто.

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

В однопроцессорных системах и на ассемблере было гораздо проще жить :)
Sign up to leave a comment.