Барьер из прядильной нити с использованием Atomic Builtins

Я пытаюсь реализовать барьер вращающегося потока, используя атомику, в частности, __sync_fetch_and_add. https://gcc.gnu.org/onlinedocs/gcc-4.4.5/gcc/Atomic-Builtins.html

Я в основном хочу альтернативу барьеру pthread. Я использую Ubuntu в системе, которая может работать около сотни потоков параллельно.

int bar = 0;                      //global variable
int P = MAX_THREADS;              //number of threads

__sync_fetch_and_add(&bar,1);     //each thread comes and adds atomically
while(bar<P){}                    //threads spin until bar increments to P
bar=0;                            //a thread sets bar=0 to be used in the next spinning barrier

Это не работает по очевидным причинам (поток может установить bar = 0, а другой поток застрянет в бесконечном цикле while и т. Д.). Я видел реализацию здесь: написание (вращающегося) барьера для потоков с использованием атомарности c ++ 11, однако это кажется слишком сложным, и я думаю, что его производительность может быть хуже, чем барьер pthread.

Ожидается, что эта реализация также будет генерировать больше трафика в иерархии памяти из-за того, что строка кэша бара пинг-пинг-понг между потоками.

Любые идеи о том, как использовать эти атомарные инструкции, чтобы сделать простой барьер? Оптимальная коммуникационная схема также была бы полезна дополнительно.

0

Решение

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

int P = MAX_THREADS;
int bar = 0; // Counter of threads, faced barrier.
volatile int passed = 0; // Number of barriers, passed by all threads.

void barrier_wait()
{
int passed_old = passed; // Should be evaluated before incrementing *bar*!

if(__sync_fetch_and_add(&bar,1) == (P - 1))
{
// The last thread, faced barrier.
bar = 0;
// *bar* should be reseted strictly before updating of barriers counter.
__sync_synchronize();
passed++; // Mark barrier as passed.
}
else
{
// Not the last thread. Wait others.
while(passed == passed_old) {};
// Need to synchronize cache with other threads, passed barrier.
__sync_synchronize();
}
}

Обратите внимание, что вам нужно использовать volatile модификатор для вращающейся переменной.

C ++ код может быть несколько быстрее, чем C один, так как он может использовать приобретать/релиз барьеры памяти вместо полный один, который является единственным барьером, доступным из __sync функции:

int P = MAX_THREADS;
std::atomic<int> bar = 0; // Counter of threads, faced barrier.
std::atomic<int> passed = 0; // Number of barriers, passed by all threads.

void barrier_wait()
{
int passed_old = passed.load(std::memory_order_relaxed);

if(bar.fetch_add(1) == (P - 1))
{
// The last thread, faced barrier.
bar = 0;
// Synchronize and store in one operation.
passed.store(passed_old + 1, std::memory_order_release);
}
else
{
// Not the last thread. Wait others.
while(passed.load(std::memory_order_relaxed) == passed_old) {};
// Need to synchronize cache with other threads, passed barrier.
std::atomic_thread_fence(std::memory_order_acquire);
}
}
1

Другие решения

Других решений пока нет …