Правильно ли такой подход к барьерам?

Я нашел это pthread_barrier_wait довольно медленно, поэтому на одном месте в моем коде Я заменил pthread_barrier_wait С моей версией барьер (my_barrier), который использует атомарную переменную. Я обнаружил, что это намного быстрее, чем pthread_barrier_wait. Есть ли какой-либо недостаток в использовании этого подхода? Правильно ли это? Кроме того, я не знаю, почему это быстрее, чем pthread_barrier_wait? Любой ключ?

редактировать

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

    atomic<int> thread_count = 0;
    
    void my_barrier()
    {     
      thread_count++;
    
      while( thread_count % NUM_OF_THREADS )
       sched_yield();
    }
    

3 ответов


ваша реализация барьера не работает, по крайней мере, если барьер будет использоваться более одного раза. Рассмотрим этот случай:

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

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

как барьеры могут быть разрушены, как только pthread_barrier_wait возвращается?

может ли правильный отказоустойчивый процесс-общий барьер быть реализован в Linux?

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


AFAICT это правильно, и похоже, что это быстрее, но в высоком спорном случае это будет намного хуже. Высокий оспариваемый случай, когда у вас много потоков, намного больше, чем процессоры.

есть способ сделать быстрые барьеры, хотя, используя eventcounts (посмотрите на него через google).

struct barrier {
    atomic<int>       count;
    struct eventcount ec;
};

void my_barrier_wait(struct barrier *b)
{
    eventcount_key_t key;

    if (--b->count == 0) {
        eventcount_broadcast(&b->ec);
        return;
    }
    for (;;) {
        key = eventcount_get(&b->ec);
        if (!b->count)
            return;
        eventcount_wait(&b->ec);
    }
}

это должно масштабироваться лучше.

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


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

теперь к тому, почему это быстрее: я не совсем уверен, но я думаю pthread_barrier_wait позволит потоку спать, пока не придет время просыпаться. Твое вращаясь по условию, уступая в каждой итерации. Однако, если нет другого приложения / потока, которому требуется время обработки, поток, вероятно, будет запланирован снова сразу после yield, поэтому время ожидания будет короче. По крайней мере, это то, что игра с такого рода барьерами, казалось, указывала на мою систему.

в качестве примечания: так как вы используете atomic<int> Я предполагаю, что вы используете C++11. Разве не имеет смысла использовать std::this_thread::yield() вместо sched_yield() в этом случае для удаления зависимость от компиляции?

этой ссылке может также быть интресстинг для вас, он измеряет производительность различных реализаций барьера (ваш грубо lock xadd+while(i<NCPU) случай, за исключением уступки)