Я хотел знать то же самое, поэтому я измерил это.На моем компьютере (8-ядерный процессор AMD FX (tm) -8150 с тактовой частотой 3,612361 ГГц) блокировка и разблокировка разблокированного мьютекса, который находится в собственной строке кэша и уже кэширован, занимает 47 часов (13 нс).
Из-за синхронизации между двумя ядрами (я использовал CPU # 0 и # 1), я мог вызывать пару блокировать / разблокировать только один раз каждые 102 нс в двух потоках, то есть один раз каждые 51 нс, из чего можно сделать вывод, что это занимаетпримерно 38 нс для восстановления после того, как поток выполнит разблокировку, прежде чем следующий поток сможет снова его заблокировать.
Программа, которую я использовал для исследования этого, может быть найдена здесь: https://github.com/CarloWood/ai-statefultask-testsuite/blob/b69b112e2e91d35b56a39f41809d3e3de2f9e4b8/src/mutex_test.cxx
Обратите внимание, чтоу него есть несколько жестко закодированных значений, специфичных для моего блока (xrange, yrange и rdtsc overhead), поэтому вам, вероятно, придется поэкспериментировать с ним, прежде чем он будет работать для вас.
График, который он генерирует в этом состоянии:
Показывает результаты прогонов теста для следующего кода:
uint64_t do_Ndec(int thread, int loop_count)
{
uint64_t start;
uint64_t end;
int __d0;
asm volatile ("rdtsc\n\tshl $32, %%rdx\n\tor %%rdx, %0" : "=a" (start) : : "%rdx");
mutex.lock();
mutex.unlock();
asm volatile ("rdtsc\n\tshl $32, %%rdx\n\tor %%rdx, %0" : "=a" (end) : : "%rdx");
asm volatile ("\n1:\n\tdecl %%ecx\n\tjnz 1b" : "=c" (__d0) : "c" (loop_count - thread) : "cc");
return end - start;
}
Два вызова rdtsc измеряют количество часовчто нужно, чтобынажмите и разблокируйте `mutex '(с накладными расходами в 39 часов для вызовов rdtsc на моем ящике).Третий ассм - это петля задержки.Размер цикла задержки на 1 счет меньше для потока 1, чем для потока 0, поэтому поток 1 немного быстрее.
Вышеприведенная функция вызывается в узком цикле размером 100 000.Несмотря на то, что функция немного быстрее для потока 1, оба цикла синхронизируются из-за вызова мьютекса.Это видно на графике из того факта, что количество тактов, измеренных для пары блокировка / разблокировка, немного больше для потока 1, чтобы учесть более короткую задержку в цикле под ним.
На приведенном выше графикенижняя правая точка - это измерение с задержкой loop_count, равной 150, а затем, следуя точкам внизу, влево, loop_count уменьшается на единицу каждое измерение.Когда она становится 77, функция вызывается каждые 102 нс в обоих потоках.Если впоследствии loop_count уменьшается еще больше, то больше невозможно синхронизировать потоки, и мьютекс начинает фактически блокироваться большую часть времени, что приводит к увеличению количества часов, которое требуется для блокировки / разблокировки.Также из-за этого увеличивается среднее время вызова функции;поэтому точки заговора теперь снова идут вверх и вправо.
Из этого мы можем сделать вывод, что блокировка и разблокировка мьютекса каждые 50 нс не являются проблемой для моего бокса.
В целомя пришел к выводу, что ответ на вопрос OP заключается в том, что добавление большего числа мьютексов лучше, если это приводит к меньшему количеству конфликтов.
Попробуйте заблокировать мьютексы как можно короче.Единственная причина поместить их, скажем, вне цикла, состоит в том, что этот цикл зацикливается быстрее, чем один раз каждые 100 нс (или, точнее, число потоков, которые хотят запустить этот цикл одновременно, 50 нс) или когда 13 нс разразмер цикла больше задержки, чем задержка, которую вы получаете из-за разногласий.
РЕДАКТИРОВАТЬ: я получил гораздо больше знаний по этому вопросу сейчас и начинаю сомневаться в заключении, которое я представил здесь.Прежде всего, CPU 0 и 1 оказываются гиперпоточными;Несмотря на то, что AMD утверждает, что имеет 8 реальных ядер, безусловно, есть что-то очень сомнительное, потому что задержки между двумя другими ядрами намного больше (то есть 0 и 1 образуют пару, как и 2 и 3, 4 и 5, и 6 и 7).Во-вторых, std :: mutex реализован таким образом, что он немного вращает блокировки перед тем, как фактически выполнять системные вызовы, когда не удается немедленно получить блокировку для мьютекса (что, без сомнения, будет чрезвычайно медленным).Итак, что я измерил здесь, так это абсолютную наиболее идеальную ситуацию, и на практике блокировка и разблокировка могут занять значительно больше времени на блокировку / разблокировку.
Итог, мьютекс реализован с использованием атомики. Чтобы синхронизировать атомы между ядрами, должна быть заблокирована внутренняя шина, которая замораживает соответствующую строку кэша на несколько сотен тактов. В случае, если блокировка не может быть получена, системный вызов должен быть выполнен, чтобы перевести поток в спящий режим; это, очевидно, очень медленно. Обычно это на самом деле не проблема, потому что поток все равно должен спать - но это может быть проблемой с большим конфликтом, когда поток не может получить блокировку в течение времени, когда он обычно вращается, и так же делает системный вызов, но CAN возьмите замок вскоре после этого. Например, если несколько потоков блокируют и разблокируют мьютекс в узком цикле и каждый из них удерживает блокировку в течение 1 микросекунды или около того, то они могут быть сильно замедлены тем фактом, что они постоянно усыпляются и снова просыпаются.