Openmp были рассмотрены следующие опции. Изучаем инфраструктуру OpenMP на примере компилятора GCC. Параллельная обработка структур данных

Библиотека активно развивается, в настоящий момент актуальный стандарт версии 4.5 (выпущен в 2015 году), однако уже идет разработка версии 5.0. В тоже время, компилятор Microsoft C++ поддерживает только версию 2.0, а gcc — версию 4.0.

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

Этой библиотеке посвящено множество книг и статей. Наиболее популярными являются книги Антонова и Гергеля , мной также был написан ряд статей (поверхностных) по этой теме , и ряд примеров программ на нашем форуме . На мой взгляд, у этих книг есть ряд недостатков (которые я, конечно, исправляю):

  • в книгах стараются описать все возможности OpenMP, значительная часть которых является атавизмом. В старых версия библиотеки это было нужно, но сейчас использование этих возможностей делает ваш код опаснее и повышает порог вхождения (другой программист потратит много времени чтобы разобраться в нем). Я кратко описал некоторые из таких возможностей в заметке « » и в «учебнике» про них ничего нет. За счет этого мой учебник получился короче и не загружает читателя устаревшей информацией;
  • в книгах мало «реальных» примеров — поиск минимума/максимума не в счет. Я предлагаю своим читателям обратиться за такими примерами на . На форуме можно взять исходный код программ, которые отлажены и работают, при этом к каждой программе прилагается подробный разбор решения и подхода к распараллеливанию. Иногда можно найти несколько реализаций. Примеры будут дополняться.

Вычислительные системы. Идеология OpenMP

Существует множество разновидностей параллельных вычислительных систем — многоядерные/многопроцессорные компьютеры, кластеры, системы на видеокартах, программируемые интегральные схемы и т.д. Библиотека OpenMP подходит только для программирования систем с общей памятью, при этом используется параллелизм потоков. Потоки создаются в рамках единственного процесса и имеют свою собственную память. Кроме того, все потоки имеют доступ к памяти процесса. Схематично это показано на рисунке 1:
рис. 1 модель памяти в OpenMP

Для использования библиотеки OpenMP вам необходимо подключить заголовочный файл "omp.h" , в а также добавить опцию сборки -fopenmp (для компилятора gcc) или установить соответствующий флажок в настройках проекта (для Visual Studio). После запуска программы создается единственный процесс, который начинается выполняться как и обычная последовательная программа. Встретив параллельную область (задаваемую директивой #pragma omp parallel) процесс порождает ряд потоков (их число можно задать явно, однако по умолчанию будет создано столько потоков, сколько в вашей системе вычислительных ядер). Границы параллельной области выделяются фигурными скобками, в конце области потоки уничтожаются. Схематично этот процесс изображен на рисунке 2:


рис.2 директива omp parallel

Черными линиями на рисунке показано время жизни потоков, а красными — их порождение. Видно, что все потоки создаются одним (главным) потоком, который существует все время работы процесса. Такой поток в OpenMP называется master , все остальные потоки многократно создаются и уничтожаются. Стоит отметить, что директивы parallel могут быть вложенными, при этом в зависимости от настроек (изменяются функцией omp_set_nested) могут создаваться вложенные потоки.

OpenMP может использоваться на кластерной архитектуре, но не самостоятельно, т.к. загрузить весь кластер она не может (для этого нужно создавать процессы, использовать инструменты типа MPI ). Однако, если узел кластера многоядерный — то использование OpenMP может существенно поднять эффективность. Такое приложение будет являться «гибридным», в этой статье я не буду вдаваться в тему, тем более что по ней написано много хороших материалов .

Синхронизация — критические секции, atomic, barrier

Все переменные, созданные до директивы parallel являются общими для всех потоков. Переменные, созданные внутри потока являются локальными (приватными) и доступны только текущему потоку. При изменении общей переменной одновременно несколькими потоками возникает состояние гонок (мы не можем гарантировать какой-либо конкретный порядок записи и, следовательно, результат) — это проблема и допускать такое нельзя. Такая же проблема возникает когда один поток пытается читать переменную в то время, как другой ее изменяет. Ситуацию поясняет следующий пример:

#include "omp.h" #include int main() { int value = 123; #pragma omp parallel { value++; #pragma omp critical { std::cout << value++ << std::endl; } } }

Программа описывает переменную value , общую для всех потоков. Каждый поток увеличивает значение переменной, а затем выводит полученное значение на экран. Я запустил ее на двухъядерном компьютере, получил следующие результаты:

Проблема гонки потоков OpenMP

Видно, что чаще всего сначала каждый из потоков увеличит значение переменной, а затем они по-очереди выведут результаты (при этом каждый из них еще раз увеличивает значение), но в некоторых случаях порядок выполнения будет другим. Нас же в этом примере сейчас интересует, при попытке одновременного увеличения значения value программа может вести себя как угодно — увеличить только один раз или вовсе завершиться аварийно.

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

Для ряда операций более эффективно использовать директиву atomic , чем критическую секцию. Она ведет себя также, но работает чуть быстрее. Применять ее можно для операций префиксного/постфиксного инкремента/декремента и операции типа X BINOP = EXPR , где BINOP представляет собой не перегруженный оператор +, *, -, /, &, ^, |, <<, >> . Пример использования такой директивы:

#include "omp.h" #include int main() { int value = 123; #pragma omp parallel { #pragma omp atomic value++; #pragma omp critical (cout) { std::cout << value << std::endl; } } }

Тут же показана возможность именования критических секций — очень рекомендую ей пользоваться всегда. Дело в том, что все безымянные секции рассматриваются как одна (очень большая) и если вы не дадите им явно имена, то только в одной из этих секций в один момент времени будет один поток. Остальные будут ждать. Имя секции должно подсказывать программисту к какому виду ресурса она относится — в приведенном примере таким ресурсом является поток вывода на экран (cout).

Несмотря на то, что каждая операция над общими данными в последнем примере размещена в критической секции или является атомарной, в нем есть проблема, т.к. порядок выполнения этих операций по прежнему не определен. Запустив программу раз 20 мне удалось получить на экране не только "125 125" , но и "124 125" . Если мы хотим чтобы сначала каждый поток увеличил значение, а затем они вывели их на экран — можем использовать директиву barrier:

#pragma omp parallel { #pragma omp atomic value++; #pragma omp barrier #pragma omp critical (cout) { std::cout << value << std::endl; } }

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

Разделение задач между потоками

Параллельный цикл

Самый популярный способ распределения задач в OpenMP — параллельный цикл. Не секрет, что программы почти всю свою жизнь проводят выполняя циклы, при этом если между итерациями цикла нет зависимостей — то цикл называется векторизуемым (его итерации можно поделить между потоками и выполнить независимо друг от друга). В статье « » я поверхностно описал эту конструкцию на примерах вычисления суммы элементов массива и численного интегрирования, не буду повторяться — рекомендую вам пройти по ссылке, т.к. дальше описаны более «интересные» аспекты параллельного цикла.

Параллельный цикл позволяет задать опцию schedule , изменяющую алгоритм распределения итераций между потоками. Всего поддерживается 3 таких алгоритма. Далее полагаем, что у нас p потоков выполняют n итераций:

Опции планирования:

  • schedule(static) — статическое планирование. При использовании такой опции итерации цикла будут поровну (приблизительно) поделены между потоками. Нулевой поток получит первые \(\frac{n}{p}\) итераций, первый — вторые и т.д.;
  • schedule(static, 10) — блочно-циклическое распределение итераций. Каждый поток получает заданное число итераций в начале цикла, затем (если остались итерации) процедура распределения продолжается. Планирование выполняется один раз, при этом каждый поток «узнает» итерации которые должен выполнить;
  • schedule(dinamic), schedule(dynamic, 10) — динамическое планирование. По умолчанию параметр опции равен 1. Каждый поток получает заданное число итераций, выполняет их и запрашивает новую порцию. В отличии от статического планирования, выполняется многократно (во время выполнения программы). Конкретное распределение итераций между потоками зависит от темпов работы потоков и трудоемкости итераций;
  • schedule(guided), schedule(guided, 10) — разновидность динамического планирования с изменяемым при каждом последующем распределении числе итераций. Распределение начинается с некоторого начального размера, зависящего от реализации библиотеки до значения, задаваемого в опции (по умолчанию 1). Размер выделяемой порции зависит от количества еще нераспределенных итераций

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

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

Int sum_arr(int *a, const int n) { int sum = 0; #pragma omp parallel reduction (+: sum) { #pragma omp for for (int i = 0; i < n; ++i) sum += a[i]; } return sum; }

Выглядит это красиво, но на самом деле в каждом потоке создается локальная переменная для хранения суммы части массива (вычисление которой назначено текущему потоку), ей присваивается значение 0 (т.к. редукция с оператором +). Каждый поток вычисляет сумму, но необходимо ведь сложить все эти значение чтобы получить окончательный результат? — Делается это с помощью критической секции или атомарной операции примерно следующим образом:

Int sum_arr(int *a, const int n) { int sum = 0; #pragma omp parallel { int local_sum = 0; #pragma omp for for (int i = 0; i < n; ++i) local_sum += a[i]; #pragma omp atomic sum += local_sum; } return sum; }

Такой подход используется постоянно, поэтому я рекомендую внимательно рассмотреть этот код. Чуть более сложным примером является параллельный . В качестве задачи для проверки усвоения материала предлагаю попробовать построить гистограмму (например для изображения).

Параллельные задачи (parallel tasks)

Параллельные задачи — это более гибкий механизм, чем параллельный цикл. Параллельный цикл описывается внутри параллельной области, при этом могут возникнуть проблемы. Например, мы написали параллельную функцию вычисления суммы элементов одномерного массива, и нашу функцию решили применить для вычисления суммы элементов матрицы, но сделать это также параллельно. Получится вложенный параллелизм. Если (теоретически) наш код запущен на 8 ядрах — то фактически будет создано 64 потока. Ну а если кому-нибудь придет в голову идея делать параллельно еще что-нибудь?

Иногда такую ситуацию нелегко обнаружить, например, на нашем форуме можно найти параллельную реализацию , при этом параллельно вычисляется n определителей. вызывает функцию .

Проблема параллельного цикла в том, что число создаваемых потоков зависит от того какие функции распараллелены и как они друга друга вызывают. Очень сложно все это отслеживать и, тем более, поддерживать. Решение проблемы — параллельные задачи, которые не создают поток, а лишь выполняют добавление задачи в очередь, освободившийся поток выбирает задачу из пула. Я описывал этот механизм в статье
« » и не буду повторяться (рекомендую прочитать материал по ссылке — в статье рассматривается возможность распараллеливания рекурсивных функций с помощью механизма задач). Отмечу лишь то, что в параллельные задачи были предложены в стандарте OpenMP 3.0 (в 2008 году) поэтому их поддержка отсутствует в Microsoft C++. Кроме того, в свежем стандарте OpenMP 4.5 была предложена конструкция taskloop , за счет которой использовать параллельные задачи для распараллеливания циклов теперь также удобно как и параллельный цикл.

Параллельные секции

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

For (int i = 1; i < n; ++i) a[i] = a+1;

Если же у нас в программе появляется несколько фрагментов, не зависящих друг от друга, но имеющий зависимости внутри себя — то их распараллеливают с помощью механизма параллельных секций:

#pragma omp parallel { #pragma omp sections { #pragma omp section { for (int i = 1; i < n; ++i) a[i] = a+1; } #pragma omp section { for (int i = 1; i < n; ++i) b[i] = b+1; } } }

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

Заключение и дополнительная литература

Тут учебнику конец… Если хотите посмотреть больше примеров исходников с OpenMP или у вас есть вопросы по решению конкретных задач — загляните на наш форум. Если же у вас возникли вопросы по тексту статьи — пишите в комментариях.

  1. Поддержка OpenMP в Lazarus/Freepascal: http://wiki.lazarus.freepascal.org/OpenMP_support
  2. OpenMP для Java: http://www.omp4j.org/
  3. Антонов А.С. Технологии параллельного программирования MPI и OpenMP: Учеб. пособие. Предисл.: В.А.Садовничий. – М.: Издательство Московского университета, 2012.-344 с.-(Серия “Суперкомпьютерное образование”). ISBN 978-5-211-06343-3.
  4. Гергель В.П. Высокопроизводительные вычисления дл многоядерных многопроцессорных систем. Учебное пособие – Нижний Новгород; Изд-во ННГУ им. Н.И.Лобачевского, 2010
  5. : https://сайт/archives/1150
  6. Параллельные задачи (tasks) OpenMP:
  7. : https://сайт/forums/forum/programming/parallel_programming/openmp_library
  8. : href=»https://сайт/forums/topic/openmp-problems
  9. : https://сайт/forums/topic/matrix-triangulation_cplusplus
  10. Профилировка гибридных кластерных приложений MPI+OpenMP: https://habrahabr.ru/company/intel/blog/266409/
  11. : https://сайт/forums/topic/openmp-cramer-method_cplusplus
  12. 32 подводных камня OpenMP при программировании на Си++: https://www.viva64.com/ru/a/0054/
  13. OpenMP и статический анализ кода: https://www.viva64.com/ru/a/0055/
OpenMP - стандарт программного интерфейса приложений для параллельных систем с общей памятью. Поддерживает языки C, C++, Фортран .

Модель программы в OpenMP

Модель параллельной программы в OpenMP можно сформулировать следующим образом:

  • Программа состоит из последовательных и параллельных секций (рис. 2.1).
  • В начальный момент времени создается главная нить, выполняющая последовательные секции программы.
  • При входе в параллельную секцию выполняется операция fork ,порождающая семейство нитей. Каждая нить имеет свой уникальный числовой идентификатор (главной нити соответствует 0). При распараллеливании циклов все параллельные нити исполняют один код. В общем случае нити могут исполнять различные фрагменты кода.
  • При выходе из параллельной секции выполняется операция join .Завершается выполнение всех нитей, кроме главной.

OpenMP составляют следующие компоненты:

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

Использование директив компилятора и подпрограмм библиотеки времени выполнения подчиняется правилам, которые различаются для разных языков программирования. Совокупность таких правил называется привязкой к языку .

Привязка к языку Fortran

В программах на языке Fortran директивы компилятора , имена подпрограмм и переменных окружения начинаются с OMP . Формат директивы компилятора :

{!|C|*}$OMP директива [оператор_1[, оператор_2, ...]]

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

Пример программы на языке Fortran с использованием OpenMP

program omp_example integer i, k, N real*4 sum, h, x print *, "Please, type in N:" read *, N h = 1.0 / N sum = 0.0 C$OMP PARALLEL DO SCHEDULE(STATIC) REDUCTION(+:sum) do i = 1, N x = i * h sum = sum + 1.e0 * h / (1.e0 + x**2) enddo print *, 4.0 * sum end

OpenMP позволяет легко создавать параллельные программы для систем с общей памятью (много­ядерных и многопроцессорных). В этой статье я расскажу о том, как включить OpenMP в самой рас­пространённой среде программирования - Visual Studio. Согласно официальной версии Micro­soft , OpenMP поддерживается только в версиях Professional среды разработки Visual Studio 2005/2008/2010. Однако бесплатная Visual Studio Express обладает тем же компилятором, что и версия Professional. Поэтому после небольшой «доработки напильником» параллельные OpenMP-программы будут компилироваться и, главное, работать даже в Visual Studio Express.

OpenMP от Microsoft реализован посредством следующих компонентов:

  1. компилятор C++, входящий в состав Visual Studio;
  2. заголовочный файл omp.h;
  3. библиотеки стадии компиляции: vcomp.lib и vcompd.lib (последняя используется для отлад­ки);
  4. библиотеки времени выполнения: vcomp90.dll и vcomp90d.dll. Цифры в названии могут разли­чаться: в Visual Studio 2005 вместо 90 стоят цифры 80.

В бесплатной Visual Studio Express перечисленные библиотеки отсутствуют.

OpenMP и Visual Studio Express

Если вы хотите создавать параллельные OpenMP-программы под Windows, то самый удобный спо­соб - это воспользоваться Visual Studio 2005/2008/2010 Professional. Напоминаю, что она бес­платна для студентов и аспирантов . Кроме того, вы можете купить Visual Studio Professional за $600 (конечно, существует ещё и третий вариант, но мы о нём не будем говорить).

Если на вашем компьютере установлена версия Professional - переходите к следующему разделу статьи. В этом разделе рассмотрим случай, когда по каким-либо причинам вы вынуждены использо­вать Visual Studio Express.

Visual Studio Express - это бесплатная урезанная версия Visual Studio. Нас будет интересовать вер­сия 2008 года. Скачать её можно отсюда: http://www.microsoft.com/exPress/download/ . Естествен­но, программировать будем на C++, поэтому выби­райте Visual C++ 2008.

Программа установки будет загружать данные из Интернета (примерно 100 мегабайт), поэтому вы можете сэкономить немного трафика, отключив установку Microsoft Silverlight и Microsoft SQL Server, если они вам не требуются.

После установки Visual Studio Express нам понадо­бится добавить в неё OpenMP-компоненты. Легаль­ный бесплатный способ сделать это - установить Windows SDK for Windows Server 2008 and .NET Framework 3.5 . Во время установки этого пакета программ будет произведено обновление Visual Studio. Процесс обновления не смотрит, какая именно версия Visual Studio у вас установлена (Express или Professional), поэтому во время установ­ки будут «случайно» добавлены недостающие компоненты.

Так как мы ставим Windows SDK только ради OpenMP, то нам не нужен тот гигабайт документации, который идёт в комплекте. Рекомендую оставить только следующие элементы:

Рисунок 1. Необходимые нам компоненты SDK

К сожалению, в состав SDK не входит библиотека vcomp90d.dll, поэтому на данный момент в Visual Studio Express вы сможете запускать только OpenMP-программы, откомпилированные в ре­жиме Release. Я нашёл способ обойти и это ограничение, об этом читайте далее (раздел «Отладка OpenMP-программы в Visual Studio Express»).

Использование OpenMP в Visual Studio

После того, как вы выполнили шаги, описанные в предыдущем разделе, уже не важно, какой верси­ей Visual Studio вы пользуетесь. Покажу шаг за шагом, как создать проект с поддержкой OpenMP в этой среде разработки. Прежде всего, нужно запустить Visual Studio, и выбрать File→New→Project... Появится окно создания проекта. Выберите тип проекта «Win32», шаблон - «Win32 Console Application». Введите осмысленное имя проекта, выберите папку для хранения проекта, уберите галочку «Create directory for solution»:

Рисунок 2. Окно создания проекта

Нажмите кнопку «OK», появится окно настройки будущего проекта. Выберите вкладку «Application Settings», и включите галку «Empty project»:

Рисунок 3. Окно настройки будущего проекта

По нажатию кнопки «Finish» проект будет создан. Никаких видимых изменений в главном окне Visual Studio не произойдёт. Только имя проекта в заголовке окна как бы говорит нам о том, что мы ра­ботаем с проектом.

Теперь нажмите Project→Add New Item, появится окно добавления элементов в проект. Добавьте.cpp-файл в проект:

Рисунок 4. Окно добавления элементов в проект

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

#include #include using namespace std; int main(int argc, char **argv) { int test(999); omp_set_num_threads(2); #pragma omp parallel reduction(+:test) { #pragma omp critical cout << "test = " << test << endl; } return EXIT_SUCCESS; } Листинг 1. Простейшая программа, использующая OpenMP

Запустите программу, нажав Debug→Start Without Debugging. Если всё было сделано правильно, программа откомпилируется (если спросит вас, компилировать ли, нажмите «Yes»), затем запу­стится и выведет test = 999:

Рисунок 5. Результат работы программы из листинга 1

«Как же так?! - скажете вы - Ведь программа должна была вывести ноль, причём дважды!». Дело в том, что OpenMP ещё не включен, и поэтому соответствующие директивы были проигнорированы компилятором.

Для включения OpenMP нажмите Project→OMP Properties (OMP - имя проекта из моих примеров). Слева вверху появившегося окна выберите «All Configurations» и в разделе Configuration Properties→C/C++→Language включите «OpenMP Support»:

Рисунок 6. Включаем OpenMP в свойствах проекта

После этого снова запустите программу, нажав Debug→Start Without Debugging. На этот раз про­грамма выведет test = 0 дважды:

Рисунок 7. Результат работы программы из листинга 1 с включённым OpenMP

Ура! OpenMP работает.

Примечание. Если вы используете Visual Studio Express, то выберите текущую конфигурацию «Release», иначе работать не будет (читайте далее):

Рисунок 8. Выбор текущей конфигурации

Как было сказано ранее, даже после установки Windows SDK у нас не будет в наличии необходимой для отладки библиотеки vcomp90d.dll, поэтому мы пока не можем отлаживать OpenMP програм­му в Visual Studio Express. Простое копирование имеющейся библиотеки vcomp90.dll и переимено­вание её в vcomp90d.dll не сработает, ибо не совпадёт контрольная сумма и версия, указанные во встраиваемом в exe-файл манифесте. Поэтому будем «копать» с противоположной стороны.

При компиляции в конфигурации «Debug» («Отладка»), заголовочный файл omp.h требует библио­теку vcompd.lib (она у нас имеется), которая, в свою очередь, требует vcomp90d.dll (отсутству­ет). Лицензия не позволяет нам использовать в приложениях модифицированные заголовочные файлы от Microsoft, поэтому вместо модификации omp.h включим его в нашу программу следую­щим образом, чтобы он не догадался о включённом режиме отладки:

#include #ifdef _DEBUG #undef _DEBUG #include #define _DEBUG #else #include #endif using namespace std; int main(int argc, char **argv) { int test(999); omp_set_num_threads(2); #pragma omp parallel reduction(+:test) { #pragma omp critical cout << "test = " << test << endl; } return EXIT_SUCCESS; } Листинг 2. Включаем omp.h «хитрым» способом

Приведённого действия не достаточно для того, чтобы всё работало (пока мы исправили лишь ма­нифест, встраиваемый в программу). Дело в том, что Visual Studio в режиме отладки по прежнему автоматически (из-за включённого OpenMP) прилинковывает vcompd.lib, требующую vcomp90d.dll. Чтобы это исправить, снова зайдите в настройки проекта (Project→OMP Properties), выберите на этот раз Configuration: «Debug». В разделе Configuration Properties→Linker→Input укажите, что vcompd.lib прилинковывать не нужно, а vcompd.lib - нужно:

Рисунок 9. Заменяем библиотеку в свойствах проекта

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

Рисунок 10. Точка останова

После этого запустите программу в режиме отладки: Debug→Start Debugging (не забудьте вер­нуть текущую конфигурацию «Debug», см. рисунок 8). Программа запустится - и сразу же остано­вится на точке останова. Во вкладке «Threads» мы видим, что программа действительно работает, используя два потока:

Рисунок 11. Отладка OpenMP-программы в Visual Studio Express

Параллельное программирование на OpenMP

Введение..............................................................................................................................................

Параллельное программирование................................................................................................

Написание параллельных программ............................................................................................

Параллельные архитектуры..........................................................................................................

OpenMP................................................................................................................................................

Введение в OpenMP.......................................................................................................................

Программная модель OpenMP......................................................................................................

Как взаимодействуют потоки?.....................................................................................................

Основы OpenMP.................................................................................................................................

Синтаксис.......................................................................................................................................

Параллельные регионы.................................................................................................................

Модель исполнения.......................................................................................................................

Конструкции OpenMP........................................................................................................................

Условия выполнения.....................................................................................................................

Условия private, shared, default.................................................................................................

Условие firstprivate....................................................................................................................

Конструкции OpenMP для распределения работ........................................................................

Параллельный цикл for/DO....................................................................................................

Параллельные секции.............................................................................................................

Конструкция single..................................................................................................................

Условия выполнения (2)..............................................................................................................

Условие if.................................................................................................................................

Условие lastprivatе...................................................................................................................

Условие reduction....................................................................................................................

Условие schedule.....................................................................................................................

Условие ordered.......................................................................................................................

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

Библиотечные функции OpenMP ...................................................................................................

Зависимость по данным...................................................................................................................

Средства синхронизации в OpenMP...............................................................................................

Критическая секция.....................................................................................................................

Атомарна секция..........................................................................................................................

Барьеры.........................................................................................................................................

Фиксация порядка выполнения..................................................................................................

Конcтрукция flush........................................................................................................................

Расширенные возможности OpenMP..............................................................................................

Отладка OpenMP кода......................................................................................................................

Настройка производительности OpenMP кода..............................................................................

Основной подход.........................................................................................................................

Автоматическое расспаралеливание..........................................................................................

Профилирование программы......................................................................................................

Иерархия памяти..........................................................................................................................

Задачи................................................................................................................................................

Задача 1.........................................................................................................................................

Задача 2.........................................................................................................................................

Задача 3.........................................................................................................................................

Задача 4.........................................................................................................................................

Задача 5.........................................................................................................................................

Задача 6.........................................................................................................................................

Введение

Параллельное программирование

Параллельное программирование применяется тогда, когда для последовательной программы требуется уменьшить время ее выполнения, или когда последовательна программа, в виду большого объема данных, перестает помещаться в память одного компьютера. Направление развития в области высокопроизводительных вычислений как раз направлено на решение этих двух задач: создание мощных вычислительных комплексов с большим объемом оперативной памяти с одной стороны и разработка соответствующего ПО с другой.

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

Написание параллельных программ

Разработка параллельных программ (ПП) состоит из трех основных этапов:

Декомпозиция задачи на подзадачи. Идеально, чтобы эти подзадачи работали независимо друг от друга (принцип локальности данных). Обмен данными между подзадачами является дорогой операцией, особенно, если это обмен по сети.

Распределение задачи по процессорам (виртуальным процессорам). В некоторых случаях решение этого вопроса можно оставить на усмотрение среды выполнения ПП.

Написание программы с использованием какой-либо параллельной библиотеки. Выбор библиотеки может зависеть от платформы, на которой программа будет выполняться, от требуемого уровня производительности и от природы самой задачи.

Параллельные архитектуры

В массе своей все вычислительные комплексы и компьютеры делятся на три группы:

Системы с распределенной памятью . Каждый процессор имеет свою память и не может напрямую доступаться к памяти другого процессора.

Разрабатывая программы подобные системы программист в явном виде должен задать всю систему коммуникаци (Передача сообщений – Message Passing). Библиотеки: MPI, PVM, Shmem (Cray only).

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

Подходы к разработке ПО: Threads, директивы компилятора (OpenMP), механизм передачи сообщения.

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