Introducere în programarea paralelă openmp pe software-ul c, intel®

Nu este un secret faptul că "rasa megahertz", care timp de mulți ani a rămas principala cale de îmbunătățire a performanțelor procesatorilor, a fost înlocuită de tendința de creștere a numărului de nuclee. Aproximativ, producătorii de procesoare au învățat cum să pună mai multe procesoare pe un singur cip. Acum aproape orice computer este echipat cu un procesor multi-core. Chiar și sistemele desktop de bază au două nuclee - merită spus că abordarea este de patru și opt-core de sisteme. Dacă Legea lui Moore nu își pierde puterea, în decurs de 5 ani calculatorul mediu va avea 16 sau chiar 32 nuclee pe chip.







Problema este că industria de software nu are încă timp pentru hardware, și numai o parte din aplicații pot folosi eficient resursele procesoarelor multi-core. Fiecare program are un thread principal de execuție - un set de instrucțiuni care sunt executate secvențial unul după altul. Firește, în acest caz, este implicat un nucleu al procesorului. Programatorul trebuie să aibă grijă să se încarce restul nucleului cu munca, cu alte cuvinte, trebuie să se asigure că anumite instrucțiuni sunt executate nu secvențial, ci simultan - în modul paralel.

Trebuie notat că performanța nu crește liniar cu numărul de nuclee. Asta înseamnă că utilizarea a patru nuclee nu garantează o creștere de productivitate de patru ori. Cu toate acestea, există o creștere și, în fiecare an, va fi mai puternică - va fi mai optimizat pentru programele de procesoare multi-core.

Cum pot programatorii să gestioneze firele pentru a folosi întreaga putere a procesorului? Cum de a face ca aplicația să funcționeze cât mai repede posibil, să scadă cu creșterea numărului de nuclee și să scrie o astfel de aplicație nu era un programator de coșmar? O opțiune este să creați manual fire în cod, să le dați sarcini de efectuat, apoi să le ștergeți. Dar în acest caz trebuie să ai grijă de un lucru foarte important - sincronizarea. Dacă o sarcină necesită date care sunt contorizate de o altă sarcină, situația devine mai complicată. Este greu de înțeles ce se întâmplă atunci când diferite fire în același timp încearcă să schimbe valorile variabilelor comune. Și nu vreau să creez manual fire și să le delegem sarcini. Diverse biblioteci și standarde pentru programarea paralelă ajung la salvare. Să analizăm mai detaliat cel mai răspândit standard pentru paralelizarea programelor în limbile C, C ++, Fortran - OpenMP.

OpenMP - API, proiectat pentru programarea aplicațiilor multi-threaded pe sistemele multiprocesor cu memorie partajată. Dezvoltarea specificației OpenMP este efectuată de câțiva producători mari de computere și software. OpenMP este susținut de principalele compilatoare.

În OpenMP, nu veți vedea "firele din cod. În schimb, spuneți compilatorului cu directivele #pragma că blocul de coduri poate fi paralealizat. Cunoscând aceste informații, compilatorul poate genera o aplicație constând dintr-un fir principal, care creează o mulțime de alte fire pentru blocul paralel de cod. Aceste fire sunt sincronizate la sfârșitul blocului paralel de cod și revenim din nou la același fir principal.


Introducere în programarea paralelă openmp pe software-ul c, intel®

Utilizarea OpenMP

Deoarece OpenMP este controlat de #pragma, codul C ++ este corect compilat de orice compilator C ++, deoarece #pragma neacceptat trebuie ignorat. Cu toate acestea, OpenMP API conține, de asemenea, mai multe funcții, iar pentru a le utiliza, trebuie să includeți un fișier antet. Cel mai simplu mod de a determina dacă compilatorul OpenMP suportă este să încercați să conectați fișierul omp.h: #include

Dacă este acceptat OpenMP, trebuie să-l activați folosind simboluri de compilare speciale:

Directivele OpenMP încep cu #pragma omp.







Această directivă creează un grup de fire N. N este determinat la timpul de execuție, de obicei numărul de miezuri de procesor, dar puteți seta și N manual. Fiecare dintre firele din grup execută următoarea comandă în conformitate cu directiva (sau cu blocul de instrucțiuni definit în <>-paranteze). După executare, firele "se îmbină" într-una.

Exemplul tipărește textul "Salut!" Cu înfășurarea liniei de câte ori este numărul de fire create în grup. Pentru sistemele cu două nuclee, textul va fi imprimat de două ori. (Notă: ceva de genul "HeHlellolo" poate fi ieșit, deoarece ieșirea este paralelă.)

Dacă te uiți la modul în care funcționează, putem vedea că CCG creează o funcție specială și se mută codul unității în această funcție, astfel încât toate variabilele din cadrul blocului sunt variabile locale ale funcției (respectiv, variabilele locale ale fiecărui flux). Pe de altă parte, ICC folosește un mecanism care seamănă cu furca () și nu creează o funcție specială. Ambele implementări, desigur, sunt corecte și semantic identice.

Paralelismul poate fi condiționat dacă utilizați expresia if:

În acest caz, dacă parallelism_enabled este zero, bucla va fi procesată numai de un fir (principal).

Direcția de direcție împarte buclă pentru între grupul curent de fire, astfel încât fiecare fir din grup să își proceseze partea din bucla.

Acest ciclu va trimite numere de la 0 la 9, fiecare exact o dată. Cu toate acestea, ordinea retragerii lor este necunoscută. Poate fi, de exemplu, următoarele:


0 5 6 7 1 8 2 3 4 9.

Acest cod va fi convertit de compilator în ceva de genul:

Astfel, fiecare fir procesează partea sa a ciclului în paralel cu celelalte fire.

Este important ca directiva #pragma omp să delege numai porțiunile bucla la diferite fire din grupul curent de fire. La momentul începerii programului, grupul este dintr-un fir (principal). Pentru a crea un nou grup de thread, trebuie să utilizați cuvântul cheie paralel:

Este posibil să scrieți mai repede:

Ambele înregistrări sunt echivalente.

Pentru a seta numărul de fire într-un grup, puteți utiliza parametrul num_threads:

În OpenMP 2.5, variabila buclă de iterație trebuie să fie semnată. În OpenMP 3.0. acesta poate avea și un tip întreg nesemnat, acesta poate fi un pointer sau un iterator de acces aleatoriu cu timp constant. În acest din urmă caz, std :: distance () va fi folosit pentru a determina numărul de iterații de buclă.

Rezumat succint: grup paralel, pentru și flux

Din nou, rețineți diferența dintre paralel, paralel pentru și pentru:

    • Un grup de fluxuri reprezintă fluxurile care se execută în prezent.
    • Când începe programul, grupul conține un fir.
    • Directiva paralelă împarte firul curent într-un nou grup de fire până când se ajunge la sfârșitul următorului bloc expresie / expresie, apoi grupul de fluxuri se îmbină într-un singur flux.
    • pentru a împărți bucla în părți și dă fiecărei părți firul din grupul curent. Această directivă nu creează noi fire, ci doar împarte munca dintre firele grupului curent.
  • paralel pentru - o scurtă înregistrare a două comenzi: paralel și pentru. Paralel creează un nou grup de fire, apoi distribuie o parte a bucla la fiecare fir din acel grup.

Dacă programul nu conține directiva paralelă, atunci este executată de un singur fir.

planificare (planificare)

Programatorul poate controla modul în care firele vor fi încărcate de lucrare în timpul procesării bucla. Există mai multe opțiuni.

static este opțiunea implicită. Chiar înainte de a intra în buclă, fiecare fir "știe" care părți ale bucla pe care o va ocupa.

A doua opțiune este cuvântul cheie dinamic:

În acest caz, este imposibil să se prezică ordinea în care iterațiile buclă vor fi atribuite thread-urilor. Fiecare fir execută numărul specificat de iterații. Dacă acest număr nu este specificat, valoarea implicită este 1. După ce firul finalizează execuția iterațiilor specificate, trece la următorul set de iterații. Aceasta continuă până când toate iterațiile au fost finalizate. Ultimul set de iterații poate fi mai mic decât setul inițial. Această opțiune este foarte utilă atunci când diferite iterații ale ciclului sunt calculate la momente diferite. De asemenea, puteți specifica numărul de iterații după care firul "cere" OpeMP pentru următoarele:

În acest exemplu, fiecare fir execută trei iterații, apoi "ia" următorii trei și așa mai departe. Acestea din urmă, desigur, pot avea o dimensiune mai mică de trei.

Există, de asemenea, o opțiune ghidată. Arată dinamic. dar mărimea porțiunii scade exponențial. Dacă specificați directiva #pragma omp pentru program (dinamic, 15), buclă pentru 100 de iterații poate fi executată în patru fire după cum urmează:

Dar cum poate fi rezultatul aceluiași ciclu executat de patru fire dacă este specificată directiva #pragma omp for schedule (ghidată, 15):

Programarea dinamică și gestionată este potrivită dacă la fiecare iterație sunt efectuate diferite cantități de lucru sau dacă unii procesori sunt mai productivi decât alții. Cu planificarea statică, nu există nici o modalitate de a echilibra sarcina pe diferite fire. Cu programarea dinamică și gestionată, sarcina este distribuită automat - aceasta este esența acestor abordări. În mod obișnuit, cu programarea gestionată, codul rulează mai rapid decât dinamic, din cauza costurilor reduse de planificare.

ordonare (secvențiere)

Ordinea în care vor fi procesate iterațiile de buclă este, în general, imprevizibilă. Cu toate acestea, este posibil să "forțați" OpenMP pentru a executa expresiile într-o buclă în ordine. Pentru aceasta, există cuvântul cheie ordonat:

Ciclul "comprimă" 100 de fișiere în modul paralel, dar le "trimite" strict în ordine succesivă. În cazul în care, de exemplu, fluxul de „stors“ al șaptelea dosar, dar șase fișiere în acest punct nu a fost încă „trimis“, fluxul așteaptă „trimiterea“ șase fișiere. Fiecare fișier este "comprimat" și "trimis" o singură dată, dar "compresie" poate apărea în modul paralel. Este permisă utilizarea unui singur bloc comandat pe ciclu.

Citirea în continuare







Trimiteți-le prietenilor: