Комментарии 10
Люблю такие разборы: меньше “кажется быстрее”, больше цифр. Вопрос по методике: вы фиксировали affinity/pinning потоков и режимы CPU (turbo/energy)? И пробовали notify_one vs notify_all? На некоторых профилях можно случайно замерить цену “лишних пробуждений”, а не самого примитива. Было бы интересно увидеть отдельный прогон на реальном contention (N threads) и с указанием kernel/glibc — чтобы переносить выводы в прод-реальность.
Тут вовсе нет пробуждений ибо потоки всегда успешно захватывают блокировку (они просто разные блокировки берут)
Меня интересовала прежде всего стоимость накладных системных вызовов
По поводу контеншен случая: а тут мне не понятно какой ворклоуд надо смотреть - нужно делать какую то сетку по вероятности пересечений взятия локов по времени, но я подумаю
Случай же "контеншенов мало" должен быть общим и самым частым, ибо программы стараются так писать
Стоило всё таки упомянуть, что в Линуксе mutex - обвязка над futex.
std::mutex может быть реализован по разному, там где я вижу - оно проваливается в pthread_mutex_* , который действительно в итоге обвязка futex
Ну так futex изначально задуман в первую очередь для ускорения стандартных функций синхронизации POSIX (чтобы дать возможность реализовать быстрый путь в userspace). Пользоваться напрямую, конечно, тоже можно - но только чётко понимая реальный выигрыш (и стоит ли он проблем с переносимостью).
да, мои итоговые выводы такие и вышли
я согласен что в тексте можно вычитать неточность, что якобы lock у мьютекса должен также иметь системный вызов
btw - зачем-то дали аж в стандарте wait/notify, и было интересно узнать - что будет если их на прямую использовать (как минимум можно чуть память выиграть). ну и стоимость вызова wait-а думаю мы тоже в этом эксперименте вычислили
Просто надо понимать, что сравнивается разный код в userspace c использованием одного и того же системного вызова (futex).
зачем-то дали аж в стандарте wait/notify
Если речь про std::atomic - то стандарт C++ не привязан к линуксу, POSIX функциям или конкретному железу. Возможно, на каких то сочетаниях операционки/процессора их можно реализовать эффективнее.
зачем-то дали аж в стандарте wait/notify
Чисто по интерфейсу, std::condition_variable в паре, скажем, с std::mutex:
Может использоваться только в одном адресном пространстве;
В описании интерфейса явно указывается на блокировку потока;
UB, в случае завершения потока владельца
std::mutexбез его освобождения;Размеры не специфицированы;
Имеют методы
native_handle().
std::atomic:
При наличии макроса
ATOMIC_..._LOCK_FREEможет использоваться в памяти, отображаемой в пространство нескольких процессов, смотрите [atomic.lockfree] (5);В описании интерфейса есть только слова, что более эффективно, чем простой опрос;
Формально, при завершении потока или процесса, без освобождения, нет UB, но нет и никаких гарантий, как, скажем, при использовании
PTHREAD_PROCESS_SHAREDи ошибкиEOWNERDEAD;Рекомендованный размер совпадает с размером базового типа, но это лишь рекомендация;
В целом, std::atomic может быть реализован, к примеру, на основе инструкций MONITOR/MWAIT или чего-то подобного, если они доступны.
Это к тому, что анализ используемой в тесте C++ библиотеки и компилятора излишне краток.
Тогда давайте еще добавим что нормальная поддержка в тсан есть только у мьютекса. Я не уверен можно ли правильно разметить самописное на атомиках, чтобы разлочивание в потоке отличном от взявшего лок работало (не валился сам тсан и была какая то польза от его диагностик)
В общем, если на тсан хочется рассчитывать - то тоже лучше мьютекс
Честно говоря, я сравнивал интерфейсы стандарта C++. Грубо говоря, стандарт у нас один, а разных компиляторов или инструментов много.
Что касается, конкретно, ThreadSanitizer, вроде как, он поддерживает обнаружение проблем в том числе исходя из первых принципов, т.е. исходя из отношения Happens Before. Но, как у всякого инструмента, есть сложности, ограничения и прочие несовершенства. Но на нём Свет клином не сошёлся же.
А так, да, для блокировок (мьютексы, спин-локи) есть более простые и быстрые алгоритмы. А всё, что связано с wait()/notify_all() немного сложнее в настройке и несколько медленнее, и не важно это std::atomic или std::condition_variable .
P.S.
Хотя std::atomic, да, возможно сложнее инструментировать и больше проблем с разнообразием компиляторов и/или библиотек C++.

mutex vs futex