Biblioteca Openmp

OpenMP nu este doar o bibliotecă de programare paralelă, ci și un standard acceptat oficial pentru limbile C, C ++ și Fortran (și neoficial pentru alte limbi, de exemplu, Free Pascal, de exemplu [1]). OpenMP funcționează numai pe arhitecturile de memorie partajată.

Biblioteca OpenMP este proiectată astfel încât programatorul să poată scrie mai întâi un program secvențial. depanare (la urma urmei, depanarea unui program paralel este foarte dificil), și apoi, paralelize treptat. completarea directivelor OpenMP.

Nu este un secret că majoritatea timpilor de execuție, programele se desfășoară în interiorul ciclurilor. Poate de aceea, în OpenMP există o directivă specială a bucla paralelă și cea mai mare parte a articolului este dedicată acesteia.

În articol sunt analizate două exemple de paralelizare:

  • calcularea sumei elementelor matrice;
  • calcularea integralului prin metoda dreptunghiurilor.

Toate exemplele acestui articol sunt scrise în C ++. Compilatorul gcc a fost folosit (dar puteți utiliza și alții, numai acele chei trecute la compilator vor fi diferite). Pentru a sprijini OpenMP. gcc trebuie să accepte comutatorul -fopenmp.

1 Calcularea sumei elementelor matrice

După cum sa menționat mai sus, OpenMP ne permite să ne paralelizăm programul treptat, dar mai întâi puteți scrie un program secvențial și să depanem ceea ce am făcut:

Funcția este extrem de simplă și vedem în ea o buclă, ale cărei iterații ar putea fi împărțite în fire separate. Când folosim alte biblioteci, ar trebui să distribuim iterațiile manual, în timp ce fiecare fir va calcula o parte din sumă, iar în final firele s-ar sincroniza într-un fel și ar fuziona rezultatele.

OpenMP pentru noi, de asemenea, vă permite să distribuiți lucrarea manual, dar de cele mai multe ori nu este necesar, deoarece există instrumente mai convenabile.

În a treia linie, directiva paralelă specifică o zonă paralelă. care este plasat în interiorul acolade curba. La începutul acestei zone, sunt create fire, numărul cărora poate fi specificat utilizând opțiunea num_threads (în exemplu, regiunea rulează în două fire).

Un flux poate utiliza ambele variabile locale. și împărtășite. Variabilele comune (partajate) sunt comune tuturor fluxurilor, în mod evident, acestea trebuie să fie foarte atenți la locul de muncă (în cazul în care cel puțin un fir schimbă valoarea unei variabile - toate celelalte trebuie să aștepte - acest lucru poate fi aranjate prin intermediul OMP). Toate constantele sunt partajate - în exemplul nostru, variabilele "a" și "n" sunt partajate.

Un fir poate conține un set de variabile locale (opțiuni private și firstprivate), pentru care copii sunt generate în fiecare fir. Pentru variabilele declarate în lista privată, valoarea inițială nu este definită, pentru prima persoană privată - este luată din fluxul principal. Toate variabilele declarate în regiunea paralelă sunt locale (variabila "i" din exemplul nostru).

Reducerea opțiunilor. stabilește, de asemenea, o variabilă locală (sumă) și o operație care va fi efectuată pe variabilele locale la ieșirea din regiunea paralelă ("+"). Valoarea inițială a variabilelor locale, în acest caz, este determinată de tipul operațiunii (pentru operațiunile aditive - zero pentru cele multiplicative - unitate).

Cea de-a cincea linie a exemplului specifică începutul buclei paralele. Iterațiile ciclului după directiva corespunzătoare vor fi distribuite între fire.

Directiva pentru directivă are multe opțiuni, mai multe despre care puteți citi în manualele groase. În interiorul buclă, puteți seta opțiunile private și cele de prim-persoană. dar în plus, un număr de noi. De exemplu, programul determină modul de alocare a iterațiilor între fire, iar acum elimină sincronizarea implicită a barierei, care, în mod implicit, se află la sfârșitul bucla.

La sfârșitul articolului este atașată o arhivă cu codul sursă al exemplului. Puteți verifica dacă programul paralel rulează mult mai repede.

Biblioteca Openmp

Fig. 1 paralelizare OMP

Suma elementelor matrice este considerată foarte rapidă, astfel încât pentru a obține rezultatele afișate în figură, a trebuit să ne executăm funcția de 10 ori. Cea mai mare parte a timpului de lucru al programului este ocupată de umplerea matricei cu numere aleatoare, care se efectuează într-un singur fir (din acest motiv, rezultatele sunt ușor estompate). Cu toate acestea, un cititor leneș poate paraliza cu ușurință umplerea unei matrice :).

2 Calculul integralului prin metoda dreptunghiurilor

2.1 Numărul de dreptunghiuri

Articolul folosește metoda dreptunghiurilor stângi, esența cărora este redusă la faptul că regiunea dintre graficul funcției integrabile și axa abscisă este divizată într-un număr specificat de dreptunghiuri. Pentru fiecare dreptunghi, zona este calculată, suma zonelor este rezultatul. Termenul "dreptunghiuri stângi" înseamnă că colțul din stânga sus al fiecărui dreptunghi se află direct pe funcția integrabilă.

Pe C ++, algoritmul descris poate fi exprimat după cum urmează (o versiune paralelă este imediat afișată, deoarece nu este nimic nou):

În codul de mai sus nu există nimic fundamental nou, putem observa doar că descrierea secțiunii paralele nu specifică numărul de fire - în acest caz va fi determinată de valoarea variabilei de mediu OMP_NUM_THREADS. Codul explică metoda de dreptunghiuri (pentru cei care au uitat) - atunci vom examina alte opțiuni pentru implementarea acestei metode. În plus, prin acest exemplu simplu, vă puteți uita la degradarea acurateței prin creșterea numărului de dreptunghiuri.

Fig. 2 degradarea preciziei cu un număr mare de dreptunghiuri

Ca argument, programul din Fig. 2 ia numărul de dreptunghiuri, integrează funcția x * x pe intervalul [-1, 1]. Valoarea exactă a integratorului este de 2/3. Cele mai multe dreptunghiuri pe un interval delimitat - cu atât mai mic fiecare dreptunghi, prin urmare, dreptunghiurile "se aliniază dens la grafic" și precizia ar trebui să crească. Precizia crește într-adevăr, vedem acest lucru cu o creștere a numărului de dreptunghiuri de la 10 la 100. Cu toate acestea, cu un număr foarte mare de precizie lor scade brusc. OpenMP nu este acolo (rețineți că tasta -fopenmp nu a fost utilizată în timpul compilării).

În exemplul de mai sus, precizia scade deoarece valoarea foarte mică a etapei (h) este înmulțită cu valoarea foarte mare a contorului (i). În rândurile inferioare ale pasului există gunoi, acest lucru nu poate fi evitat. Acest gunoi se procesează în loc de datele de care avem nevoie. Vedem o eroare pe care computerul nu o cunoaște, programul nu va arunca excepții în acest caz, este deosebit de dificil să se determine cauza unor astfel de erori în programele paralele.

OpenMP pare să nu aibă nimic de-a face cu el, dar se pare că numărul firelor de lucru și ordinea de calcul pot influența acuratețea. Cititorul poate verifica acest lucru, de exemplu prin calcularea secvențială a sumei seriei 1 / (x * x) mai întâi când x variază de la 1 la 100000000. și apoi, în ordine inversă. Rezultatele calculelor vor fi diferite, iar calculul în ordine inversă oferă un rezultat mai precis (dacă nu este clar de ce și este foarte interesant - anunțați-mă, voi scrie un articol pe această temă). OpenMP nu garantează o anumită ordine de calcule și, prin urmare, poate apărea o eroare neașteptată, acest lucru fiind indicat în mod repetat în unele surse [2].

2.2 Precizia integrării este specificată

Într-un fel sau altul, în exemplul anterior, am putea paraleli cu ușurință un program secvențial (așa cum intenționează dezvoltatorii OpenMP). Se poate părea că acest lucru va fi întotdeauna, dar nu este așa. Vom schimba ușor starea problemei anterioare - acum ne este dată precizia care trebuie atinsă și nu numărul de dreptunghiuri.

Programul va rupe treptat pasul și va număra cu acesta suma zonelor dreptunghiurilor până când diferența dintre suprafața totală a etapei curente și cea precedentă va fi mai mică decât precizia. Exprimați această idee în cod după cum urmează:

Acest lucru nu este în mod evident cel mai bun cod, dar este dat să demonstreze că OpenMP nu este capabil să paraleze programul în unele cazuri (cum ar fi acesta). Pentru a aloca iterații între fire, OpenMP trebuie să poată determina numărul acestor iterații, dar acest lucru nu este posibil pentru lista 4.

Bucla exterioară este executată până când se obține precizia specificată și nu există nici o modalitate de a determina numărul de iterații pe care le va lua - aceasta depinde mai mult de proprietățile funcției integrabile, care poate fi orice.

Se pare că bucla interioară poate fi paralelizată, dar nu este. Numărul de iterații nu poate fi determinat exact, deoarece ca un contor, se utilizează o variabilă de tip fracțional. și în ea se poate acumula eroare (după cum se arată mai sus). În OpenMP, o bucla paralelă trebuie să aibă un numărător întreg.

Cu toate acestea, aceasta nu înseamnă că OpenMP nu poate paraleliza soluția unei astfel de probleme, dar codul trebuie să fie scris într-un anumit mod. Faptul că numărul de iterații ale bucla interioară nu poate fi determinat indică faptul că acesta este un cod rău, indiferent dacă acesta va funcționa secvențial sau în paralel.

Pentru a utiliza OpenMP în acest exemplu, este suficient să determinați în avans numărul de iterații ale bucla interioară și să folosiți contorul întreg din ea:

Concluzia din listele 4 și 5 ar trebui să fie astfel încât, chiar dacă OpenMP este conceput ca un mijloc de paralelizare treptată a programelor secvențiale, programul pentru acest lucru trebuie să fie scris într-un anume fel. Din cauza problemelor descrise mai sus, lista de funcții 5 poate să nu funcționeze corect atunci când cereți o precizie foarte mare, dar nu puteți da vina pe OpenMP pentru aceasta.

Cod sursă:

Articole similare