Барьер прядильной нити с использованием атомных встроенных модулей

Я пытаюсь реализовать барьер вращающегося потока с помощью атомики, в частности __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 и т. Д.). Я видел здесь реализацию: Написание (вращающегося) барьера потока с использованием атомики С ++ 11, однако это кажется слишком сложным, и я думаю, что его производительность может быть хуже, чем барьер pthread.

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

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


person mas    schedule 08.11.2015    source источник


Ответы (1)


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

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, поскольку он может использовать барьеры памяти получить / release вместо полного, который является единственным барьер доступен из __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);
    }
}
person Tsyvarev    schedule 09.11.2015