Правильный ли подход барьеров? - PullRequest
9 голосов
/ 26 января 2012

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

EDIT

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

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

Ответы [ 3 ]

8 голосов
/ 26 января 2012

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

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

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

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

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

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

3 голосов
/ 26 января 2012

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

Хотя есть способ сделать быстрые барьеры, используя счет событий (посмотрите на это через 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);
    }
}

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

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

2 голосов
/ 26 января 2012

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

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

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

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

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