Атомарные операции в GCC
Ну теперь сделай это в нескольких тредах параллельно
Ну теперь сделай это в нескольких тредах параллельноМм... думаешь, действительно будет отличный результат?
Не знаю. Но почти наверняка другой.
ты ещё простой +=5 без локов проверь. pthread может быть жутко хитрым и пока трэдов нет ничего не делать.
ты ещё простой +=5 без локов проверь. pthread может быть жутко хитрым и пока трэдов нет ничего не делать.Угу, об этом я подумал, простой += 5 вообще 0 времени занимает на 100000 циклов, думаю, он сразу просто значение вычисляет нужное и цикл не делает. Вообще, вряд ли pthread такой умный, это ж библиотека, а будут ли треды или нет может только линкер конечного приложения знать, имхо.
Пробовал еще volatile sig_atomic_t делать += 5, он отрабатывает раза в два быстрее, чем код с локом.
Я, кстати, щас вот только заметил, что пример мой линкуется без pthread отличненько, почему-то, что с оптимизацией, что без o_O
Ну теперь сделай это в нескольких тредах параллельноBig thanks, жаль, что эта светлая мысль не пришла мне в голову самому
100 потоков, потоки запускаются по барьеру, в каждом по 100000 операций, время распределяется так:
Атомарные - 000080963
Локовые - 001491423
volatile sig_atomit_t - 000021333
простой int - 000000740
а если 2 треда?
000001550
000007591
000000459
000000007
Могу код кинуть, сам посмотришь.
Подозреваю, что во втором случае у тебя mtx_a из кеша не вылазит, а атомарное сложение, скорее всего, приводит к сбрасыванию значения в память при каждом вызове.
он же с volatile тестил, с ним по идее тоже в память постоянно сбрасывается.
он же с volatile тестил, с ним по идее тоже в память постоянно сбрасывается.ну это, как бы, из первого сообщения это не очевидно. Кроме того, volatile, в отличие от атомарных операций, является инструкцией уровня компилятора и не мешает, например, реорганизации команд в CPU.
с ним по идее тоже в память постоянно сбрасывается.в оперативную? нет конечно. сначала в кеш, потом в соответствии с обычной логикой записи из кеша
Это подразумевается. А что, Interlocked-функции по-другому работают? Точно сразу прямиком в оперативу пишут?
А что, Interlocked-функции по-другому работают?подозреваю что они ещё подразумевают неявный MemoryBarrier, хотя не уверен.
он же с volatile тестил, с ним по идее тоже в память постоянно сбрасывается.В приведенном коде было без volatile. С volatile sig_atomic_t я потом уже тестил, о чем здесь не упоминал.
Пробовал еще volatile sig_atomic_t делать += 5, он отрабатывает раза в два быстрее, чем код с локом.вот, с твоих слов.
Хотя, честно говоря, нафиг нужен volatile я вообще не понимаю. Т.е. я помню из детства, что если переменная может изменяться из других процессов или ядра, то должна быть volatile. А просто в многопоточной программе зачем оно надо я не понимаю.
А просто в многопоточной программе зачем оно надо я не понимаю.Судя по ассемблерному коду, нафиг не сдался.
Дело, например, в следующем:
bool locked = true;
// being ran on the main thread:
void f
{
while (locked) ;
printf( "woo-hoo\n" );
}
// ----------------
// somewhere in another thread:
...
locked = false;
...
так вот цикл while будет до бесконечности: компилятор "видит", что в теле цикла locked не изменяется, поэтому генерирует код, который не обращается к памяти, а запоминает значение locked в регистре и при проверке условия в while обращается только к этому регистру.
добавление volatile к описанию locked исправляет ситуацию.
Не стоит забывать, что volatile не делает операцию ++, например, атомарной.
Как можно понимать, что
если переменная может изменяться из других процессов, она должна быть volatile,
но при этом не понимать, что
если переменная может изменяться из других тредов, она должна быть volatile?
> Судя по ассемблерному коду, нафиг не сдался.
*facepalm*
volatile sigatomic_t x;
...
x+=5;
смотришь на листинг, и понимаешь, что в многопоточном случае нет от него пользы
И что, ты вот так на каком-то одном примере показываешь, что volatile не нужен вообще?
bool locked = true; // still not volatile
bool weReLocked(const volatile bool& y)
{
return y;
}
void f
{
while( weReLocked( locked ) ) ;
printf( "woo-hoo\n" );
}
// the same as above
Сейчас будет сложная конструкция: в след за юзером maloi цитирую цитату КОНТРы:
"narrowness of experience leads to narrowness of imagination"
не вообще, а как замена атомарным операциям при многопоточном доступе к данным
по сравнению с тем же примером без volatile разница, что характерно, есть
справедливости ради отмечу, что студия сейчас компилирует те примеры, что я написал с volatile и без одинаково.
но студия в подобных вопросах не показатель, она вполне может считать себя умнее программиста, и, в принципе, по праву =]
Я видел такой, в котором очень нужен.Дело, например, в следующем:code:Вполне очевидно, что так писать никто не будет. Либо атомарная проверка locked, либо с блокировкой:
bool locked = true;
// being ran on the main thread:
void f
{
while (locked) ;
printf( "woo-hoo\n" );
}
// ----------------
// somewhere in another thread:
...
locked = false;
...
void f
{
while (true)
{
ScopedLock(Mutex);
if (locked)
break;
};
printf( "woo-hoo\n" );
}
Я надеюсь, в этом случае volatile не нужен? Иначе мне сегодня же придется выкинуть много своего потокобезопасного кода.
Про верхний код зря так говоришь, там синхронизация не очень нужна. Вполне такое может быть.
Вообще советую статью http://www.ddj.com/cpp/184403766
ScopedLock(Mutex);что такое ScopedLock? Надеюсь не class ScopedLock =]
Вполне очевидно, что так писать никто не будет. Либо атомарная проверка locked, либо с блокировкой:Я не эксперт по стандартам, но вроде бы, так как locked объявлена вне функции, а ScopedLock вызывает разные штуки, то оптимизатор допускает возможность того, что locked меняется через алиас.
void f
{
while (true)
{
ScopedLock(Mutex);
if (locked)
break;
};
printf( "woo-hoo\n" );
}
стандарт про многопоточность скромно умалчивает.
зато кое-что говорит о предположениях оптимизатора
что такое ScopedLock?Этим загадочным словом я обозначил штуку, которая в конструкторе лочит мьютекс, а в деструкторе анлочит. Конечно, там можно было бы и сложнее без классов написать.
Ну я к тому, что в этом случае писать ScopedLock( mutex ) бессмысленно. Это часто используемая штука, но локальную переменную все-таки стоит объявить, а то так он у тебя разлачивается сразу же (временный объект).
да, туплю, говорит.
Ну я к тому, что в этом случае писать ScopedLock( mutex ) бессмысленно. Это часто используемая штука, но локальную переменную все-таки стоит объявить, а то так он у тебя разлачивается сразу же (временный объект).Да, был не прав. Кстати, интересная статья, что ты дал, не знал про volatile функции.
Да, я когда на нее наткнулся, тоже не знал. Это очень круто, жаль, что не все этим заморачиваются.
Я не эксперт по стандартам, но вроде бы, так как locked объявлена вне функции, а ScopedLock вызывает разные штуки, то оптимизатор допускает возможность того, что locked меняется через алиас.s/объявлена вне функции/объявлена в глобальной области видимости/
Иначе неправда: если locked — член класса, у которого не берётся адрес, то ScopedLock никак не может до него добраться.
Оставить комментарий
erotic
День добрый.Кто-нибудь может объяснить, почему атомарные операции в GCC работают медленнее, чем блокировки? Конкретно пример:
Получается, что атомарная операция выполняется в полтора примерно раза медленнее, чем работа с мьютексами. Тестировал на Intel Core2Duo как на 64-битной (GCC 4.3.2 там 32-битной (GCC 4.3.3) системах, собирал с -O3 -march=core2.
В ассемблерном коде в первом цикле есть инструкция lock addl, во втором цикле вызовы к pthread_mutex_lock и pthread_mutex_unlock, между которыми инструкция addl.
Ставил библиотеку кроссплатформенную libatomic_ops, особо в ней не разбирался, но результат по времени она выдает такой же, как и __sync_fetch_and_add, может быть через него она и сделана.