Динамическое и управляемое планирование OpenMP
я изучаю планирование OpenMP и, в частности, различные типы. Я понимаю общее поведение каждого типа, но уточнение было бы полезно в отношении того, когда выбирать между dynamic
и guided
планирование.
документы Intel описания dynamic
планирование:
используйте внутреннюю рабочую очередь, чтобы дать блок размером с кусок цикла итерации для каждого потока. Когда поток завершен, он извлекает следующий блок циклические итерации из верхней части рабочей очереди. От по умолчанию, размер блока 1. Будьте осторожны при использовании этого планирования тип из-за дополнительных накладных расходов.
он также описывает guided
планирование:
подобно динамическому планированию, но размер куска начинается с большого и уменьшается, чтобы лучше обрабатывать дисбаланс нагрузки между итерациями. Этот необязательный параметр chunk указывает минимальный размер используемого фрагмента. От по умолчанию блок размер приблизительно loop_count / number_of_threads.
С guided
планирование динамически уменьшает размер куска во время выполнения, зачем мне когда-либо использовать dynamic
планирование?
я исследовал этот вопрос и нашел эту таблицу из Дартмута:
guided
указана как high
накладные расходы, в то время как dynamic
имеет средние издержки.
Это изначально имело смысл, но при дальнейшем исследовании я прочитайте статью Intel по теме. Из предыдущей таблицы я предположил guided
планирование займет больше времени из-за анализа и корректировки размера блока при запуске (даже при правильном использовании). Однако, в статье Intel говорится:
управляемые графики лучше всего работают с небольшими размерами куска в качестве их предела; это дает большую гибкость. Непонятно, почему они хуже большие размеры куска, но они могут занять слишком много времени, когда ограничены большими размеры порций.
почему размер куска относится к guided
больше времени, чем dynamic
? Это имело бы смысл для отсутствия "гибкости", чтобы вызвать потерю производительности путем блокировки размера куска слишком высоко. Однако я бы не назвал это "накладными расходами", и проблема блокировки дискредитировала бы предыдущую теорию.
наконец, это указано в статье:
динамические графики дают большая гибкость, но возьмите самый большой производительность при неправильном расписании.
имеет смысл для dynamic
планирование должно быть более оптимальным, чем static
, но почему это более оптимально, чем guided
? Я спрашиваю только о накладных расходах?
этой несколько связанных так пост объясняет NUMA, связанные с типами планирования. Это не имеет отношения к этому вопросу, так как организация проиграла "первым пришел, первым обслужен" поведение этих типов планирования.
dynamic
планирование может быть коалесцентным, вызывая повышение производительности, но тогда то же гипотетическое должно применяться к guided
.
вот время каждого типа планирования для разных размеров куска из статьи Intel для справки. Это только записи из одной программы, и некоторые правила применяются по-разному для каждой программы и машины( особенно при планировании), но они должны обеспечивать общее тенденции.
редактировать (ядро моего вопроса):
- что влияет на время выполнения
guided
планирование? Конкретные примеры? Почему это медленнее, чемdynamic
в некоторых случаях? - когда бы я предпочел
guided
надdynamic
или наоборот? - после того, как это было объяснено, источники выше поддерживают ваше объяснение? Противоречат ли они вообще?
1 ответов
что влияет на время выполнения управляемого планирования?
есть три эффекта, чтобы рассмотреть:
1. Баланс нагрузки
весь смысл динамического/управляемого планирования заключается в улучшении распределения работы в случае, когда не каждая итерация цикла содержит такой же объем работы. Принципиально:
-
schedule(dynamic, 1)
предоставляет оптимальная балансировка нагрузки -
dynamic, k
всегда есть или балансировки нагрузки, чемguided, k
стандартные мандаты, что размер каждого куска пропорционально к числу неназначенных итераций, деленному на количество потоков в команде,
уменьшается до k
.
на GCC OpenMP implemntation воспринимать это буквально, игнорируя пропорционально. Например, для 4 потоков,k=1
, это будет 32 итерации как 8, 6, 5, 4, 3, 2, 1, 1, 1, 1
. Сейчас ИМХО это действительно глупо: это приводит к плохому дисбалансу нагрузки, если первые 1 / n итераций содержат более 1 / n работы.
конкретные примеры? Почему в некоторых случаях он медленнее, чем динамический?
Ok, давайте посмотрим на тривиальный пример, где внутренняя работа уменьшается с итерацией цикла:
#include <omp.h>
void work(long ww) {
volatile long sum = 0;
for (long w = 0; w < ww; w++) sum += w;
}
int main() {
const long max = 32, factor = 10000000l;
#pragma omp parallel for schedule(guided, 1)
for (int i = 0; i < max; i++) {
work((max - i) * factor);
}
}
казнь выглядит так1:
как вы вижу, guided
здесь очень плохо. guided
сделает гораздо лучше для различных видов распределений работы. Также возможно осуществить направленный по-разному. Реализация в clang (который IIRC происходит от Intel), является гораздо более сложные. Я действительно не понимаю идею наивной реализации GCC. В моих глазах это эффективно побеждает цель динамической нагрузки, если вы даете1/n
работа с первым потоком.
2. Накладные расходы
теперь каждый динамический фрагмент имеет некоторое влияние на производительность из-за доступа к общему состоянию. Издержки guided
будет немного выше за кусок, чем dynamic
, так как есть немного больше вычислений. Однако,guided, k
будет иметь меньше общих динамических кусков, чем dynamic, k
.
накладные расходы также будут зависеть от реализации, например, использует ли он atomics или locks для защиты общего состояния.
3. Кэш и NUMA-эффекты
скажем, напишите вектор целых чисел в итерации цикла. Если бы каждая вторая итерация выполнялась другим потоком, каждый второй элемент вектора был бы записан другим ядром. Это очень плохо, потому что, делая это, они конкурируют с кэш-линиями, которые содержат соседние элементы (ложный общий доступ). Если у вас есть небольшие размеры кусков и/или размеры кусков, которые не совпадают с кэшами, вы получаете плохую производительность по "краям" кусков. Вот почему вы обычно предпочитаете большие Ницца (2^n
) размеров блока. guided
может дать вам большие размеры куска в среднем, но не 2^n
(или k*m
).
ответ (на который вы уже ссылались), подробно обсуждает недостаток динамического/управляемого планирования с точки зрения NUMA, но это также относится к локальности/кэшам.
не думаю, мера
учитывая различные факторы и трудности для прогнозирования специфики, я могу только рекомендовать измерьте ваше конкретное приложение в вашей конкретной системе, в вашей конкретной конфигурации с помощью вашего конкретного компилятора. К сожалению, нет идеальной переносимости производительности. Я лично утверждаю, что это особенно верно для guided
.
когда бы я предпочел руководствоваться динамикой или наоборот?
если у вас есть конкретные знания о накладных расходах / работе за итерацию, я бы сказал, что dynamic, k
дает наиболее стабильные результаты выбирая хорошего k
. В частности, вы не так сильно зависите от того, насколько умна реализация.
С другой стороны, guided
может быть хорошей первой догадкой, с разумным коэффициентом балансировки накладных расходов / нагрузки, по крайней мере, для умной реализации. Будьте особенно осторожны с guided
если вы знаете, что более поздние времена итерации короче.
имейте в виду, там тоже schedule(auto)
, что дает полный контроль над компилятором / средой выполнения и schedule(runtime)
, что позволяет вы должны выбрать политику планирования во время выполнения.
после того, как это было объяснено, источники выше поддерживают ваше объяснение? Противоречат ли они вообще?
возьмите источники, включая этот Ансер, с солью. Ни диаграмма, которую вы опубликовали, ни моя временная шкала не являются научно точными цифрами. Существует огромная разница в результатах, и нет никаких баров ошибок, они, вероятно, просто будут повсюду с этими очень несколько точек данных. Также диаграмма сочетает в себе несколько эффектов, которые я упомянул, не раскрывая Work
код.
[из Intel docs]
по умолчанию размер куска приблизительно loop_count / number_of_threads.
это противоречит моему наблюдению, что icc обрабатывает мой маленький пример намного лучше.
1: Используя GCC 6.3.1, Score-P / Vampir для визуализации, две чередующиеся рабочие функции для раскраска.