Przejdź do treści

OpenMP

logo OpenMP

Wprowadzenie

OpenMP (Open Multi-Processing) to powszechnie używany standard do zrównoleglania obliczeń w architekturze pamięci współdzielonej, tj. w obrębie jednego komputera i pojedynczego procesu. Oparty o wiele wątków z dostępem do tej samej pamięci. Przewidziany głównie do wykorzystania możliwości wielordzeniowych procesorów, obecnie umożliwia również offloading na akcelerator (tj. delegowanie obliczeń do wykonania np. na układzie GPU).

Dostępność

Dostępny dla języków C, C++ oraz Fortran, wspierany przez wszystkie popularne kompilatory (gcc/gfortran, clang/flang, Intel icc/icx/ifort, AMD aocc, NVIDIA nvfortran/nvc). W przypadku klasycznego zrównoleglania na samym procesorze nie wymaga instalacji dodatkowego oprogramowania. Kompilacja z obsługą OpenMP zwykle wymaga dodania odpowiedniej opcji kompilacji (np. -fopenmp dla gcc).


Szczegóły

Zrównoleglanie odbywa się poprzez wzbogacenie kodu o dyrektywy, powodujące np.:

  • wykonanie wskazanej sekcji kodu przez wiele wątków,
  • automatyczny podział iteracji pętli między wiele wątków i ich równoległe wykonanie,
  • tworzenie niezależnych zadań wykonywanych przez różne wątki,
  • wykonanie wskazanej sekcji kodu na akceleratorze (tzw. offload).

schemat zrownoleglania

schemat zrównoleglenia sekwencyjnego kodu [źródło]1

Wątki mają dostęp do tych samych danych, gdyż pracują na współdzielonej pamięci. Takie podejście pozwala w łatwy sposób akcelerować sekwencyjny kod, wprowadzając zrównoleglenie obliczeń krok po kroku, tylko do wskazanych części aplikacji. Jednocześnie kod po zignorowaniu dyrektyw może być dalej poprawnym kodem sekwencyjnym.

Standard OpenMP pozwala także na:

  • synchronizację wątków poprzez bariery, sekcje krytyczne lub operacje atomowe,
  • bezpośrednie określenie sposobu wektoryzacji obliczeń za pomocą dyrektyw SIMD.

Przykład kodu

przykładowy kod

zrównoleglone obliczanie liczby PI [źródło]

Biblioteka i zmienne środowiskowe

Oprócz dyrektyw standard określa również funkcje z biblioteki uruchomieniowej (omp.h dla C/C++, moduł omp_lib dla Fortran). Służą one głównie do kontroli zachowania środowiska OpenMP w trakcie wykonania ─ pozwalają np. ustawić liczbę wątków jaka ma być użyta do zrównoleglenia (omp_set_num_threads) albo odczytać unikatowy identyfikator wątku w zrównoleglonej sekcji kodu (omp_get_thread_num).

Na zachowanie OpenMP można również wpływać poprzez zmienne środowiskowe, które podaje się na etapie uruchomienia programu. Pozwalają one na podobne ustawienia co funkcje z biblioteki, np. OMP_NUM_THREADS definiuje domyślną liczbę wątków. Dodatkowo umożliwiają ustawienie koligacji (thread affinity), tj. konfigurację sposobu rozmieszczania wątków w wielordzeniowym procesorze. Służą do tego takie zmienne jak OMP_PROC_BIND oraz OMP_PLACES. Warto dodać, że w zależności od używanego kompilatora możemy mieć do czynienia z różnymi implementacjami biblioteki OpenMP. Mogą one definiować swoje własne rozszerzenia, przykładowo środowisko Intel udostępnia zmienną KMP_AFFINITY, natomiast GCC dostarcza GOMP_CPU_AFFINITY.

Wartości podane w zmiennych środowiskowych należy traktować jako początkowe ustawienie różnych parametrów środowiska OpenMP, a ewentualne wywołania funkcji bibliotecznych nadpisują je. Należy jednak pamiętać, że na poziomie dyrektyw mogą być podane jeszcze inne wartości i wtedy ustawienia zadane zmiennymi środowiskowymi lub za pośrednictwem biblioteki nie mają wpływu na wykonanie takiej dyrektywy.


Informacje o standardzie

Standard jest dobrze ugruntowany i systematycznie rozwijany przez głównych producentów sprzętu i oprogramowania. Zapoczątkowany w 1997 roku, podąża za rozwojem możliwości sprzętowych i technik programowania. Co 5 lat ukazuje się nowa główna wersja ─ w roku 2018 został wydany standard 5.0, a standard 6.0 zaplanowano na rok 2023. Między głównymi wersjami mogą pojawiać się poprawki. Ostatnia aktualizacja standardu to wersja 5.2, wydana w listopadzie 2021.

Ewolucja standardu przebiegała następująco:

grafika ewolucja standardu

[źródło]

  • pierwsze wersje określiły podstawowe funkcjonalności (przede wszystkim zrównoleglanie pętli);
  • wersja 3.0 wprowadziła możliwość tworzenia zadań (task parallelism) i specyfikowania między nimi zależności poprzez określanie zależności od danych wejściowych i wyjściowych;
  • wersja 4.0 wprowadziła możliwość offloadingu na akcelerator (w domyśle na GPU) oraz wsparcie dla jawnej wektoryzacji SIMD i specyfikowania thread affinity;
  • wersja 5.0 określiła m.in. współpracę standardu z funkcjonalnościami C++17 oraz Fortran 2008.

ewolucja standardu w formie filmu [źródło]

Należy pamiętać, że standard jest stopniowo wdrażany przez każdy kompilator z osobna. Czasami potrzeba nawet kilku lat zanim najnowsza wersja zostanie w pełni zaimplementowana. Pracując z konkretnym kompilatorem trzeba sprawdzić, które funkcjonalności już wspiera ─ patrz lista kompilatorów a wdrożenie standardu. W większości standard 4.5 został już zaimplementowany, a wsparcie dla 5.0 jest systematycznie rozszerzane. Offloading na GPU może mieć różne ograniczenia i każdy kompilator wspiera wybrany zakres architektur GPU.


Linki


  1. Wikipedia user A1, CC BY 3.0, via Wikimedia Commons 


Ostatnia aktualizacja: 15 września 2023