Комментарии 24
оно вроде бы и интересно, но использовать в реальном коде без очень большой нужды я не буду )
Вот оно же в готовом виде: https://linux.die.net/man/3/vrb
И реализация для винды: https://github.com/nschurando/libvrb-win
Бенчмарк не совсем корректен, размер сообщения кратен размеру буфера, значит в медленном варианте один из вызовов memcpy будет всегда з нулевым размером.
А ведь кроме оптимизации ведь получается, что можно возвращать указатель на данные, потому что в них теперь не будет разрывов на границе, это довольно круто в некоторых кейса.
Я бы померил версию без оператора %
(сделать размер буфера кратным степени 2 и использовать известный трюк, либо просто if).
А также cделал бы lock-free и разнес указатели read/write в разные cache lines (иначе MESI эту line будет постоянно дергать туда-сюда).
И как ksergey01 написал (плюсанул бы но кармы мало), лучше на С++. У меня в песочнице есть ringbuf.h и более общий pipeline.h для примера.
Трюк с двойным отображением прикольный, но интересно сколько он добавит если сначала сделать всё, что я выше описал. Я понимаю, что там дело не только в скорости но и в удобстве — не надо поддерживать специальный случай двух сегментов если пишешь в сеть (хотя с сетью scatter/gather и iovec сглаживают неудобство почти полностью).
Еще отлично ляжет интерфейс как в boost::beast::flat_buffer
В коде на гитхабе по ссылке из статьи, конечно же, есть про многопоточность с posix threads/mutex. Иначе зачем вообще ring buffer делать, если нет речи о конвейерной обработке (=многопоточнось)?
Только если он написан как lock free. Никто не мешает просто взять массив и обернуть во mutex — будет не lock feee. Или вообще не делать синхронизацию если писатель и читатель - один и тот же поток (так тоже бывает).
Вы в каком языке нашли коробки с ring buffers? Или вы под "из коробки" имеете ввиду boost, disruptor, и т.п.? Статья, по-моему, о том как их писать а не как их использовать. На С++ и Java без правильной happens-before семантики ничего работать не будет (в машинных кодах тоже, но по меньшему количеству причин).
Вот, пожалуйста — C++ Memory Model и Memory Ordering.
Простым языком — и компилятор, и процессор по умолчанию оптимизируют исполнение программы как однопоточное (всё, что не меняет наблюдаемый результат, разрешено — перестановка кода, кэширование на регистрах и т.п.). Программист должен явным образом указать ограничения компилятору (а в случае ассемблера и процессору), чтобы программа работала правильно при многопоточном исполнении.
Зависит от use case. Вам зачем?
На С++ и Java без правильной happens-before семантики ничего работать не будет (в машинных кодах тоже, но по меньшему количеству причин).
Вот прошу уточнить.
Посмотрите, на исходниках boost::spsc_queue
как там сделаны write_index_
и read_index_
.
Вы не можете что-нибудь пояснить по этому поводу?
Это было в ответ на — "чего там знать-то". Я привел ссылку на header file и поля, на которых весь concurrency SPSC ring buffer основан. У меня точно такая-же, но обобщенная на N участников (конвейер). Там барьеры надо расставить правильно везде (в терминах C++ выбрать правильный memory_order), и данные в памяти разнести, чтобы строчку в кэше туда-сюда не дёргать.
Вариантов реализации рабочих есть несколько, у всех описание почему они single/multi producer/consumer wait-free/lock-free будет на абзац как минимум, в идеале и картинку надо. Целая маленькая статья будет и скорее всего их уже миллион хороших в Интернете. Всё что я хотел сказать — concurrent ring buffer это офигеть как не просто.
Используем черную магию для создания быстрого кольцевого буфера