страница 1 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Похожие работы
|
Параллельное программирование с использованием OpenMP - страница №1/1
![]() Глава 4 Параллельное программирование с использованием OpenMP В данной главе описывается технология параллельного программирования OpenMP для систем вычислительных с общей памятью. OpenMP - прикладной программный интерфейс (API) для создания параллельных приложений для МВС, использующих общую память. При реализации OpenMP основной упор делался на создание системы программирования, которая позволила облегчить параллельное программирование задач для систем с общей памятью. Стандарт OpenMP предназначен, для выполнения параллельной программы на широком круге систем с архитектурой SMP, за счет поддержки данного стандарта производителями суперкомпьютеров и создания реализаций стандарта для каждой вычислительной платформы. Развитие многоядерных и многопоточных процессоров позволяет использовать OpenMP, для создания программ для однопроцессорных компьютеров. OpenMP не является новым языком программирования, это нотации (директивы), которые могут быть добавлены к последовательной программе в ФОРТРАНе, C или C++, чтобы описать, как вычисления должна быть разделены среди нитей (threads), которые выполнятся на различных процессорах или ядрах и разграничить доступ к общим данным. Вставка соответствующих директив OpenMP в последовательную программу позволит увеличить производительность выполняемой программы на параллельной архитектуре с общей памятью при минимальной модификации кода. При подготовке главы были использованы работы российских и зарубежных ученых. Стандарт OpenMP описан группой разработчиков в Спецификации OpenMP [16]. В качестве основных русскоязычных источников, посвященных технологии OpenMP, можно рассматривать работы: Антонова А. С. [1], Гергеля В. П. [2], Лупина С. А. [3], Левина М. П. [4], в качестве англоязычных работ: Chandra R. [14], Charman B. [15]. Так же информация о OpenMP представлена на информационных порталах по тематике параллельных вычислений, например, [6-13]. При изложении материала описание директив и функции OpenMP приводится для языков программирования Си, Си++ и Фортран. Примеры же в основном приводятся на языке Си, кроме тех случаев, когда используются принципиально разные подходы в Си и Фортране. Для удобства использования в учебном процессе материал изложен в виде лабораторных работ, рассчитанных на 1 или 2 академических часа. Каждая лабораторная заканчивается списком вопросов для самоконтроля и закрепления материала, списком заданий для выполнения на занятии и списком заданий для самостоятельного выполнения.
Стандарт OpenMP отличается от MPI тем, что в последнем, программист явно указывает какие данные с какого процесса и на какой должны быть отправлены. В OpenMP реализация функций скрыта от программиста, он с помощью набора директив указывает, как должны быть распределены данные, библиотека OpenMP реализует функции по распределению. Модель запуска программ OpenMP Стандарт OpenMP реализуется на уровне компилятора языков высокого уровня Фортран, Си и Си ++, с помощью набора директив. Если компилятор не поддерживает стандарт OpenMP, то программа компилируется как обычное последовательное приложение. Операционная система создает процесс и запускает одну нить приложения OpenMP. Первая нить называется главная, она существует на протяжении всего цикла работы программы. Программа выполняется главной нитью, как только главная нить обнаруживает директиву создания параллельной области (parallel), создается группа нитей для выполнения параллельных вычислений. После выполнения команд параллельной области группа нитей завершает свою работу, и управление переходит опять главной нити. На рис. 1 показан порядок выполнения параллельной программы OpenMP. Главная нить (master thread) выполняет блок последовательного кода, директивой parallel создает группу нитей (teams of threads), далее блок параллельного кода выполняется всеми нитями в группе. После выполнения параллельного кода группа нитей завершает свою работу, и последовательную часть выполняет опять главная нить.
Рис. 1 Модель запуска OpenMP Модель памяти В OpenMP предполагается наличие как общей для всех нитей области памяти, так и локальной области памяти для каждой нити. Общая переменная всегда существует лишь в одном экземпляре для всей области действия и доступна всем нитям под одним и тем же именем. Объявление локальной переменной вызывает порождение своего экземпляра данной переменной (того же типа и размера) для каждой нити. Изменение нитью значения своей локальной переменной никак не влияет на изменение значения этой же локальной переменной в других нитях. Если несколько переменных одновременно записывают значение общей переменной без выполнения синхронизации или если как минимум одна нить читает значение общей переменной и как минимум одна нить записывает значение этой переменной без выполнения синхронизации, то возникает ситуация так называемой «гонки данных» (data race), при которой результат выполнения программы непредсказуем. Создание OpenMP программы Первый шаг в создании программы OpenMP – это переход от последовательного к параллельному программированию, который состоит в нахождении блоков команд, которые могут выполняться одновременно различными процессорами. Иногда, это достаточно простая задача, иногда, разработчик должен реорганизовать части кода, чтобы получить независимые последовательности команд. Распараллеливание с использованием технологии OpenMP заключается в создании описания блоков команд, которые будут выполняться одновременно. Описание блока реализуются с помощью комментариев в программе, и называется директива. Директивы OpenMP позволяют программисту говорить компилятору, какие команды должны выполняться параллельно и как их распределять среди нитей, которые выполнят код. Директива OpenMP – это команда в специальном формате, которая понятна только компиляторам OpenMP. Фактически, это выглядит как комментарий для компилятора ФОРТРАН или псевдокомментарий для компилятора C/C ++, это сделано для того, чтобы программа могла выполниться даже, если компилятор не знает директив OpenMP. Количество директив стандарта OpenMP невелико, по сравнению с другими стандартами на параллельные библиотеки (MPI, DVM), но их функциональных возможностей хватает для обеспечений потребностей разработчиков программного обеспечения. Функции позволяют управлять параметрами и замерять время выполнения OpenMP программ.
Все директивы начинаются с #pragma omp. Оставшаяся часть директивы записывается в соответствии с соглашениями стандарта Си/Си++ для директив компиляции. Предварительная обработка (препоцессинг) следующего маркера #pragma omp выполняет подстановку макрокоманды. Директивы зависят от регистра ввода команд. Исполняемые директивы OpenMP применяется к одному структурированному блоку. Директивы OpenMP для языка Фортран задаются следующим образом:
Только одна команда directive-name может быть использована при описании директивы (кроме случая использования комбинированных директив, о которых будет рассказано позднее). Порядок записи clauses в директиве не существенен. В директиве clause может повторяться по мере необходимости, список ограничений использования описывается к каждой clause. Все OpenMP директивы компилятора должны начинаться с комментария sentinel. Формат метки sentinel различается для исходных файлов с фиксированной и свободной формой ввода. Для языка Фортран директивы не зависят от регистра ввода команд. В порядке упрощения представления материала, используется свободная форма для записи синтаксиса директив OpenMP на языке Фортран.
Метки должны начинаться с первой позиции в строке и выглядеть как единое слово без промежуточных символов. В Фортране фиксируется длина строки, включая пробелы, символы переноса и эти правила распространяются и на строку директивы. Директива должна начинаться с 6-ой позиции в строке, для того, чтобы выполнить перенос директивы на следующую строку необходимо в 6-ой позиции выставить знак переноса.
Метка может располагаться в любой позиции строки, отступ задается символами пробел или табуляция. Метка должны быть записана как одно слово без пробелов. К строке директивы применяются правила Фортрана: длина строка, пробелы, правила переноса. В строке директивы после метки обязательно следует пробел. Для разделения длинной строки директивы в конце строки ставиться амперсанд.
Директивы OpenMP можно разделить на 3 категории: определение параллельной области, распределение работы, синхронизация. Каждая директива может иметь несколько дополнительных атрибутов – опций (clause). Отдельно специфицируются опции для назначения классов переменных, которые могут быть атрибутами различных директив. В качестве примера рассмотрим описание директивы создания параллельной области. Дериктива parallel – инициализирует параллельную область и создает группу нитей. Полное описание директивы будет приведено в следующем параграфе.
Все порождённые нити исполняют один и тот же код, соответствующий параллельной области. Предполагается, что в SMP-системе нити будут распределены по различным процессорам или ядрам. Функции Функции OpenMP применяются для определения и изменения параметров исполнения параллельной программы, синхронизации и блокировки нитей и др. Имена все функций OpenMP начинаются с префикса omp_. Если имя функции начинается omp_set_, то это означает, что функция изменяет выбранный параметр. Если имя функции начинается omp_get_, то это означает, что функция считывает значение параметра. Почти все функции OpenMP являются парными функциями. Если пользователь не будет использовать в программе имён, начинающихся с такого префикса, то конфликтов с объектами OpenMP заведомо не будет. В языке Си, кроме того, является существенным регистр символов в названиях функций. Названия функций OpenMP записываются строчными буквами. Для использования функций OpenMP, в программу нужно включить заголовочный файл omp.h (для Фортран-программ – файл omp_lib.h). Если вы используете в приложении только OpenMP-директивы, включать этот файл не требуется. Функции OpenMP можно разделить на следующие блоки: - функции исполняющей среды; - функции блокировки; - функции определения времени; - функции изменения среды выполнения параллельной программы: вложенный параллелизм, динамическая корректировка потоков;
- функция omp_get_num_threads() возвращает количества нитей; - функция omp_set_num_threads() задает количество используемых нитей; - функция omp_get_max_threads() возвращает максимально допустимого количества нитей; - функция omp_get_thread_num() возвращает номер нити; - функция omp_get_thread_limit() возвращает максимально допустимое количество нитей; - функция omp_get_num_procs() возвращает количества доступных для использования процессоров; - функция omp_in_parallel() возвращает нахождения в параллельной области; В OpenMP реализованы простые и вложенные функции блокировки. Вложенные блокировки помечаются префиксом «nest». Выполняются блокировки с помощью переменных блокировки типа omp_lock_t и omp_lock_nest_t. Переменная блокировки может находиться в одном из следующих состояний: инициалирована, неинициалирована, заблокирована, разблокирована. Управление переменными блокировки осуществляется следующим набором функций: - функция инициализации простой блокировки omp_init_lock(); - функция перевода простой блокировки в неинициализированное состояние omp_destroy_lock(); - функция захвата переменной простой блокировки omp_set_lock(); - функция снятия простой блокировки omp_unset_lock(); - функция, реализующая неблокирующую проверку и попытку захвата простой блокировки omp_test_lock(); - функция инициализации вложенной блокировки omp_init_nest_lock(); - функция перевода вложенной блокировки в неинициализированное состояние omp_destroy_nest_lock(); - функция захвата переменной вложенной блокировки omp_set_nest_lock(); - функция снятия вложенной блокировки omp_unset_nest_lock(); - функция, реализующая неблокирующую проверку и попытку захвата переменной вложенной блокировки omp_test_nest_lock();
- функция определения времени в секундах omp_get_wtime(); - функция определения разрешения системного таймера omp_get_wtick();
- функции задает динамическое изменение количества нитей omp_set_dynamic(); - функции определяет, используется ли динамическое изменение количества нитей omp_get_dynamic(); - функция задает вложенных параллельных областей omp_set_nested(); - функция определяет, используется ли вложенных параллельных областей omp_get_nested(); - функция omp_set_schedule() задает тип планировщика, при применении для распараллеливания циклов варианта планирования schedule(runtime); - функция omp_get_schedule() возвращает тип планировщика, при применении для распараллеливания циклов варианта планирования schedule(runtime); - функция omp_set_max_active_levels() задает максимальное количество активных вложенных параллельных областей; - функция omp_get_max_active_levels() возвращает максимальное количество активных вложенных параллельных областей; - функция omp_get_level() возвращает уровень вложенности параллельных областей; - функция omp_get_ancestor_thread_num() возвращает номер нити (предка) для текущей нити выполняющей вложенную параллельную область; - функция omp_get_team_size() возвращает количество нитей, которые выполняют текущую вложенную параллельную область; - функция omp_get_active_level() возвращает уровень вложенности параллельных областей.
Функция omp_get_wtime() возвращает в вызвавшей нити астрономическое время в секундах (вещественное число двойной точности).
Для определения времени выполнения блока кода исходной программы, необходимо вызвать функцию omp_get_wtime() в начале блока и в конце. Разность полученных значений и будет время исполнения блока. Гарантируется, что момент времени, используемый в качестве точки отсчета, не будет изменён за время существования процесса. Таймеры разных нитей могут быть не синхронизированы и выдавать различные значения. Функция omp_get_wtick() возвращает в вызвавшей нити разрешение таймера в секундах. Это время можно рассматривать как меру точности таймера.
Компиляция и запуск программы Компиляция OpenMP программы выполняется компилятором, поддерживающим OpenMP, для этого необходимо задать параметр компиляции. Для компиляторов разных производителей задание параметра OpenMP может отличаться, в таблице ниже приведены самые распространенные:
После создания параллельной OpenMP программы требуется задание количества нитей, на которых будет запущена программа. Возможны три варианта задания количества нитей: - с помощью переменных окружения; - с помощью функций задания количества нитей; - с помощью параметров директив. Выставлены по мере увеличения мощности. Переменные окружения задают количество нитей для всей программы, функции определяют количество нитей для выбранного блока кода, параметры задают количество нитей для вабранной директивы. Количество нитей определяется значением переменной окружения OMP_NUM_THREADS.
Проверить значение можно с помощью команды echo.
Для запуска программы OpenMP не требуется специализированных исполнительных оболочек, используется стандартный загрузчик операционной системы. В Unix-системах по умолчанию название программы a.out, в Windows - по названию исходного файла. После запуска программа считывает значение переменной окружения OMP_NUM_THREADS определения количества порождаемых параллельных нитей.
Первая программа OpenMP Рассмотрим пример OpenMP программы, которая выводит номер нити и количество нитей, на которых выполняется параллельная программа.
В приведенном примере инициализируется параллельная область, вызываются функции определения номера нити и количества используемых нитей и полученные значения выводится на экран. Используемые директивы и функции будут подробно разобраны в следующих лабораторных работах. Из общего курса по Параллельным вычислениям известно, что основным критерием оценки параллельной программы является уменьшением времени вычислений при увеличении количества используемых процессоров/ядер. Следующий пример иллюстрирует применение функций omp_get_wtime() и omp_get_wtick() для определения времени выполнения параллельной программы, написанной с использованием OpenMP. В данном примере производится замер начального времени, вычисления моделируются оператором sleep(1), затем замер конечного времени. Разность даёт время выполнения параллельной программы. Точность системного таймера измеряется функцией omp_get_wtick().
В результате выполнения программы на экран будет выведено затраченное время и разрешение системного таймера. Варьируя значением оператора sleep (pause), можно попытаться моделировать более ресурсоемкие вычисления. Вместо указанных операторов можно вставить цикл, например, суммирования ряда чисел. Вопросы
Упражнения
|
ещё >> |