Параллельная очередь строительных блоков Intel: использование pop () вместо pop_if_present () - PullRequest
4 голосов
/ 18 февраля 2010

Какая разница в использовании блокирующего вызова pop() по сравнению с

while(pop_if_present(...)) 

Что должно быть предпочтительнее другого? И почему?

Я ищу более глубокое понимание компромисса между опросом себя, как в случае while(pop_if_present(...)), в отношении того, чтобы система сделала это за вас. Это довольно общая тема. Например, с boost::asio я мог бы сделать myIO.run(), который блокирует или сделать следующее:

while(1) 
{
myIO.poll()
}

Одним из возможных объяснений является то, что поток, который вызывает while(pop_if_present(...)), останется занятым, так что это плохо. Но кто-то или что-то должен опросить асинхронное событие. Почему и как это может быть дешевле, если оно делегировано ОС или библиотеке? Это потому, что операционная система или библиотека умно опрашивают, например, экспоненциальный откат?

Ответы [ 2 ]

3 голосов
/ 21 февраля 2010

Библиотека Intel TBB с открытым исходным кодом, поэтому я посмотрел ...

Похоже, pop_if_present(), по сути, проверяет, пуста ли очередь, и немедленно возвращает, если она есть. Если нет, он пытается получить элемент в верхней части очереди (что может привести к сбою, так как другой поток мог прийти и забрать его). Если он пропускает, он выполняет паузу "atomic_backoff" перед повторной проверкой. atomic_backoff будет просто вращаться первые несколько раз, как он вызывается (удваивая количество циклов вращения каждый раз), но после определенного количества пауз он просто уступит планировщику ОС вместо того, чтобы вращаться в предположении, что, поскольку он ждет какое-то время он мог бы сделать это хорошо.

Для простой функции pop(), если в очереди ничего нет, atomic_backoff ожидает, пока в очереди что-то получится.

Обратите внимание, что есть по крайней мере 2 интересные вещи (для меня в любом случае) об этом:

  • функция pop() выполняет ожидание вращения (с точностью до точки), чтобы что-то появилось в очереди; она не уступит ОС, если не будет ждать более короткого момента. Так что, как и следовало ожидать, нет особой причины крутить себя, звоня по номеру pop_if_present(), если только у вас нет чего-то еще, что вы собираетесь делать между вызовами pop_if_present()

  • , когда pop() уступает ОС, он просто отказывается от своего временного интервала. Он не блокирует поток в объекте синхронизации, о котором можно сигнализировать, когда элемент помещается в очередь, - он, похоже, входит в цикл ожидания / опроса, чтобы проверить в очереди что-то всплывающее. Это меня немного удивило.

Возьмите этот анализ с небольшим количеством соли ... Источник, который я использовал для этого анализа, может быть немного старым (на самом деле это concurrent_queue_v2.h и .cpp), потому что более поздний concurrent_queue имеет другой API - нет pop() или pop_if_present(), просто функция try_pop() в новейшем интерфейсе class concurrent_queue. Старый интерфейс был перемещен (возможно, несколько изменен) в класс concurrent_bounded_queue. Похоже, что более новые concurrent_queues можно настроить, когда библиотека построена для использования объектов синхронизации ОС вместо занятых ожиданий и опросов.

2 голосов
/ 20 февраля 2010

С while(pop_if_present(...)) вы делаете грубую силу занятое ожидание (также называемое вращением) в очереди. Когда очередь пуста, вы теряете циклы, занимая ЦП, пока один из элементов, запущенных на другом ЦП, не будет помещен в очередь другим элементом или ОС решит передать ЦП другому, возможно, не связанному потоку / процессу.

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

При наличии нескольких процессоров это может быть лучше, если ОС выбирает (или принудительно применяет) поток производителя для запуска на другом процессоре. Это основная идея spin-lock - примитива синхронизации, построенного непосредственно на специальных инструкциях процессора, таких как сравнение-и-замена или условное связывание нагрузки / сохранение и обычно используется внутри операционной системы для связи между обработчиками прерываний и остальной частью ядра, а также для создания конструкций более высокого уровня, таких как семафоры .

С блокировкой pop(), если очередь пуста, вы вводите sleep wait , т.е. просите ОС перевести поток потребителя в состояние non-schedulable до события - нажать на очередь - происходит из другого потока. Ключевым моментом здесь является то, что процессор доступен для другой (надеюсь полезной) работы. Реализация TBB на самом деле изо всех сил пытается избежать сна, поскольку это дорого (вход в ядро, перепланирование и т. Д.). Цель состоит в том, чтобы оптимизировать обычный случай, когда очередь не пуста и элемент может быть быстро получен.

Хотя выбор действительно прост - всегда в режиме ожидания , т. Е. Блокируйте pop(), если вам не нужно занято-ожиданием (и это в системах реального времени , Контекст прерывания ОС и некоторые очень специализированные приложения.)

Надеюсь, это немного поможет.

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