Обновить

mutex vs futex

Уровень сложностиСредний
Время на прочтение14 мин
Охват и читатели10K
Всего голосов 10: ↑10 и ↓0+11
Комментарии10

Комментарии 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++.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации