Комментарии 25
А если использовать readIdx_.load(std::memory_order_relaxed)
вместо readIdxCached_
, это будет не то же самое? В чём разница?
В принципе, тут везде можно читать расслабленно, так как устаревание чужого курсора ничего не ломает. Тут важно только писать с барьером памяти, чтобы работа с буфером и запись в свой курсор были упорядочены.
На x86 да, а вот, например, на arm упс получится
Почему это?
Небольшая выдержка про порядок Release-Acquire.
Если atomic store в потоке A помечен как memory_order_release, atomic load в потоке B из той же переменной помечен как memory_order_acquire, а load в потоке B считывает значение, записанное store в потоке A, то store в потоке A synchronizes-with (синхронизируется с) load в потоке B.
Все записи в память (включая неатомарные и атомарные с memory_order_relaxed), которые happened‑before (произошли до) atomic store с точки зрения потока A, становятся видимыми побочными эффектами в потоке B. То есть, как только atomic load завершен, поток B гарантированно увидит все, что поток A записал в память. Это обещание выполняется только в том случае, если B фактически получает значение, которое A сохранил (или значение из более поздней последовательности записи с memory_order_release).
Таким образом не достаточно установить только memory_order_release при записи в атомик, нужно еще и прочитать из него с memory_order_acquire.
Искусство писать простые вещи сложным языком... Написали бы просто: чтения после acquire гарантированно увидят все записи до release, без барьеров на каждом из потоков порядок видимости случаен. Подробную доку нашёл тут: https://lwn.net/Articles/576489/
В этом случае будет задействован механизм синхронизации кеша между ядрами, о чем есть упоминание в статье (в начале главы "Оптимизированный кольцевой буфер").
При чтении из readIdx_ придется вытеснить в память значение из кеш линии ядра, в котором последний раз проводилась модификация, попутно сбросив состояние кеш линии в shared.
А при записи в readIdx_ придется инвалидировать все кеш линиию в ядре, которое последним читало из этой памяти.
В случае же с readIdxCached_ никаких дополнительных синхронизаций не потребуется (кроме первоначального чтения), так как эта память будет присутствовать только в кеше одного ядра.
С протоколом MESI можно поиграться здесь https://www.scss.tcd.ie/Jeremy.Jones/vivio/caches/MESIHelp.htm
"Разумеется, это также означает, что, если вам приходится оперировать сравнительно крупными элементами, то вы особенно не выиграете от оптимизации кольцевого буфера"
на этом можно было бы и закончить с оптимизацией, имхо.
Передаём через буфер не сам объект, а исключительную ссылку на него, и всё.
И зачем нужно, чтобы ссылки кешировались? Это будет влиять на производительность? Просто я видел использование кольцевых буферов в драйверах Ethernet и использовал из в драйверах АЦП, везде стат аллокация памяти и DMA. С boost не работал. Поэтому и не очень понимаю о чём речь, хотя идея и понятна
Тут я уже не понял вашего вопроса.
Я не очень понял реальные юзкейсы, где может потребоваться использование циклических буферов такого типа. Буфер под ссылки с кешированием - тоже не очень буфер, имхо. Если есть короткая ссылка куда нибудь буду рад познакомиться
Эти кейсы из библиотек биржевого трейдига - цены гонять в busy loop с минимальной задержкой. Тут выигрыш может быть даже от того, что кэш-линия последовательно пишется.
А для выравнивая лучше брать двойной размер (2x64=128) префетчер все равно две за раз прочтет при запросе (текущую, и следующую за ней), и соседнюю - лучше не "марать".
Можно ли использовать кольцевые буферы для организации rate_limiter и мониторинга производительности? Есть ли быстрые реализации распределённого кольцевого буфера?
Что такое распределённый кольцевой буфер?
Единый буфер на несколько нодов/подов/узлов/машин
Это противоречит смыслу такого буфера, как места, где читатель и писатель не мешают друг другу. Но можно из нескольких таких буферов сделать коммуникацию многие ко многим. Например: https://github.com/nin-jin/go.d
Потестил, кстати, на i5-8300H CPU @ 2.30GHz, переливание и сложение 160 миллионов интов занимает как раз секунду.
Код
import std.datetime.stopwatch;
import std.range;
import std.stdio;
import std.algorithm;
import jin.go;
enum int iterations = 160_000_000;
static auto produce()
{
return iterations.iota;
}
void main()
{
auto timer = StopWatch(AutoStart.yes);
int sum = go!produce.fold!q{a+b};
timer.stop();
writeln("Result\t\tTime");
writeln(sum, "\t", timer.peek.total!"msecs", " ms");
}
Кольцевой буфер также называется очередью «один производитель — один потребитель» (SPSC). В ней не бывает ожидания (и, соответственно, не бывает блокировок)
Разве переполнение буфера не вызовет ожидание на запись и соответсвующую блокировку?
На практике такие ситуации не редкость, когда темп записи данных больше темпа их чтения.
У меня давний вопрос по поводу кольцевого буфера.
Я правильно понимаю, что записывать может один поток, а читать другой поток, при этом они могут выполняться на разных ядрах и это не создаст гонки (при условии атомарности операций с указателями чтения и записи)?
И еще, не будет ли гонки, когда мы опустошим буфер или наоборот заполним (т.е. когда указатель записи упрется в указатель чтения и наоборот)?
Да, это исключительно для ситуации с одним потоком-читателем и одним потоком-писателем. Когда поток упирается в предел, происходит возврат из функции с флагом false, так что гонке тут взяться неоткуда.
А не будет гонки по причине одновременного доступа к указателям? Или т.к. каждый поток записывает только свой указатель, а другой его исключительно только читает, то тут все нормально?
Я больше с baremetal имею дело. И мне интересно, выходит, я могу условно в обработчике прерывания писать в кольцевой буфер, а в основном цикле читать из него и критические секции тут не нужны получается?
Сами атомики безопасны, их можно читать и писать из разных потоков без каких либо гонок. Единственное, что приходится дополнительно защищать - это память, где будут лежать передаваемые данные. При этом сами указатели вектора - указатели начала данных, конца данных и конца выделенной памяти - только читаются, поэтому тут гонок нет).
Оптимизация кольцевого буфера для повышения пропускной способности