Kurs jest częścią programu Xtreme Computing.

Autorem kursu jest Paweł Przybyłowicz - asystent na Wydziale Matematyki Stosowanej AGH.

Krok 5 - dyrektywy sterujące if i num_threads.

Pamiętamy z Lekcji 4, że aby porównać czas obliczania wartości liczby Pi na jednym i wielu wątkach, musieliśmy umieszczać w programie po dwie prawie identyczne funkcje. Używając dyrektyw if i num_threads możemy znacznie skrócić program i uczynić go przejrzystszym.

Dyrektywa if standardu OpenMP jest tzw. dyrektywą wykonania warunkowego. Pozwala na dynamiczne włączanie lub wyłączanie wielowątkowego przetwarzania dla danego fragmentu kodu. Można jej używać tylko w połączeniu z dyrektywą parallel. Jej składnia jest następująca:

if(skalarne_wyrażenie_logiczne)
Jeśli wartością wyrażenia logicznego jest liczba całkowita różna od 0, co w standardzie języka C/C++ oznacza prawdę, wówczas blok kodu będzie wykonany równolegle przez wiele wątków. Jeśli wartością wyrażenia logicznego jest 0 (fałsz), to blok kodu wykonywany jest sekwencyjnie. Dyrektywy if używamy najczęściej do sprawdzenia czy w bloku parallel jest wystarczająco dużo pracy do wykonania. Odpowiednie jej użycie pozwoli na równoległe wykonanie obliczeń dopiero wtedy gdy ilość danych (rozmiar macierzy, liczba iteracji w pętli for itp.) przekroczy pewną wartość progową. Podczas Lekcji 1 zwróciliśmy uwagę na fakt, że da się zauważyć wyraźne przyśpieszenie w czasie wykonania programu zrównoleglonego dopiero dla dużego zbioru danych. Gdy ilość danych jest mała, to podział na wątki może się nie opłacać, a nawet może spowolnić pracę (dochodzi obsługa wielu wątków).

Dyrektywy num_threads również można używać tylko w połączeniu z dyrektywą parallel. Jej składnia wygląda następująco:

num_threads(skalarne_wyrażenie_o_wartościach_całkowitych).
Określa ona, poprzez wartość tego skalarnego wyrażenia, ile wątków ma być użytych do wykonania bloku kodu objętego dyrektywą parallel.

Używając tych dyrektyw możemy następująco zmodyfikować funkcję count_pi1_omp z Lekcji 4.

Program 05/01 - step_0501.c

#include <stdio.h> #include <stdlib.h> #include <time.h> #include <omp.h> #define THRESHOLD 1000000 // Metoda Wallis'a obliczania wartosci liczby Pi z wykorzystaniem OpenMP. void count_pi1_omp(double *pi, int n_thr, long int acc) { double tmp = 1.0, a_n; long int i; #pragma omp parallel for schedule(static) \ if (acc > THRESHOLD) num_threads(n_thr) \ default(none) shared(acc) private(a_n, i) \ reduction(* : tmp) for (i = 1; i <= acc; i++) { a_n = (double)(4.0*i*i / (4.0*i*i - 1.0)); tmp = tmp * a_n; } *pi = (double)(2.0 * tmp); } // Program glowny int main(int argc, char *argv[]) { int i, N_thr; long int ACC; double p; time_t begin_t, end_t; printf("Podaj liczbe watkow jaka chcesz uzyc:"); scanf("%d", &N_thr); printf("Podaj liczbe iteracji (dokladnosc obliczen):"); scanf("%ld", &ACC); printf("Bez OpenMP:\n"); begin_t = time(NULL); count_pi1_omp(&p, 1, ACC); end_t = time(NULL); printf("Metoda Wallis'a Pi = %f.\n" , p); printf("Czas wykonywania obliczen: %f.\n\n", difftime(end_t, begin_t)); printf("Z OpenMP:"); begin_t = time(NULL); count_pi1_omp(&p, N_thr, ACC); end_t = time(NULL); printf("Metoda Wallis'a Pi = %f.\n" , p); printf("Czas wykonywania obliczen: %f.\n\n", difftime(end_t, begin_t)); return 0; }

Funkcja count_pi1_omp posiada teraz na wejściu trzy zmienne. Do pierwszej, tak jak poprzednio będziemy zapisywać wynik obliczeń. Podając funkcji wartość zmiennej n_thr ustalamy, za pomocą dyrektywy num_threads, ile wątków ma być użytych do zrównoleglenia pętli for. Trzecim parametrem acc określamy liczbę iteracji w pętli, czyli dokładność obliczeń. Wartość progową ustaliliśmy na 1000000 iteracji. Jeśli wartość zmiennej acc przekroczy wartość progową THRESHOLD wówczas do obliczeń używamy tylu wątków ile podaliśmy w zmiennej n_thr. Jeśli wartość zmiennej acc jest mniejsza, wówczas obliczenia przeprowadzana są sekwencyjnie, za pomocą jednego wątku. Obliczenia przeprowadzane są sekwencyjnie również wtedy, gdy n_thr=1. W takim wartość parametru acc nie ma żadnego znaczenia.

W powyższym programie użyliśmy prawie wszystkich poznanych dotąd dyrektyw (oprócz sections i section). Standard OpenMP pozwala na zapisanie dyrektyw w funkcji count_pi1_omp w bardziej czytelny sposób.

Program 05/02 - step_0502.c

// ... void count_pi1_omp(double *pi, int n_thr, long int acc) { double tmp = 1.0, a_n; long int i; #pragma omp parallel if(acc >THRESHOLD) num_threads(n_thr) \ default(none) shared(acc) reduction(* : tmp) { #pragma omp for schedule(static) private(a_n, i) for (i = 1; i <= acc; i++) { a_n = (double)(4.0*i*i / (4.0*i*i - 1.0)); tmp = tmp * a_n; } } *pi = (double)(2.0 * tmp); } // ...

Forma ta ma nie tylko walory estetyczne. Taki sposób zapisu będziemy wykorzystywać w bardziej zaawansowanych konstrukcjach, dlatego chcemy z nim zaznajomić Czytelnika już teraz.

Przy okazji omawiania opcji num_threads powróćmy na chwilę do opcji sections. Podczas Lekcji 3 omawialiśmy wstępnie sposób rozdziału dwóch zadań pośród dostępne wątki. W ogólnym przypadku, jeśli w programie jest więcej bloków kodu ograniczonych dyrektywą sections niż dostępnych wątków, wówczas kilka lub wszystkie wątki wykonują wiele bloków kodu. Spójrzmy na poniższy fragment kodu:

Program 05/03 - step_0503.c

// ... #pragma omp parallel sections num_threads(2) private(p1, p2) { #pragma omp section { function_1(); } #pragma omp section { function_2(); } #pragma omp section { function_3(); } } // ...

W tym przypadku jeden wątek będzie musiał wykonać dwie funkcje, natomiast drugi wątek tylko jedną.

Ćwiczenia:

  1. Wykorzystując dyrektywy if oraz num_threads zmodyfikuj również funkcje count_pi2_omp oraz count_pi12_omp z Lekcji 4.
  2. Wartość stałej THRESHOLD została przyjęta w Program 05/01 arbitralnie. Wielokrotnie uruchamiając ten program na swoim komputerze, spróbuj określić taką wartość THRESHOLD, przy której opłaca się rozdzielać obliczenia na większą liczbę wątków.
  3. Dopisz dyrektywę num_threads do programu z Lekcji 1. Przetestuj czas wykonywania programu dla dwóch i więcej wątków.
  4. W programie Program 03/02 (Lekcja 3) dodaj w sekcji parallel opcje num_thread oraz poniższą sekcję:
    #pragma omp parallel sections num_threads(2) private(p1, p2) { // ... #pragma omp section { printf("Komunikat wypisany przez watek nr %d.\n", omp_get_thread_num()); } // ... }
    Sprawdź jak teraz zostaną rozdzielone trzy zadania pomiędzy dwa wątki.

Kolejne lekcje tutorialu:

OpenMP.pl - polski portal użytkowników standardu OpenMP.