Правильно ли такой подход к барьерам?
Я нашел это 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 ответов
ваша реализация барьера не работает, по крайней мере, если барьер будет использоваться более одного раза. Рассмотрим этот случай:
-
NUM_OF_THREADS-1
нити ждут у барьера, крутятся. - последний поток прибывает и проходит через барьер.
- последний поток выходит из барьера, продолжает обработку, завершает свою следующую задачу и повторно запускает ожидание барьера.
- только теперь другие потоки ожидания получают расписание, и они не могут выйти из барьер, потому что счетчик был увеличен снова. Тупик.
кроме того, одна часто упускаемая, но неприятная проблема, связанная с использованием динамически распределенных барьеров, разрушает/освобождает их. Вы хотели бы, чтобы любой из потоков мог выполнять уничтожение / бесплатно после возвращения барьера, пока вы знаете, что никто не будет пытаться ждать его снова, но для этого требуется убедиться все официанты закончили касаться памяти в объекте барьера до официанты 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)
случай, за исключением уступки)