Пока у меня есть хороший спинлок, который работает как намерение:
std::atomic_flag barrier = ATOMIC_FLAG_INIT;
inline void lock( ){
while( barrier
.test_and_set( std::memory_order_acquire ) )
{}
}
Однако я хочу знать (ориентировочно), сколько циклов ЦП тратится в нем (если ожидание занятости слишком велико, возможно, я рассмотрю мьютекс, который по крайней мере переводит ожидающие потоки в спящий режим):
inline void lock( int & waitCounter){
while( barrier
.test_and_set( std::memory_order_acquire ) )
waitCounter++;
}
Конечно, это не учитывает саму инструкцию блокировки, поэтому на какую константу я должен увеличивать waitCounter, чтобы получить точное представление о циклах, проведенных в занятом ожидании (я считаю, что инструкции не будут переданы по конвейеру из-за барьера памяти, поэтому счет довольно точный теоретически)?
waitCounter+=2;
waitCounter+=3;
waitCounter+=4; //...
Количество циклов, требуемых спин-блокировкой, зависит от ряда факторов, включая количество потоков, пытающихся выполнить спин-блокировку одновременно.
Я недавно проверил это, Вот.
Короткий ответ: он может сильно различаться из-за того, что вы можете контролировать напрямую (код приложения), а чего нет (конфликт из-за шины). Соотношение между наименьшим числом циклов и наибольшим может составлять от 110 до 950 или больше.
По крайней мере на GCC с -O4 кажется функция
inline void lock( int & waitCounter){
while( barrier
.test_and_set( std::memory_order_acquire ) )
waitCounter+=5;
waitCounter+=2;
сводится к коду, который ведет подсчет инструкций, которые сам использует
.L5:
add DWORD PTR [rdi], 5
.L3:
mov eax, edx
xchg al, BYTE PTR barrier[rip]
test al, al
jne .L5
add DWORD PTR [rdi], 2
ret
Это далеко не полный ответ, но может дать идею.