[МУЗЫКА]
[МУЗЫКА] Приветствую вас,
мои дорогие слушатели!
В этой лекции мы поговорим о новых директивах,
которые были добавлены в стандарт OpenMP 4.0.
Каждая новая спецификация OpenMP вводит очень полезные и необходимые
дополнения к уже существующему функционалу.
Например, в версии 3.0 были добавлены так ожидаемые задачи (tasks),
позволившие решать еще больший спектр задач по распараллеливанию приложений.
В 3.1 был введен целый ряд улучшений по работе с задачами и редукциями.
Последняя версия дополнительно расширила типы поддерживаемого параллелизма.
Мы с вами уже знаем, что параллелизм очень разный.
Начинается он еще на уровне инструкции, когда современные процессорные
архитектуры предоставляют нам функционал конвейера или суперскалярности.
Уже на этом уровне мы можем говорить о том, что наше приложение параллельно.
Но это то, что уже заложено в микропроцессорах на сегодняшний день и что
для разработчиков программного обеспечения используется неявно.
Есть у существующих на сегодняшний день процессоров и другие полезные вещи.
Это несколько ядер и векторные регистры, и векторные инструкции.
Они дают нам еще 2 типа параллелизма.
Первый – по задачам.
В данном случае используются все ядра нашего процессора.
Идея заключается в выделении независимых задач,
которые одновременно могут выполняться на нескольких ядрах.
Второй – по данным (векторизация).
Используются регистры увеличенной длины каждого ядра.
В этом случае происходит обработка сразу нескольких элементов векторов,
данных за 1 операцию, инструкцию.
Отсюда и название – векторизация.
Вот, собственно, и все типы параллелизма для систем с общей памятью.
Есть еще системы с распределенной памятью,
но это не тема нашего сегодняшнего разговора.
OpenMP используется именно для систем с общей памятью.
Более того, до последнего момента в OpenMP был параллелизм только по задачам.
А в современном мире и для современных процессоров немаловажную роль
играет параллелизм по данным, то есть векторизация.
Если вы и хотите использовать все возможности процессора,
и вам они действительно нужны для написания эффективной программы,
то нужно использовать векторизацию.
Поэтому в последней спецификации OpenMP 4.0 были добавлены хорошо известные людям,
работающим с компиляторам Intel, директивы для работы с параллелизмом по данным,
весьма напоминающие аналогичные из Intel Cilk Plus.
Итак, что же появилось?
Теперь можно не просто распределять работу по разным потокам через существовавшие
и раньше директивы, но и обеспечить векторизацию соответствующего кода.
И все это в рамках технологии OpenMP,
то есть без привязки к конкретному компилятору.
Благо стандарты стремятся поддерживать почти все производители компиляторов.
Допустим, у нас есть функция и в ней некий цикл.
Проект со всеми исходными кодами вы можете взять в дополнительных материалах к
лекции.
Так вот, если мы скомпилируем его с помощью ключа vec-report,
чтобы узнать, был ли он векторизован, то увидим, что нет.
Причина в консервативности компилятора.
Если он чувствует, что векторизация опасна,
то предпочтет перестраховаться и ничего не делать.
В нашем случае это происходит из-за большого количества указателей,
переданных в функцию.
Компилятор просто думает,
что какие-то из них вполне могут иметь пересечения в памяти.
Но если мы как разработчики знаем, что это не так, и векторизовать этот
цикл безопасно, можем использовать новую директиву OpenMP simd.
Добавляем эту директиву перед циклом, пересобираем код.
И видим, что код был векторизован.
Я думаю, что вы уже заметили,
что она очень похожа на директиву из набора инструментов Intel Cilk Plus.
Но теперь она стала частью стандарта OpenMP.
И если компилятор, не обязательно от Intel, поддерживает данный стандарт,
то вы можете воспользоваться векторизацией.
Кроме того, есть и смешанная конструкция для использования двух типов
параллелизма одновременно.
Данная директива позволяет одновременно создать параллельную область,
распределить операции цикла и провести векторизацию.
Стоит отметить,
что использовать конструкцию в голом виде не очень рекомендовано,
потому что компиляторные проверки для данного цикла полностью отключаются.
Поэтому можно и нужно задавать, как и в других OpenMP директивах,
дополнительные опции.
Это опции private, lastprivate, reduction, collapse.
Эти опции мы уже рассматривали в других директивах.
В этой директиве они работают аналогично.
А четыре другие опции мы рассмотрим подробнее.
Первая опция – safelen.
В круглых скобках задается целая положительная константа.
С помощью этой опции можно задать максимальное расстояние между двумя
итерациями, которые могут выполняться параллельно.
Например, в данном примере есть зависимость по данным,
но только между элементами, которые на расстоянии 18 итераций.
Если элементы ближе, то можно их вычислять параллельно.
Используя опцию safelen можно компилятору это сказать.
Вторая опция – это simdlen.
В круглых скобках задается целое положительное число.
С помощью этой опции можно задать предпочтительное число итераций,
которые должны быть выполнены параллельно с помощью векторных инструкций.
Третья опция – alignet.
В скобках указываются переменные (массивы или указатели) и через запятую число,
относительно которого выровнены адреса в памяти.
Это означает, что адрес первого элемента массива или адрес указателя делится
нацело на данное число.
И четвертая, последняя опция, – это linear.
В скобках задается переменная и через двоеточие целое положительное число.
Данная опция позволяет установить линейную взаимосвязь между переменной и
значениями пространства итераций.
Давайте двинемся дальше.
Если в цикле вызывается некая функция, то в общем случае этот цикл не векторизуется.
В OpenMP 4.0 была добавлена очень полезная вещь – так называемые элементные функции.
Синтаксис этой директивы вы видите на экране: #pragma omp declare simd.
Эта директива позволяет сделать из самой обычной функции элементную,
то есть ту, которая могла бы выдавать вектор результатов.
В итоге ее можно вызывать и в цикле с последующей его удачной векторизацией.
Следующий пример демонстрирует применение данной директивы.
Мы создали функцию, которая складывает два числа,
а потом воспользовались этой функцией для сложения двух векторов.
Если не указать директивы, то компилятор не сможет векторизовать цикл.
Мы же, воспользовавшись опцией #pragma omp declare simd,
подсказали компилятору, что нужно из обычной функции сделать векторную.
И потом, воспользовавшись опцией #pragma omp simd, сообщили компилятору,
что нужно векторизовать цикл.
Таким образом,
в руках разработчиков появился мощный механизм создания параллельных
программ и использования обоих типов параллелизма: по задачам и по данным.
Но это не все нововведения в OpenMP 4.0.
Не обошли вниманием и очень часто используемую сейчас модель
программирования для систем с гибридной архитектурой,
с использованием ускорителей.
Это не только графические карты, но и ускорители от Intel Xeon Phi.
Были введены новые директивы, которые позволяют в рамках одной
спецификации использовать эти ускорители для вычислений.
С помощью директивы target мы теперь можем провести вычисления на
ускорителе и получить результат на хосте.
Следующий пример это демонстрирует: с помощью опции мы указали,
что нужно отправить на ускоритель массив b и переменные c и d,
а по завершению выполнения данного кода забрать с ускорителя массив a.
Но это серьезная большая тема, о которой стоит говорить отдельно.
И, к сожалению, пока эта возможность была реализована только в
компиляторе Intel для ускорителя от Intel Xeon Phi.
В данной лекции мы рассмотрели основные нововведения в стандарте OpenMP 4.0.
Есть другие мелкие изменения, о которых мы не говорили,
но они также являются важными, и вы можете прочитать их в спецификациях.
А в следующей лекции мы рассмотрим несколько примеров векторизации программ.
До встречи!