1. Zasady projektowania aplikacji wielowątkowych. 2. Funkcje wspomagające równoległość przetwarzania. Wykład PR 2012
Zasady projektowania aplikacji wielowątkowychprzegląd A. Określenie zadań niezależnych B. Implementacja równoległości na najwyższym poziomie kodu duże ziarna przetwarzania rzadkie synchronizacje. Granulacja gruboziarnista jest wtedy, gdy duża jest odległość między punktami synchronizacji przetwarzania. C. Uwzględniać skalowalność bazującą na tym co jest możliwą przyczyną wzrostu złożoności: Przewidywane kolejne niezależne etapy analizy danych czy więcej danych przetwarzanych w ten sam sposób - praca dla kolejnych procesorów Dekompozycja oparta na danych dostarcza rozwiązań lepiej skalowalnych D. Zastosowanie bibliotek i bibliotek thread safe funkcje nadające się do rekurencji (bez współdzielonych danych) lub konieczna dodatkowa synchronizacja dla zasobów współdzielonych. 2
Zasady projektowania aplikacji wielowątkowych D. Wykorzystanie adekwatnego do potrzeb modelu wątkowości: Open MP, wątki bezpośrednie tylko to co niezbędne a. minimalizacja złożoności kodu, b. ocena efektywności za pomocą wersji o niewielkiej złożoności kodu E. Brak założeń o kolejności obliczeń (niesynchronizowanych wprost). F. Maksymalny stopień zastosowania lokalnych dla wątku zmiennych, ochrona współdzielonych zmiennych poprzez związane z nimi zamki, co najwyżej jeden zamek dla zmiennej; wiele zmiennych chronionych jednym zamkiem, gdy niewielka szansa wąskiego gardła dostępu. 3
Granulacja a efektywność w równoległości Granulacja jest określona przez złożoność - wielkość pracy zadania. Właściwy rozmiar zadania jest niezbędny do osiągnięcia efektywności przetwarzania równoległego. Zadania zbyt duże brak zrównoważenia. Zadania zbyt małe-narzut komunikacyjny, synchronizacyjny. 4
Przykład: wyznaczanie liczb pierwszych róŝne warianty rozwiązania 01#pragma omp parallel 02 { intj, limit, prime; 03 #pragmaompfor schedule(dynamic, 1) 04 for(i = 3; i <= 1000000; i += 2) { 05 limit = (int) sqrt((float)i) + 1; 06 prime= 1; // załóżmy że jest pierwsza 07 j = 3; 08 while(prime&& (j <= limit)) { 09 if(i%j== 0) prime= 0; 10 j += 2; 11 } 13 if(prime) { 14 #pragma omp critical 15 { 16 numprimes++; 17 if(i%4 == 1) nump41++; // 4k+1 pierwsze 18 if(i%4 == 3) nump43++; // 4k-1 pierwsze 19 } 20 } 21 } 22 }// dlaczego dynamic, dlaczego critical, jaki koszt? 5
01#pragma omp parallel 02 { int j, limit, prime; 03 #pragma omp for schedule(dynamic,1) reduction(+:numprimes,nump41,nump43) 04 for(i = 3; i <= 1000000; i += 2) { 05 limit = (int) sqrt((float)i) + 1; 06 prime = 1; // załóżmy że jest pierwsza 07 j = 3; 08 while (prime && (j <= limit)) { 09 if (i%j == 0) prime = 0; 10 j += 2; 11 } 13 if (prime) { 16 numprimes++; 17 if (i%4 == 1) nump41++; // 4k+1 pierwsze 18 if (i%4 == 3) nump43++; // 4k-1 pierwsze 19 20 } 21 } 22 } // usunięcie synchronizacji 6
01#pragma omp parallel 02 { intj, limit, prime; 03 #pragma omp for schedule(static, 100) reduction(+:numprimes,nump41,nump43) 04 for(i = 3; i <= 1000000; i += 2) { 05 limit = (int) sqrt((float)i) + 1; 06 prime= 1; // załóżmy że jest pierwsza 07 j = 3; 08 while(prime&& (j <= limit)) { 09 if(i%j== 0) prime= 0; 10 j += 2; 11 } 13 if(prime) { 16 numprimes++; 17 if(i%4 == 1) nump41++; // 4k+1 pierwsze 18 if(i%4 == 3) nump43++; // 4k-1 pierwsze 19 20 } 21 } 22 } 23 Czy static100 wystarczy? co oznacza, czy zapewni zrównoważenie? 7
Podsumowanie granulacja a efektywność Zrozumienie wymagańkomunikacyjnych, synchronizacji, narzut przesyłania komunikatów, narzutu współdzielenia danych przechowywanych w poszczególnych poziomach pamięci. Znajomość kosztówuruchomienia równoległości i synchronizacji. Właściwy stosunekilości pracy zadania równoległego do narzutów wielowątkowości. Określenie właściwej granulacji przydziału pracy do wątku. 8
Równoległość zadań, a równoległość wątków metoda szeregowania zadań Wątki w OpenMP>= 3.0 Zarządzanie pulą zadań w ramach każdego wątku. Pula zadań zorganizowana jako kolejka zadań z możliwością pobierania z dwóch końców. Pula traktowana jest lokalnie jak stos, nowe zadanie umieszczane na szczycie i ze szczytu pobierane po zakończeniu realizacji poprzedniego pobieranie najnowszego zadania - możliwe, że korzystającego z zawartych w pp danych. Brak zadań w lokalnej puli powoduje pobieranie zadania z puli innego wątku zadania najdłużej tam oczekującego. Program planisty uniemożliwia przerwanie realizacji przydzielonego wątkowi zadania (i realizacja innego) np. w przypadku blokady, przetwarzanie wątku jest wstrzymywane. 9
int fibopenmp( int n ) { int i, j; if( n < 10 ) { return fibserial(n); } else { // tworzenie zadania #pragma omp task shared( i ), untied i = fibopenmp( n -1 ); // tworzenie kolejnego zadania #pragma omp task shared( j ), untied j = fibopenmp( n -2 ); // oczekiwanie na zakończenie zadań #pragma omp taskwait return i + j; } } // procedura realizowana przez jeden wątek w regionie równoległym, zadania liczone potencjalnie przez wszystkie wątki. 10
Co może stanowić problem? double sum=0.0, sum_local[num_threads]; #pragmaompparallel num_threads(num_threads) { intme = omp_get_thread_num(); sum_local[me] = 0.0; #pragmaompfor for (i= 0; i< N; i++) sum_local[me] += x[i] * y[i]; #pragmaompatomic sum += sum_local[me]; } 11
False sharing W systemie wieloprocesorowym SMP (pamięć logicznie współdzielona) każdy procesor posiada lokalną pp. System pamięci musi dbać o spójność pamięci. False sharing procesory modyfikują wartości umieszczone w tej samej linii pp. Powoduje to unieważnienia linii danych z powielonymi danymi, następnie uaktualnienie, w rezultacie spadek efektywności przetwarzania. 12
False sharing 13
double sum=0.0; volatilesum_local[num_threads]; #pragmaompparallel num_threads(num_threads) { intme = omp_get_thread_num(); sum_local[me] = 0.0; #pragmaompfor for (i= 0; i< N; i++) sum_local[me] += x[i] * y[i]; #pragmaompatomic sum += sum_local[me]; } W przypadku optymalizacji kodu efekt false sharing niewidoczy dzięki zastosowaniu tymczasowych prywatnych zmiennych wątku (gdzie są w kodzie?). Problem pojawi się w trybie debug lub po zastosowaniu dla powyższego kodu dyrektywy volatile. Objects that are declared as volatile are not used in certain optimizations because their values can change at any time. The system always reads the current value of a volatile object when it is requested, even if a previous instruction asked for a value from the same object. Also, the value of the object is written immediately on assignment. [http://msdn.microsoft.com] 14
Rozwiązania praktyczne unikanie false sharing _aligned_malloc(); <malloc.h> void * _aligned_malloc( size_t size, size_t alignment ); size żądana wielkość przydziału pamięci alignment parametr wartości granicy wyrównania, potęga 2 15
Wyrównanie adresu lokacji obiektów w pamięci do adresu będącego wielokrotnością określonej wartości #define CACHE_LINE 64 #define CACHE_ALIGN declspec(align(cache_line)) CACHE_ALIGN int i; CACHE_ALIGN int array[128]; typedef CACHE_ALIGN struct { int a; } S1; S1 array[10]; // wyrównanie do granicy 64 bajtów każdego elementu array declspec(align(8)) struct S2 { int a, b, c, d; }; 16
Powinowactwo wątków SMP (Windows) Funkcje wymuszają uruchomienie wątku na specyficznym podzbiorze procesorów. Ustalenie powinowactwa wątków może kolidować z regułami szeregowania i może utrudniać modułowi szeregującemu uzyskanie efektywności przetwarzania w całym systemie. System reprezentuje powinowactwo za pomocą maski bitowej maski powinowactwa wątków, rozmiar maski jest równy maksymalnej liczbie procesorów w systemie, a ustawione bity (=1) określają podzbiór procesorów. System określa pierwotny stan maski. Funkcje: GetProcessAffinityMask SetProcessAffinityMask SetThreadAffinityMask 17
Funkcje powinowactwa Windows GetProcessAffinityMask -uzyskanie aktualnej maski powinowactwa dla wszystkich wątków danego procesu. SetProcessAffinityMask -ustalenie aktualnej maski powinowactwa dla wszystkich wątków danego procesu. SetThreadAffinityMask -ustalenie aktualnej maski powinowactwa dla pojedynczego wątku. Powinowactwo wątków musi być podzbiorem powinowactwa procesu. Dla systemów z więcej niż 64 procesorami maska powinowactwa określana jest dla pojedynczej grupy procesorów. 18
Funkcje pomocnicze (szeregowanie) Windows SetThreadIdealProcessorokreśla preferowany procesor dla wątku, lecz nie gwarantuje jego wyboru, jest informacją pomocniczą dla modułu szeregującego. GetLogicalProcessorInformationdostarcza informacji o procesorach logicznych w systemie i zależnościach między nimi (np. współdzielony rdzeń, procesor, węzeł NUMA) (dla aktualnej grupy procesorów). 19
Wspomaganie przydziału procesów/wątków i pamięci (Linux) Rozszerzenia GNU deklarowane w sched.h Typ danych: cpu_set_t - pozwala okreslić maskę powinowactwa (ustalić gdzie uruchomic wątek) każdy bit odpowiada innemu CPU Makro int CPU_SETSIZE- dostarcza liczby procesorów obsługiwanych przez cpu_set_t Makrado manipulacji na typie cpu_set_t void CPU_ZERO(cpu_set_t *set) void CPU_SET(int cpu, cpu_set_t *set) CPU_CLR(int cpu, cpu_set_t *set) int CPU_ISSET(int cpu, const cpu_set_t *set) 20
Wspomaganie przydziału procesów/wątków i pamięci (Linux) Funkcje do poznania i określenia maski powinowactwa dla procesów i wątków: int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *cpuset) int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *cpuset Dodatkowe narzędzia producenta sprzętu dla SMP (np. Intel Xeon 5300) do określania pozycji w masce powinowactwa poszczególnych rdzeni dostępnych w systemie przynależnych lub nie do jednego procesora, współdzielących (np. parami) pamięci podręczne L2 lub L3. 21
Szeregowanie wielu wątków w systemie wieloprocesorowym NUMA Dane współdzielone kolejne wątki należy przydzielać do różnych rdzeni tego samego procesora. Dane niezależne kolejne wątki należy przydzielać do różnych procesorów. 22
ZAGADNIENIA LOKALNOŚCI DANYCH - NUMA Realizować w kodzie dostęp do danych węzła przez wątek będący lokalnym dla tego węzła W przypadku danych niezależnych każdy wątek powinien alokować pamięć i zainicjować (zapis) dane, które będzie wykorzystywał -pozwala to na podjęcie przez system operacyjny właściwych decyzji odnoście lokalizacji danych ( w pamięci węzłów). Wywłaszczenie wątku powoduje niebezpieczeństwo jego powrotu na inny węzeł i inicjowania tam kolejnych danych co daje rozproszony fizycznie system danych. Warto zatem definiować powinowactwo (affinity) wątków, gdyż ułatwia to efektywne szeregowanie wątków. 23
LOKALNOŚĆ DANYCH NUMA Zalecane podejścia: Dane współdzielone warto dane współdzielone przez wiele wątków rozproszyć przeplot lokacji stron danych aplikacji, aby dostęp do nich mógł być zrównoleglony, a korzystanie z pamięci nie było wąskim gardłem. Usunięcie niekoniecznego współdzielenia lini pp między wątkami (64 bajty linii pp) -usunięcie nieprawdziwego współdzielenia (ang. false sharing). Stosowanie zamków korzystających z pamięci podręcznej (minimalizacja ilości blokad na magistralach między węzłami zapobieganie serializacji) (cachable locks). 24
NUMA STRUKTURA SYSTEMU (Windows) Określenie struktury systemu jest możliwe za pomocą nastepujących funkcji: GetNumaHighestNodeNumber dostarcza najwyższy numer węzła -nie jest to koniecznie liczba węzłów, sąsiednie numery nie muszą dotyczyć sąsiednich w architekturze węzłów GetProcessAffinityMask poznanie liczby procesorów w systemie GetNumaProcessorNode określa węzeł dla danego procesora GetNumaNodeProcessorMask określa listę procesorów danego węzła Dodatkowo także w systemie NUMAużyteczne są: GetProcessAffinityMask, SetProcessAffinityMask, SetThreadAffinityMask oraz GetLogicalProcessorInformation 25
FUNKCJE PRZYDZIAŁU PAMIĘCI -NUMA (Windows)-bez VirtualAlloc rezerwuje stronę pamięci wirtualnej lokowaną fizycznie na lokalnym węźle NUMA (na tym na którym nastąpi pierwszy dostęp do tej strony lub preferowanym przez system ) (zapobieganie rozproszeniu danych między węzłami) VirtualAllocExNuma-rezerwuje stronę pamięci wirtualnej lokowaną fizycznie na preferowanym węźle NUMA VirtualFree, VirtualFreeEx - zwalniają pamięć 26
WSPOMAGANIE PRZYDZIAŁU PROCESÓW/WĄTKÓW I PAMIĘCI -NUMA (Linux) -bez cat /proc/cpuinfo - listing dostępnych procesorów w systemie int NCPUs = sysconf(_sc_nprocessors_conf); zwraca liczbę procesorów dostępnych dla systemu operacyjnego (#include <unistd.h> ) Interfejs programistyczny NUMA API (do sterowania powinowactwem wątków i pamięci) składa się z: części jądra zarządzającej przydziałem pamięci (ważne w NUMA, gdyż w odróżnieniu od pamięci podręcznej gdzie dane idą za wątkiem przydzielonym do procesora, w przypadku ulokowania danych w pamięci rozproszonej raz przydzielone do określonej lokacji dane wracają zawsze do niej), biblioteki przestrzeni użytkownika libnuma dołączanej do aplikacji i wywołania linii zleceń: numactl pozwala na zarządzanie powinowactwem wątków i procesów, sterowaniem przydziału pamięci w NUMA (przeplot stron między węzłami) numactl --cpubind=0--membind=0,1 proces uruchamia proces na węźle 0 z pamięcią przydzieloną do węzłów 0 i 1. numactl hardware dostarcza informacji o dostępnych węzłach w systemie 27
Ćwiczenie: Optymalizacja transmisji danych między rdzeniami (producent-konsument) Systemy z współdzieloną między rdzeniami pp L3 Wątek uruchomiony na jednym rdzeniu produkuje dane konsumowane przez inny wątek na sąsiednim rdzeniu tego samego procesora (efektywna wymiana danych bezpośrednio przez pp L3). pp write-back -dane usuwane z L1 trafiają do L2, zaś usuwane z L2 do L3. MIN MAX L1 L2 L3 28
Ćwiczenie: Optymalizacja transmisji danych między rdzeniami (wynik) Określenie odległości w buforze pomiędzy danymi dostarczanymi, a pobieranymi. odległość jest większa od sumy rozmiarów L1 i L2 (dlaczego?) aby konsument znalazł interesujące go dane w pp L3. odległość ta nie może być jednocześnie zbyt duża > L3+L2+L1, aby nie spowodować usunięcia z pp L3 danych nieskonsumowanych jeszcze przez konsumenta. Określenie odległości w buforze pomiędzy obszarem wykorzystywanym, a dostepnym w L3 odległość jest większa od sumy rozmiarów L1 i L2 (dlaczego?) Aby producent znalazł interesujący go obszar bufora z powrotem w pp L3 (w odpowiednim stanie CC protokół MOESI). 29
SYNCHRONIZACJA W WINDOWS Wykorzystuje: zdarzenia (events), zamki(mutex), semafory i liczniki Zamki-CreateMutex, OpenMutex, ReleaseMutex, WaitForSingleObject, WaitForMultipleObjects (funkcje oczekiwania) Zdarzenia - CreateEvent, SetEvent, ResetEvent, funkcje oczekiwania Semafory- CreateSemaphore, ReleaseSemaphore, OpenSemaphore, funkcje oczekiwania Liczniki-CreateWaitableTimer, SetWaitableTimer, funkcje oczekiwania 30
Literatura http://msdn.microsoft.com opis bibliotek systemów Windows WHITE PAPER Intel Xeon processor 3500 and 5500 series Intel Microarchitecture Family 10h AMD Phenom II Processor Product Data Sheet Software Optimization Guide for AMD Family 10h Processors NUMA optimization in Windows ApplicationsMichael Wall, AMD 31