Сводка директив OpenMP для Intel C++

Страницы работы

Содержание работы

Сводка директив OpenMP* для Intel C++
Соколенко П.Т., Россия, Ростов-на-Дону

OpenMP создавался как средство для программирования параллельных вычислений, выполняемых в многопроцессорных системах с общим пространством памяти (SMP). Его основную часть составляет набор директив, включаемых в тексты программ, написанных на обычных алгоритмических языках. Первая версия стандарта для языков C/C++ появилась в октябре 1998 г., а вторая, и пока последняя - в марте 2002 г., ее полное описание вы найдете на сайте www.openmp.org.

В связи с выпуском микропроцессора Pentium 4 HT, поддерживающего технологию Hyper Threading, корпорация Intel разработала реализацию стандарта OpenMP для компиляторов Intel C++. Она позволяет программировать параллельные вычисления, на двух логических процессорах, входящих в состав Pentium 4 HT и последних моделей Pentium 4 с новым ядром Prescott.

Список директив, поддерживаемых реализацией Intel, описан в документе "OpenMP* Standard Option". Судя по этому документу при работе с Intel C++ можно использовать стандартный набор директив OpenMP для C/C++ и почти все параметры. Поэтому при подготовке данной публикации я использовал описание, приведенное в стандарте, поскольку оно является более полным.

1. Директивы

В преамбуле к описанию стандарта сказано, что для использования директив и всех возможностей OpenMP для C/C++ в командной строке компилятора должен быть указан специальный ключ. В версии для Intel C++ он называется /Qopenmp.

Директивы OpenMP для C/C++ имеют следующий формат:

#pragma omp <имя директивы> [список параметров]

Как известно, в семействе языков C директива #pragma предназначена для управления специфическими возможностями компилятора. Ключевое слово omp используется для того, чтобы исключить случайные совпадения имен директив OpenMP с другими именами.

Директивы parallel, for, section и single имеют параметры, которые указываются в той же строке после имени директивы. Если параметры не указаны, то используются их значения принятые по умолчанию.

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

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

Параллельно выполняемые нити формируют директивы for и sections. Первая из них создает нити, содержащие копии ассоциированного оператора цикла, каждая из которых выполняет некоторую часть от общего количества итераций. Вторая формирует нити из отдельных блоков, расположенных в исходной программе последовательно друг за другом. Перед каждым преобразуемым в нить блоком указывается вспомогательная директива section.

В сфере действия директив for и sections нельзя использовать все остальные директивы OpenMP, что ограничивает возможности управления вычислительным процессом в параллельном регионе. Для снятия этих ограничений описание региона должно начинаться с директивы parallel. Формально сферой ее действия является блок, но он может содержать вложенные блоки и директивы, как формирующие структуру региона, так и управляющие процессом вычислений. Кроме того, директива parallel имеет параметры, которые позволяют по усмотрению программиста выбирать количество нитей, выполняемых в параллельном регионе.

Состав директив и их краткая характеристика приведены в следующей таблице:

Директива

Описание

parallel [параметры]

Директива имеет декларативный характер и не управляет действиями, выполняемыми в параллельном регионе. Она нужна, например, в тех случаях, когда для распараллеливания региона используется несколько директив формирующих нити или выполняющих другие действия.
Параметры: private, firstprivate, shared, default, reduction, copyin, if, num_threads.

for [параметры]

Формирует нити, содержащие копии ассоциированного с директивой цикла типа for. Каждая копия будет выполнять свою часть от общего числа итераций, описанных в исходном операторе цикла.
Параметры: private, firstprivate, lastprivate, reduction, ordered, nowait, schedule.

sections [параметры]

Формирует параллельный регион из блоков, расположенных в исходном тексте программы последовательно друг за другом. Перед каждым преобразуемым блоком указывается директива section.
Параметры: private, firstprivate, lastprivate, reduction, nowait.

section

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

single [параметры]

Указывает на то, что в регионе должна выполняться только одна нить, содержащая ассоциированный с директивой блок. Такая нить может, например, изменять значения частных переменных, используемых другими нитями региона.
Параметры: private, firstprivate, copyprivate, nowait.

parallel for
[параметры]

Сокращенная форма записи для создания параллельного региона, содержащего единственную директиву for. Сочетание двух директив увеличивает количество доступных параметров.
Параметры: private, firstprivate, lastprivate, shared, default, reduction, ordered, schedule, copyin, if, num_threads.

parallel sections
[параметры]

Сокращенная форма записи для создания параллельного региона, содержащего единственную директиву sections. Сочетание двух директив увеличивает количество доступных параметров.
Параметры: private, firstprivate, lastprivate, shared, default, reduction, copyin, if, num_threads.

master

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

critical [имя секции]

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

barrier

Указывает точку, в которой организуется ожидание окончания исполнения всех нитей параллельного региона. По умолчанию (если не указан параметр nowait) директивы for, sections и single устанавливают барьер в нужной точке региона.

atomic

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

flush
[список переменных]

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

threadprivate
(список переменных)

Объявляет частными в нитях параллельного региона переменные, описанные во внешнем блоке. Директива указывается во внешнем блоке сразу после описания соответствующих переменных и не влияет на работу с ними вне параллельного региона. См. copyin.

ordered

Используется в сфере действия директивы for для выделения блока, в котором повторы цикла будут происходить в естественном порядке (как при обычных последовательных вычислениях). У директивы for должен быть указан одноименный ключ ordered.

2. Параметры директив

Параметры директив (directive clauses) можно условно разделить на две категории. Одни из них влияют на ход вычислительного процесса и позволяют учесть особенности распараллеливаемой задачи и многопроцессорной системы, в которой она будет выполняться. Например, с их помощью можно разрешить создание параллельного региона только при выполнении заданного условия, назначить количество нитей в регионе или количество итераций, выполняемых в нитях.

Другие параметры, их примерно половина, определяют видимость переменных в параллельном регионе и специальные действия, которые должны выполняться над ними в начале и в конце сферы действия директивы. В таких случаях после имени параметра в круглых скобках указывается список переменных, на которые распространяется действие параметра. Он может содержать только те имена, которые описаны в исходном тексте стандартными средствами C/C++ и используются в ассоциированном с директивой операторе или блоке.

Работа с переменными. По умолчанию действует простое правило определения видимости переменных в параллельном регионе. Те из них, которые в исходном тексте программы описаны вне распараллеливаемого оператора или блока, объявляются общими, а описанные внутри оператора или блока - частными. Общие (shared) переменные доступны всем нитям, а частные (private) создаются для каждой нити только на время ее выполнения. Пример общей переменной - имя массива, элементы которого обрабатываются в теле цикла, а переменная, управляющая повторами цикла, является частной, иначе будет невозможно распараллеливание цикла.

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

Предположим, что результаты вычислений, выполняемых в теле цикла, накапливаются в переменной sum. По умолчанию она объявляется общей и доступна всем нитям. Конечный результат окажется корректным, но в процессе выполнения задания сразу несколько нитей будут обращаться к переменной sum для чтения или записи ее содержимого. Это неизбежно замедлит процесс выполнения задания. Просто объявить переменную sum частной нельзя, поскольку будут потеряны результаты вычислений накопленные в нитях. Специально для такого случая предназначен параметр reduction. Если в его списке указать переменную sum, то в нитях она будет использоваться как частная, а при выходе из каждой нити накопленный результат будет добавлен к общей переменной sum.

Другой пример. Предположим, что в теле цикла для хранения промежуточных результатов вычислений используется переменная temp. Если в исходном тексте ее описание расположено вне оператора цикла, то при распараллеливании по умолчанию она будет объявлена общей переменной. В таком случае при выполнении задания вполне вероятны случаи, когда одна из нитей успеет изменить значение temp, прежде чем другая использует сформированное в ней значение. Это неизбежно приведет к ошибкам в результатах вычислений. Поэтому все переменные, используемые для хранения промежуточных результатов, следует объявлять частными, либо описывать их непосредственно в теле оператора цикла.

Параметры и их краткое описание приведены в следующей таблице:

Параметр

Описание

private (список)

Для каждой нити на время ее выполнения создаются копии перечисленных в списке переменных, доступные только в пределах конкретной нити (на конкретном процессоре). Исходные значения копий не определены.
Применяется с директивами parallel, for, sections, single.

firstprivate (список)

Отличается от параметра private тем, что создаваемым копиям присваивается значения, которые имели перечисленные в списке переменные перед входом в параллельный регион.
Применяется с директивами parallel, for, sections, single.

lastprivate (список)

Отличается от параметра private тем, что после выхода из цикла или из последней секции конечные значения перечисленных в списке переменных будут доступны для использования.
Применяется с директивами for, sections.

shared (список)

Описывает те переменные исходного блока, которые будут доступны всем нитям параллельного региона. Другими словами, нити будут работать не с копиями, а с оригиналами переменных.
Применяется с директивой parallel.

default (shared | none)

Данный параметр действует на все перемнные, используемые в параллельном регионе. Default (shared) делает их общими, а default (none) недоступными. В последнем случае необходимо явное описание каждой переменной, используемой в регионе.
Применяется с директивой parallel.

reduction
(оператор:список)

Ограничивает доступ к оригиналам переменных. В нитях используются копии, а значения оригиналов корректируются после выполнения каждой нити. Ограниченные переменные должны использоваться в качестве операндов указанного перед их списком оператора.
Применяется с директивами parallel, for, sections.

nowait

Отменяет барьер, установленный по умолчанию, поэтому после окончания выполнения нити не происходит ожидания окончания выполнения других нитей.
Применяется с директивами for, sections, single.

if (условие)

Если условие выполнено (true), то регион распараллеливается, в противном случае (false) все его блоки выполняются одним процессором в естественном порядке.
Применяется с директивой parallel.

ordered

Указывает на то, что в теле директивы for будет использована директива ordered, для выделения блока, в котором итерации выполняются в естественном порядке.
Применяется с директивой for.

schedule (static |
dynamic | guided |
runtime)

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

copyin (список)

Для каждой нити параллельного региона создаются копии переменных, описанных в директиве threadprivate, копиям присваиваются текущие значения оригиналов. Имена, указанные в списках директивы threadprivate и параметра copyin, должны совпадать.
Применяется с директивой parallel.

Следующие два параметра отсутствуют в оригинальном документе,
но они предусмотрены стандартом OpenMP для C/C++.

copyprivate (список)

После выполнения нити, содержащей конструкцию single, новые значения переменных списка copyprivate будут доступны всем одноименным частным переменным (private и firstprivate), описанным в начале параллельного региона и используемым всеми его нитями.
Применяется с директивой single.

num_threads
(целочисленное
выражение
)

Предназначен для явного задания количества нитей, которое может содержать параллельный регион. По умолчанию выбирается последнее значение, установленное с помощью функции omp_set_num_threads, или значение переменной omp_num_threads.
Применяется с директивой parallel.

3. Переменные окружения

Стандарт OpenMP и его реализация для Intel C++ предусматривают существование четырех переменных окружения (environment variables). Они содержат статически определенные величины, которые могут использоваться при формировании структуры параллельного региона. Имена переменных набираются заглавными латинскими буквами, а аргументы могут набираться как на верхнем, так и на нижнем регистре. Способ установки значений переменных зависит от конкретной реализации компилятора, например, в описании стандарта сказано, что для Unix оболочки C применяется такой способ: setenv OMP_SCHEDULE "dynamic". Изменение значений переменных при выполнении задания невозможно.

Переменные и их назначение перечислены в следующей таблице:

Переменная

Описание

OMP_SHEDULE

Содержит тип планирования и количество итераций выполняемых в нитях. Вызывается, например, если для директивы for указан параметр shedule (runtime).
Значение по умолчанию: static

OMP_NUM_THREADS

Задает количество нитей в параллельном регионе. Может использоваться, например, если в директиве parallel не указан параметр num_threads.
Значение по умолчанию: количество процессоров в системе.

OMP_DYNAMIC

Разрешает/запрещает динамическое регулирование количества нитей в параллельном регионе.
Значение по умолчанию: false.

OMP_NESTED

Разрешает или запрещает распараллеливание вложенных (внутренних) циклов.
Значение по умолчанию: false.

4. Функции библиотеки runtime

В описании стандарта OpenMP для С/C++ перечислены 17 функций, входящих в состав библиотеки runtime. Их состав и точное количество в реализации Intel C++ мне не известны, поэтому ниже описаны только 4 функции, которые обязательно должны быть доступны. Они позволяют в процессе выполнения задания определить и изменить количество нитей, используемых в параллельном регионе, определить номер конкретной нити и количество доступных процессоров в системе.

Для возможности использования библиотечных функций к приложению должен быть подключен заголовочный файл omp.h. Все четыре функции целочисленные.

Имена и назначение функций перечислены в следующей таблице:

Функция

Описание

omp_get_num_threads

Возвращает фактическое количество нитей, существующих в параллельном регионе. При вызове из последовательного региона возвращает значение 1.
Формат: int omp_get_num_threads(void);

omp_set_num_threads

Устанавливает количество нитей, которое может содержать параллельный регион, имеет приоритет над переменной окружения OMP_NUM_THREADS. Для использования функции должно быть разрешено изменение количества нитей в процессе выполнения задания.
Формат: int omp_set_num_threads(int NumThreads);

omp_get_thread_num

Возвращает номер нити из которой вызвана функция. Номер изменяется в пределах от 0 до omp_get_num_threads() - 1. Для основной нити (master thread) он всегда равен нулю.
Формат: int omp_get_thread_num(void);

omp_get_num_procs

Возвращает количество процессоров, доступных для использования нитями параллельного региона, включая логические процессоры, если поддерживается технология Hyper-Threading.
Формат: int omp_get_num_procs(void);

Похожие материалы

Информация о работе