Атомарные операции в GCC

erotic

День добрый.
Кто-нибудь может объяснить, почему атомарные операции в GCC работают медленнее, чем блокировки? Конкретно пример:
 
gettimeofday(&tm1, 0);

for (i = 0; i < sCount; ++i)
{
__sync_fetch_and_add(&ato_a, 5);
}

gettimeofday(&tm2, 0);

for (i = 0; i < sCount; ++i)
{
pthread_mutex_lock(&mutex);
mtx_a += 5;
pthread_mutex_unlock(&mutex);
}

gettimeofday(&tm3, 0);


Получается, что атомарная операция выполняется в полтора примерно раза медленнее, чем работа с мьютексами. Тестировал на 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, может быть через него она и сделана.

Marinavo_0507

Ну теперь сделай это в нескольких тредах параллельно :)

erotic

Ну теперь сделай это в нескольких тредах параллельно
Мм... думаешь, действительно будет отличный результат?

Marinavo_0507

Не знаю. Но почти наверняка другой.

vall

ты ещё простой +=5 без локов проверь. pthread может быть жутко хитрым и пока трэдов нет ничего не делать.

erotic

ты ещё простой +=5 без локов проверь. pthread может быть жутко хитрым и пока трэдов нет ничего не делать.
Угу, об этом я подумал, простой += 5 вообще 0 времени занимает на 100000 циклов, думаю, он сразу просто значение вычисляет нужное и цикл не делает. Вообще, вряд ли pthread такой умный, это ж библиотека, а будут ли треды или нет может только линкер конечного приложения знать, имхо.
Пробовал еще volatile sig_atomic_t делать += 5, он отрабатывает раза в два быстрее, чем код с локом.

erotic

Я, кстати, щас вот только заметил, что пример мой линкуется без pthread отличненько, почему-то, что с оптимизацией, что без o_O

erotic

Ну теперь сделай это в нескольких тредах параллельно
Big thanks, жаль, что эта светлая мысль не пришла мне в голову самому :(
100 потоков, потоки запускаются по барьеру, в каждом по 100000 операций, время распределяется так:
 
Атомарные - 000080963
Локовые - 001491423
volatile sig_atomit_t - 000021333
простой int - 000000740

Marinavo_0507

а если 2 треда?

erotic

 

000001550
000007591
000000459
000000007

Могу код кинуть, сам посмотришь.

Andbar

Подозреваю, что во втором случае у тебя mtx_a из кеша не вылазит, а атомарное сложение, скорее всего, приводит к сбрасыванию значения в память при каждом вызове.

Serab

он же с volatile тестил, с ним по идее тоже в память постоянно сбрасывается.

Andbar

он же с volatile тестил, с ним по идее тоже в память постоянно сбрасывается.
ну это, как бы, из первого сообщения это не очевидно. Кроме того, volatile, в отличие от атомарных операций, является инструкцией уровня компилятора и не мешает, например, реорганизации команд в CPU.

procenkotanya

с ним по идее тоже в память постоянно сбрасывается.
в оперативную? нет конечно. сначала в кеш, потом в соответствии с обычной логикой записи из кеша

Serab

Это подразумевается. А что, Interlocked-функции по-другому работают? Точно сразу прямиком в оперативу пишут?

Andbar

А что, Interlocked-функции по-другому работают?
подозреваю что они ещё подразумевают неявный MemoryBarrier, хотя не уверен.

erotic

он же с volatile тестил, с ним по идее тоже в память постоянно сбрасывается.
В приведенном коде было без volatile. С volatile sig_atomic_t я потом уже тестил, о чем здесь не упоминал.

Serab

Пробовал еще volatile sig_atomic_t делать += 5, он отрабатывает раза в два быстрее, чем код с локом.
вот, с твоих слов.

erotic

отвечал на сообщение, в котором я про volatile еще не писал.
Хотя, честно говоря, нафиг нужен volatile я вообще не понимаю. Т.е. я помню из детства, что если переменная может изменяться из других процессов или ядра, то должна быть volatile. А просто в многопоточной программе зачем оно надо я не понимаю.

Marinavo_0507

А просто в многопоточной программе зачем оно надо я не понимаю.
Судя по ассемблерному коду, нафиг не сдался.

Serab

это по какому? Я видел такой, в котором очень нужен.
Дело, например, в следующем:

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 не делает операцию ++, например, атомарной.

procenkotanya

>> А просто в многопоточной программе зачем оно надо я не понимаю.
Как можно понимать, что
если переменная может изменяться из других процессов, она должна быть volatile,
но при этом не понимать, что
если переменная может изменяться из других тредов, она должна быть volatile?
> Судя по ассемблерному коду, нафиг не сдался.
*facepalm*

Marinavo_0507

ну просто
volatile sigatomic_t x;
...
x+=5;
смотришь на листинг, и понимаешь, что в многопоточном случае нет от него пользы

procenkotanya

И что, ты вот так на каком-то одном примере показываешь, что volatile не нужен вообще?

Serab

еще стоит понимать следующее: volatile — модификатор типа, аналогичный const, поэтому тот код можно исправить и следующим образом:


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

Serab

на этом примере разницы и не будет.
Сейчас будет сложная конструкция: в след за юзером maloi цитирую цитату КОНТРы:
"narrowness of experience leads to narrowness of imagination"

Marinavo_0507

не вообще, а как замена атомарным операциям при многопоточном доступе к данным

Marinavo_0507

> на этом примере разницы и не будет.
по сравнению с тем же примером без volatile разница, что характерно, есть

Serab

справедливости ради отмечу, что студия сейчас компилирует те примеры, что я написал с volatile и без одинаково.

Serab

но студия в подобных вопросах не показатель, она вполне может считать себя умнее программиста, и, в принципе, по праву =]

erotic

Я видел такой, в котором очень нужен.Дело, например, в следующем:code:
bool locked = true;
// being ran on the main thread:
void f
{
while (locked) ;
printf( "woo-hoo\n" );
}
// ----------------
// somewhere in another thread:
...
locked = false;
...
Вполне очевидно, что так писать никто не будет. Либо атомарная проверка locked, либо с блокировкой:
 
void f
{
while (true)
{
ScopedLock(Mutex);
if (locked)
break;
};
printf( "woo-hoo\n" );
}

Я надеюсь, в этом случае volatile не нужен? Иначе мне сегодня же придется выкинуть много своего потокобезопасного кода.

Serab

при единичном обращении не нужен походу.
Про верхний код зря так говоришь, там синхронизация не очень нужна. Вполне такое может быть.
Вообще советую статью http://www.ddj.com/cpp/184403766

Serab

ScopedLock(Mutex);
что такое ScopedLock? Надеюсь не class ScopedLock =]

Marinavo_0507

Вполне очевидно, что так писать никто не будет. Либо атомарная проверка locked, либо с блокировкой:


void f
{
while (true)
{
ScopedLock(Mutex);
if (locked)
break;
};
printf( "woo-hoo\n" );
}
Я не эксперт по стандартам, но вроде бы, так как locked объявлена вне функции, а ScopedLock вызывает разные штуки, то оптимизатор допускает возможность того, что locked меняется через алиас.

Serab

стандарт про многопоточность скромно умалчивает.

Marinavo_0507

зато кое-что говорит о предположениях оптимизатора

erotic

что такое ScopedLock?
Этим загадочным словом я обозначил штуку, которая в конструкторе лочит мьютекс, а в деструкторе анлочит. Конечно, там можно было бы и сложнее без классов написать.

Serab

Ну я к тому, что в этом случае писать ScopedLock( mutex ) бессмысленно. Это часто используемая штука, но локальную переменную все-таки стоит объявить, а то так он у тебя разлачивается сразу же (временный объект).

Serab

да, туплю, говорит.

erotic

Ну я к тому, что в этом случае писать ScopedLock( mutex ) бессмысленно. Это часто используемая штука, но локальную переменную все-таки стоит объявить, а то так он у тебя разлачивается сразу же (временный объект).
Да, был не прав. Кстати, интересная статья, что ты дал, не знал про volatile функции.

Serab

Да, я когда на нее наткнулся, тоже не знал. Это очень круто, жаль, что не все этим заморачиваются.

procenkotanya

Я не эксперт по стандартам, но вроде бы, так как locked объявлена вне функции, а ScopedLock вызывает разные штуки, то оптимизатор допускает возможность того, что locked меняется через алиас.
s/объявлена вне функции/объявлена в глобальной области видимости/
Иначе неправда: если locked — член класса, у которого не берётся адрес, то ScopedLock никак не может до него добраться.
Оставить комментарий
Имя или ник:
Комментарий: