Спин-блокировка должна иметь лучшую производительность, чем мьютекс для простых задач. Однако в этом простом тесте (8 потоков увеличивают счетчик) результаты отображаются по-разному:
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <vector>
using namespace std;
class SpinLock {
private:
atomic_flag lck = ATOMIC_FLAG_INIT;
public:
void lock() { while(lck.test_and_set(memory_order_acquire)) {} }
void unlock() { lck.clear(memory_order_release); }
};
int total = 0;
#ifdef SPINLOCK
SpinLock my_lock;
#else
mutex my_lock;
#endif
void foo(int n)
{
for(int i = 0; i < 10000000; ++i) {
#ifdef SPINLOCK
lock_guard<SpinLock> lck(my_lock);
#else
lock_guard<mutex> lck(my_lock);
#endif
++total;
}
}
int main()
{
vector<thread> v;
for(int i = 0; i < 8; ++i)
v.emplace_back(foo, i);
for(auto& t : v)
t.join();
cout << "total: " << total << endl;
return 0;
}
Чтобы проверить спин-блокировку:
$ g++ -DSPINLOCK -std=c++11 -Wall -pthread test.cc
$ time ./a.out
total: 80000000
real 0m18.206s
user 2m17.792s
sys 0m0.003s
Чтобы проверить мьютекс:
$ g++ -std=c++11 -Wall -pthread test.cc
$ time ./a.out
total: 80000000
real 0m9.483s
user 0m6.451s
sys 1m6.043s
Результаты показывают, что мьютекс почти в два раза быстрее, чем спин-блокировка. Вращающаяся блокировка проводит большую часть времени в «пользовательском процессоре», а мьютекс проводит больше всего времени в «системном процессоре». Как реализован мьютекс и нужно ли использовать мьютекс вместо спиновой блокировки в простых вычислениях, подобных этому? Кто-нибудь может объяснить результаты?
G ++ — 4.8.2, а ОС — Red Hat Enterprise Linux 7.
Благодарю.
некоторые заметки:
время показано в time
вывод утилиты — это время процессора, которое используют ваши потоки, а не фактическое время. Spinlock использует процессор даже во время ожидания, в то время как мьютексы ядра будут выполнять другие потоки в других процессах во время ожидания, не выставляя счет вашему процессору за это время процессора, за исключением того, которое используется для фактического планирования (то, которое вы видите в строке sys в случае мьютекса) ).
по той же причине, указанной выше, может быть так, что общее время ожидания от начала процесса до конца в случае спин-блокировки быстрее, однако ваш процессор может иметь более высокую загрузку, что вы и наблюдали.
Многопоточность может быть хорошим выбором, если у вас низкий шанс столкновения, то есть время, которое поток проводит в синхронизированной нагрузке, мало по сравнению с нагрузкой, которую он может выполнять асинхронно. Если вся ваша нагрузка защищена мьютексом, то использование потоков вообще накладно, и вам, вероятно, следует сериализовать его.
Спинлок удобен, если у вас низкий шанс столкновения и малое время ожидания в случае столкновения. В вашем случае 8 потоков сталкиваются с одним и тем же ресурсом, а затем требуют, чтобы ресурс был доступен сразу же после его освобождения. Это означает, что в среднем будет работать 1 поток и 7 других спин-блокировок, в результате чего общее время процессора будет использовано в 8 раз больше времени, необходимого для одного потока (если у вас 8-ядерный компьютер и на нем нет другой нагрузки). В случае мьютекса поток приостанавливается и просыпается только тогда, когда ресурс доступен, так что нет никаких накладных расходов на ожидание, однако блокировка мьютекса потребует некоторых накладных расходов, так как работа в ядре отслеживает, какие процессы ожидают мьютекс и даже если он не слишком велик, вы выполняете 160 миллионов операций мьютекса, что составляет sys
время, затраченное на ваш процесс