MPI aansluiten in Visual Studio. MPI-functies

Het gebeurde zo dat ik in nauw contact moest komen met de studie parallel computergebruik en in het bijzonder MPI. Misschien is deze richting vandaag de dag veelbelovend, dus ik zou de hubbrowsers de basisprincipes van dit proces willen laten zien.

Basisprincipes en voorbeeld
De berekening van exponentieel (e) zal als voorbeeld worden gebruikt. Een van de opties om het te vinden is de Taylor-serie:
e^x=∑((x^n)/n!), waarbij de sommatie plaatsvindt van n=0 tot oneindig.

Deze formule kan gemakkelijk worden geparallelliseerd, omdat het gewenste aantal de som is van individuele termen en, dankzij dit, elk aparte verwerker kan beginnen met het berekenen van individuele termen.

Het aantal termen dat in elke individuele processor zal worden berekend, hangt zowel af van de lengte van het interval n als van het beschikbare aantal processors k dat kan deelnemen aan het berekeningsproces. Als de lengte van het interval bijvoorbeeld n=4 is en er zijn vijf processors (k=5) bij de berekeningen betrokken, dan krijgen de eerste tot en met de vierde processor elk één term en wordt de vijfde niet gebruikt. Als n=10 en k=5, krijgt elke processor twee termen voor berekening.

Aanvankelijk verzendt de eerste processor, met behulp van de MPI_Bcast-broadcastfunctie, naar de anderen de waarde van de door de gebruiker gespecificeerde variabele n. Over het algemeen heeft de functie MPI_Bcast het volgende formaat:
int MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm), waarbij buffer het adres is van de buffer met het element, count het aantal elementen is, datatype het overeenkomstige gegevenstype in MPI is, root is de rang van de hoofdprocessor die het doorsturen afhandelt, en comm is de naam van de communicator.
In mijn geval zal de rol van de hoofdprocessor, zoals reeds vermeld, de eerste processor met rang 0 zijn.

Nadat het getal n met succes is verzonden, begint elke processor met het berekenen van de voorwaarden ervan. Om dit te doen, wordt bij elke stap van de cyclus een getal dat gelijk is aan het aantal processors dat aan de berekeningen deelneemt, toegevoegd aan het getal i, dat aanvankelijk gelijk is aan de rangorde van de processor. Als het nummer bezig is volgende stappen aantal dat ik zal overschrijden gebruiker gedefinieerde nummer n, lusuitvoering voor van deze verwerker zal stoppen.

Tijdens de uitvoering van de cyclus worden de termen toegevoegd aan een afzonderlijke variabele en na voltooiing ervan wordt het resulterende bedrag naar de hoofdprocessor gestuurd. Om dit te doen, wordt de reductiefunctie MPI_Reduce gebruikt. IN algemeen beeld het ziet er zo uit:
int MPI_Reduce(void *buf, void *result, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm)

Het voegt de invoerbufferelementen van elk proces in de groep samen met behulp van de op-bewerking en retourneert de gecombineerde waarde naar de uitvoerbuffer van procesnummer root. Het resultaat van een dergelijke bewerking zal één enkele waarde zijn, vandaar dat de castingfunctie zijn naam heeft gekregen.

Nadat het programma op alle processors is uitgevoerd, ontvangt de eerste processor de totale som van termen, wat de exponentwaarde is die we nodig hebben.

Opgemerkt moet worden dat zowel parallelle als sequentiële methoden voor het berekenen van de exponent de faculteit vinden recursieve functie. Toen ik een beslissing nam over hoe ik de uit te voeren taak moest parallelliseren, overwoog ik de optie om de faculteit ook aan te zetten verschillende verwerkers, maar uiteindelijk was deze optie voor mij irrationeel.

De primaire taak is nog steeds het vinden van de waarde van de exponent, en als processors elke faculteit van elke term afzonderlijk gaan berekenen, kan dit tot precies het tegenovergestelde effect leiden, namelijk een aanzienlijk verlies aan prestaties en rekensnelheid.
Dit wordt verklaard door het feit dat dit in dit geval zal beginnen enorme druk op de communicatieomgeving, die vaak al een zwakke schakel is in parallelle computersystemen. Als de faculteit op elke processor privé wordt berekend, zal de belasting van de communicatielijnen minimaal zijn. Deze zaak kan worden gebeld goed voorbeeld dat de taak van parallellisatie soms ook zijn grenzen moet hebben.

Code-uitvoeringsalgoritme
1. De waarde van het getal n wordt overgebracht van de visuele shell naar het programma, dat vervolgens naar alle processors wordt verzonden met behulp van de uitzendfunctie.
2. Wanneer de eerste hoofdprocessor wordt geïnitialiseerd, start er een timer.
3. Elke processor voert een lus uit, waarbij de incrementwaarde het aantal processors in het systeem is. Bij elke iteratie van de lus wordt een term berekend en de som van dergelijke termen wordt opgeslagen in de drobSum-variabele.
4. Nadat de lus is voltooid, voegt elke processor zijn drobSum-waarde toe aan de resultaatvariabele met behulp van de MPI_Reduce-reductiefunctie.
5. Nadat de berekeningen op alle processors zijn voltooid, stopt de eerste hoofdprocessor de timer en verzendt de resulterende waarde van de resultaatvariabele naar de uitvoerstroom.
6. De door onze timer gemeten tijdswaarde in milliseconden wordt ook naar de uitvoerstroom verzonden.
Codelijst
Het programma is geschreven in C++, we gaan ervan uit dat de argumenten voor uitvoering vanuit de externe shell worden doorgegeven. De code ziet er als volgt uit:
#erbij betrekken "mpi.h"
#erbij betrekken
#erbij betrekken
namespace std; gebruiken;

dubbel Feit(int n)
{
als (n==0)
retour 1;
anders
retourneer n*Feit(n-1);
}

int hoofd(int argc, char *argv)
{
SetConsoleOutputCP(1251);
int n;
int myid;
int numprocs;
int ik;
int rc;
lange dubbele drob,drobSum=0,Resultaat, som;
dubbele starttijd = 0,0;
dubbele eindtijd;

N = atoi(argv);

als (rc= MPI_Init(&argc, &argv))
{
uit<< "Opstartfout, uitvoering gestopt" << endl;
MPI_Abort(MPI_COMM_WORLD, rc);
}

MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&mijnid);

als (myid == 0)
{

Starttijd = MPI_Wtime();
}
MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);

voor (i = myid; i<= n; i += numprocs)
{
drob = 1/Feit(i);
drobSom += drob;
}

MPI_Reduce(&drobSum, &Resultaat, 1, MPI_LONG_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
cout.precisie(20);
als (myid == 0)
{
uit<< Result << endl;
endwtime = MPI_Wtime();
uit<< (endwtime-startwtime)*1000 << endl;
}

MPI_Finalize();
retour 0;
}


* Deze broncode is gemarkeerd met Source Code Highlighter.
Conclusie
We ontvingen dus een eenvoudig programma voor het berekenen van de exponent met meerdere processors tegelijk. Waarschijnlijk is het knelpunt het opslaan van het resultaat zelf, omdat met een toename van het aantal cijfers het opslaan van een waarde met behulp van standaardtypen niet triviaal zal zijn en deze plek nadere uitwerking vereist. Misschien is een redelijk rationele oplossing om het resultaat naar een bestand te schrijven, hoewel het gezien de puur educatieve functie van dit voorbeeld niet nodig is om hier veel aandacht aan te besteden.

Annotatie: De lezing is gewijd aan de beschouwing van MPI-technologie als een parallelle programmeerstandaard voor gedistribueerde geheugensystemen. De belangrijkste vormen van gegevensoverdracht worden overwogen. Begrippen als procesgroepen en communicatoren worden geïntroduceerd. Omvat basisgegevenstypen, point-to-point-bewerkingen, collectieve bewerkingen, synchronisatiebewerkingen en tijdmetingen.

Doel van de lezing: De lezing is gericht op het bestuderen van de algemene methodologie voor het ontwikkelen van parallelle algoritmen.

Video-opname van de lezing - (volume - 134 MB).

5.1. MPI: basisconcepten en definities

Laten we een aantal concepten en definities bekijken die fundamenteel zijn voor de MPI-standaard.

5.1.1. Het concept van een parallel programma

Onder parallel programma Binnen het raamwerk van MPI verstaan ​​we een reeks gelijktijdig uitgevoerde handelingen processen. Processen kunnen op verschillende processors worden uitgevoerd, maar er kunnen ook meerdere processen op dezelfde processor worden geplaatst (in dit geval worden ze uitgevoerd in de time-sharing-modus). In het extreme geval kan een enkele processor worden gebruikt om een ​​parallel programma uit te voeren - in de regel wordt deze methode gebruikt om in eerste instantie de juistheid van het parallelle programma te controleren.

Elk proces van een parallel programma komt voort uit een kopie van dezelfde programmacode ( SPMP-model). Deze programmacode, gepresenteerd in de vorm van een uitvoerbaar programma, moet op het moment dat het parallelle programma wordt gestart op alle gebruikte processors beschikbaar zijn. De broncode voor het uitvoerbare programma is ontwikkeld in de algoritmische talen C of Fortran met behulp van een of andere implementatie van de MPI-bibliotheek.

Het aantal processen en het aantal gebruikte processors worden bepaald op het moment dat het parallelle programma wordt gestart met behulp van de MPI-programma-uitvoeringsomgeving en kunnen tijdens de berekeningen niet veranderen (de MPI-2-standaard biedt de mogelijkheid om het aantal processen dynamisch te wijzigen). Alle programmaprocessen zijn opeenvolgend genummerd van 0 tot p-1, Waar P is het totale aantal processen. Het procesnummer wordt gebeld rang proces.

5.1.2. Gegevensoverdrachtbewerkingen

MPI is gebaseerd op het doorgeven van berichten. Er zijn verschillende functies die worden aangeboden als onderdeel van MPI verdubbelt (punt-tot-punt) bewerkingen tussen twee processen en collectief (collectief) communicatieacties voor de gelijktijdige interactie van meerdere processen.

Om gepaarde handelingen uit te voeren kunnen verschillende transmissiemodi worden gebruikt, waaronder synchroon, blokkeren, enz. - waarbij alle mogelijke transmissiemodi zal worden uitgevoerd in paragraaf 5.3.

Zoals eerder opgemerkt, voorziet de MPI-standaard in de noodzaak om de meeste basisoperaties voor collectieve gegevensoverdracht te implementeren - zie paragrafen 5.2 en 5.4.

5.1.3. Concept van communicatoren

Processen van een parallel programma worden gecombineerd tot groepen. Onder communicator MPI verwijst naar een speciaal gemaakt serviceobject dat een groep processen en een aantal aanvullende parameters combineert ( context) gebruikt bij het uitvoeren van gegevensoverdrachtbewerkingen.

Meestal worden gepaarde gegevensoverdrachtbewerkingen uitgevoerd voor processen die tot dezelfde communicator behoren. Collectieve operaties worden gelijktijdig toegepast op alle communicatorprocessen. Als gevolg hiervan is het opgeven van de te gebruiken communicator verplicht voor gegevensoverdrachtbewerkingen in MPI.

Tijdens berekeningen kunnen nieuwe procesgroepen en communicators worden aangemaakt en bestaande groepen processen en communicators worden verwijderd. Hetzelfde proces kan tot verschillende groepen en communicatoren behoren. Alle processen die aanwezig zijn in het parallelle programma zijn opgenomen in de communicator die standaard is gemaakt met de identificatie MPI_COMM_WORLD.

Als het nodig is om gegevens over te dragen tussen processen van verschillende groepen, is het noodzakelijk om een ​​globale communicator te creëren ( intercommunicator).

Een gedetailleerde bespreking van de mogelijkheden van MPI voor het werken met groepen en communicatoren zal worden uitgevoerd in paragraaf 5.6.

5.1.4. Gegevenstypen

Wanneer u bewerkingen voor het doorgeven van berichten uitvoert, moet u de gegevens opgeven die moeten worden verzonden of ontvangen in MPI-functies. type verzonden gegevens. MPI bevat een grote set basistypen data die grotendeels samenvalt met datatypen in de algoritmische talen C en Fortran. Bovendien heeft MPI het vermogen om nieuwe te creëren afgeleide typen gegevens voor een nauwkeurigere en beknoptere beschrijving van de inhoud van doorgestuurde berichten.

Een gedetailleerde bespreking van de mogelijkheden van MPI voor het werken met afgeleide gegevenstypen zal worden uitgevoerd in paragraaf 5.5.

5.1.5. Virtuele topologieën

Zoals eerder opgemerkt kunnen gepaarde gegevensoverdrachtbewerkingen worden uitgevoerd tussen alle processen van dezelfde communicator, en nemen alle processen van de communicator deel aan een collectieve bewerking. In dit opzicht heeft de logische topologie van communicatielijnen tussen processen de structuur van een volledige grafiek (ongeacht de aanwezigheid van echte fysieke communicatiekanalen tussen processors).

Tegelijkertijd (en dit werd al opgemerkt in Hoofdstuk 3) is het voor de presentatie en daaropvolgende analyse van een aantal parallelle algoritmen raadzaam om te beschikken over een logische representatie van het bestaande communicatienetwerk in de vorm van bepaalde topologieën.

MPI heeft de mogelijkheid om meerdere processen in het formulier weer te geven roosters willekeurige dimensie (zie paragraaf 5.7). In dit geval kunnen de grensprocessen van de roosters aangrenzend worden verklaard en daardoor, op basis van de roosters, structuren van het type torus.

Daarnaast beschikt MPI over tools voor het genereren van logische (virtuele) topologieën van elk gewenst type. Een gedetailleerde bespreking van de mogelijkheden van MPI voor het werken met topologieën zal worden uitgevoerd in paragraaf 5.7.

En tot slot nog een laatste reeks opmerkingen voordat we naar MPI gaan kijken:

  • Beschrijvingen van functies en alle voorbeelden van aangeboden programma's zullen worden gepresenteerd in de algoritmische taal C; kenmerken van het gebruik van MPI voor de algoritmische taal Fortran zullen worden gegeven in paragraaf 5.8.1,
  • Een korte beschrijving van de beschikbare implementaties van MPI-bibliotheken en een algemene beschrijving van de uitvoeringsomgeving van MPI-programma's zullen worden besproken in paragraaf 5.8.2.
  • De belangrijkste presentatie van MPI-mogelijkheden zal gericht zijn op de versie 1.2-standaard ( MPI-1); aanvullende eigenschappen van de standaard versie 2.0 worden gepresenteerd in clausule 5.8.3.

Wanneer we MPI gaan bestuderen, kan worden opgemerkt dat MPI enerzijds behoorlijk complex is: de MPI-standaard voorziet in de aanwezigheid van meer dan 125 functies. Aan de andere kant is de structuur van MPI zorgvuldig doordacht: de ontwikkeling van parallelle programma's kan beginnen nadat slechts 6 MPI-functies zijn overwogen. Alle extra functies van MPI kunnen worden beheerst naarmate de complexiteit van de ontwikkelde algoritmen en programma's toeneemt. Het is in deze stijl – van eenvoudig tot complex – dat al het educatieve materiaal over MPI verder zal worden gepresenteerd.

5.2. Inleiding tot parallelle programmaontwikkeling met behulp van MPI

5.2.1. MPI-basisprincipes

Laten we de minimaal vereiste set MPI-functies presenteren, voldoende voor de ontwikkeling van tamelijk eenvoudige parallelle programma's.

5.2.1.1 Initialisatie en beëindiging van MPI-programma's

Eerste functie gebeld MPI moet een functie zijn:

int MPI_Init (int *agrc, char ***argv);

om de uitvoeringsomgeving van het MPI-programma te initialiseren. De functieparameters zijn het aantal argumenten op de opdrachtregel en de tekst van de opdrachtregel zelf.

Laatst gebelde functie MPI moet een functie zijn:

int MPI_Finalize(ongeldig);

Als gevolg hiervan kan worden opgemerkt dat de structuur van een parallel programma dat is ontwikkeld met behulp van MPI de volgende vorm zou moeten hebben:

#include "mpi.h" int main (int argc, char *argv) (<программный код без использования MPI функций>MPI_Init(&agrc, &argv);<программный код с использованием MPI функций>MPI_Finalize();<программный код без использования MPI функций>retour 0; )

Opgemerkt moet worden:

  1. Bestand mpi.h bevat definities van benoemde constanten, functieprototypes en gegevenstypen van de MPI-bibliotheek,
  2. Functies MPI_Init En MPI_Finaliseren zijn verplicht en moeten (en slechts één keer) worden uitgevoerd door elk proces van het parallelle programma,
  3. Vóór de oproep MPI_Init functie kan worden gebruikt MPI_geïnitialiseerd om te bepalen of er eerder een oproep is gedaan MPI_Init.

De hierboven besproken voorbeelden van functies geven een idee van de syntaxis voor het benoemen van functies in MPI. De functienaam wordt voorafgegaan door het MPI-voorvoegsel, gevolgd door een of meer woorden van de naam, het eerste woord van de functienaam begint met een hoofdletter en de woorden worden gescheiden door een onderstrepingsteken. De namen van MPI-functies verklaren in de regel het doel van de acties die door de functie worden uitgevoerd.

Opgemerkt moet worden:

  • Communicator MPI_COMM_WERELD, zoals eerder opgemerkt, wordt standaard gemaakt en vertegenwoordigt alle processen van het parallelle programma dat wordt uitgevoerd,
  • Rang verkregen met behulp van de functie MPI_Comm_rang, is de rangorde van het proces dat deze functie heeft aangeroepen, d.w.z. variabel ProcRank zal in verschillende processen verschillende waarden aannemen.

Parallellisatie in C-taal
Voorbeeld 3b. Parallellisatie in Fortran
Voorbeeld 4a. Bepalen van de kenmerken van de systeemtimer in C-taal
Voorbeeld 4b. Kenmerken van systeemtimers definiëren in Fortran

1.4. Berichten verzenden en ontvangen tussen afzonderlijke processen

1.4.1. Point-to-point-bewerkingen

1.4.2. Berichten verzenden en ontvangen met blokkering

Voorbeeld 5a. Uitwisseling van berichten tussen twee processen in C-taal
Voorbeeld 5b. Uitwisseling van berichten tussen twee processen in Fortran
Voorbeeld 6a. Berichtenuitwisseling tussen even en oneven processen in C
Voorbeeld 6b. Berichtenuitwisseling tussen even en oneven processen in Fortran
Voorbeeld 7a. Doorsturen naar een niet-bestaand proces in C
Voorbeeld 7b. Doorsturen naar een niet-bestaand proces in Fortran
Voorbeeld 8a. Gebufferde gegevensverzending in C-taal
Voorbeeld 8b. Gebufferde gegevensverzending in Fortran-taal
Voorbeeld 9a. Informatie verkrijgen over berichtkenmerken in C-taal
Voorbeeld 9b. Informatie verkrijgen over berichtkenmerken in Fortran
Voorbeeld 10a. Definitie van latentie en doorvoer in C-taal
Voorbeeld 10b. Latentie en doorvoer definiëren in Fortran

1.4.3. Berichten verzenden en ontvangen zonder te blokkeren

Voorbeeld 11a. Uitwisseling via een ringtopologie met behulp van niet-blokkerende bewerkingen in C
Voorbeeld 11b. Uitwisseling via een ringtopologie met behulp van niet-blokkerende bewerkingen in Fortran
Voorbeeld 12a. Communicatieschema "meester - werkers" in C-taal
Voorbeeld 12b. Communicatiediagram "meester - arbeiders" in Fortran-taal
Voorbeeld 13a. Matrixtranspositie in C-taal
Voorbeeld 13b. Een matrix omzetten in Fortran

1.4.4. Interactieverzoeken die in behandeling zijn

Voorbeeld 14a. Schema van een iteratieve methode met uitwisseling langs een ringtopologie met behulp van uitgestelde zoekopdrachten in C-taal
Voorbeeld 14b. Schema van een iteratieve methode met uitwisseling via een ringtopologie met behulp van uitgestelde zoekopdrachten in Fortran

1.4.5. Impasse situaties

Voorbeeld 15a. Uitwisseling via een ringtopologie met behulp van de MPI_Sendrecv-procedure in C-taal
Voorbeeld 15b. Uitwisseling via een ringtopologie met behulp van de MPI_SENDRECV-procedure in Fortran

1.5. Collectieve procesinteracties

1.5.1. Algemene bepalingen

1.5.2. Barrière

Voorbeeld 16a. Modellering van barrièresynchronisatie in C-taal
Voorbeeld 16b. Modellering van barrièresynchronisatie in Fortran

1.5.3. Collectieve gegevensoverdrachtsoperaties

1.5.4. Globale operaties

Voorbeeld 17a. Modelleren van globale sommatie met behulp van een verdubbelingsschema en de collectieve bewerking MPI_Reduce in C-taal
Voorbeeld 17b. Modellering van globale sommatie met behulp van een verdubbelingsschema en de collectieve bewerking MPI_Reduce in Fortran

1.5.5. Aangepaste wereldwijde operaties

Voorbeeld 18a. Aangepaste globale functie in C-taal
Voorbeeld 18b. Aangepaste globale functie in Fortran

1.6. Groepen en communicatoren

1.6.1. Algemene bepalingen

1.6.2. Bewerkingen met procesgroepen

Voorbeeld 19a. Werken met groepen in C-taal
Voorbeeld 19b. Werken met groepen in Fortran

1.6.3. Operaties met communicatoren

Voorbeeld 20a. Het afbreken van een communicator in C
Voorbeeld 20b. Een communicator partitioneren in Fortran
Voorbeeld 21a. Hernummering van processen in C-taal
Voorbeeld 21b. Hernummering van processen in Fortran

1.6.4. Intercommunicatoren

Voorbeeld 22a. Master-worker-schema met behulp van een intercommunicator in C-taal
Voorbeeld 22b. Master-worker-circuit met behulp van een intercommunicator in Fortran

1.6.5. Kenmerken

1.7. Virtuele topologieën

1.7.1. Algemene bepalingen

1.7.2. Cartesiaanse topologie

1.7.3. Grafiektopologie

Voorbeeld 23a. Master-worker-diagram met behulp van grafiektopologie in C-taal
Voorbeeld 23b. Master-worker-schema met behulp van grafiektopologie in Fortran

1.8. Verschillende soorten gegevens verzenden

1.8.1. Algemene bepalingen

1.8.2. Afgeleide gegevenstypen

Voorbeeld 24a. Matrixkolommen in omgekeerde volgorde herschikken in C-taal
Voorbeeld 24b. Herschikken van matrixkolommen in omgekeerde volgorde in Fortran

1.8.3. Gegevensverpakking

Voorbeeld 25a. Verpakte gegevens verzenden in C-taal
Voorbeeld 25b. Verpakte gegevens verzenden in Fortran

1.9. info-object

1.9.1. Algemene bepalingen

1.9.2. Werken met het info-object

1.10. Dynamische procescontrole

1.10.1. Algemene bepalingen

1.10.2.Creatie van processen

meester.c
slaaf.c
Voorbeeld 26a. Master-worker-schema met behulp van process spawning in C-taal
meester.f
slaaf.f
Voorbeeld 26b. Master-worker-schema met behulp van process spawning in Fortran

1.10.3. Client-server-communicatie

server.c
klant.c
Voorbeeld 27a. Uitwisseling van gegevens tussen server en client met behulp van een openbare naam in C-taal
server.f
klant.f
Voorbeeld 27b. Uitwisseling van gegevens tussen server en client met behulp van een openbare naam in de Fortran-taal

1.10.4. Een proceskoppeling verwijderen

1.10.5. Socket-communicatie

1.11. Communicatie in één richting

1.11.1. Algemene bepalingen

1.11.2. Werken met een raam

1.11.3. Data overdracht

1.11.4. Synchronisatie

Voorbeeld 28a
Voorbeeld 28b
Voorbeeld 29a. Uitwisseling via een ringtopologie met behulp van eenrichtingscommunicatie in C
Voorbeeld 29b. Uitwisseling via een ringtopologie met behulp van eenrichtingscommunicatie in Fortran
Voorbeeld 30a. Uitwisseling via een ringtopologie met behulp van eenrichtingscommunicatie in C
Voorbeeld 30b. Uitwisseling via een ringtopologie met behulp van eenrichtingscommunicatie in Fortran

1.12. Externe interfaces

1.12.1. Algemene vragen

1.12.2. Informatie uit status

1.12.3. Draden

1.13. Parallelle I/O

1.13.1. Definities

1.13.2. Werken met bestanden

1.13.3. Toegang tot data

Voorbeeld 31a. Gebufferd lezen uit een bestand in C-taal
Voorbeeld 31b. Gebufferd lezen uit een bestand in Fortran
Voorbeeld 32a. Collectief lezen uit een bestand in C-taal
Voorbeeld 32b. Collectief lezen uit een bestand in Fortran

1.14. Foutverwerking

1.14.1. Algemene bepalingen

1.14.2. Foutafhandelaars geassocieerd met communicators

1.14.3. Venstergerelateerde foutafhandelaars

1.14.4. Bestandsgerelateerde foutafhandelaars

1.14.5. Aanvullende procedures

1.14.6. Foutcodes en klassen

1.14.7. Foutafhandelaars bellen

Voorbeeld 33a. Foutafhandeling in C-taal
Voorbeeld 33b. Foutafhandeling in Fortran

Hoofdstuk 2 OpenMP parallelle programmeertechnologie

2.1. Invoering

2.2. Basisconcepten

2.2.1. Het samenstellen van een programma

Voorbeeld 34a. Voorwaardelijke compilatie in C
Voorbeeld 34b
Voorbeeld 34c. Voorwaardelijke compilatie in Fortran

2.2.2. Parallel programmamodel

2.2.3. Richtlijnen en procedures

2.2.4. Uitvoering van het programma

2.2.5. Tijdstip

Voorbeeld 35a. Werken met systeemtimers in C
Voorbeeld 35b. Werken met systeemtimers in Fortran

2.3. Parallelle en seriële gebieden

2.3.1. parallelle richtlijn

Voorbeeld 36a. Parallelle regio in C-taal
Voorbeeld 36b. Parallelle regio in Fortran
Voorbeeld 37a. De reductieoptie in C-taal
Voorbeeld 37b. De reductieoptie in Fortran

2.3.2. Stenonotatie

2.3.3. Omgevingsvariabelen en helperprocedures

Voorbeeld 38a. Omp_set_num_threads procedure en num_threads optie in C-taal
Voorbeeld 38b. Procedure omp_set_num_threads en optie num_threads in Fortran-taal
Voorbeeld 39a. Procedures omp_set_dynamic en omp_get_dynamic in C-taal
Voorbeeld 39b. Procedures omp_set_dynamic en omp_get_dynamic in Fortran
Voorbeeld 40a. Geneste parallelle gebieden in C
Voorbeeld 40b. Geneste parallelle regio's in Fortran
Voorbeeld 41a. Omp_in_parallel-functie in C-taal
Voorbeeld 41b. Functie omp_in_parallel in Fortran-taal

2.3.4. enkele richtlijn

Voorbeeld 42a. Enkele richtlijn en nowait-optie in C-taal
Voorbeeld 42b. Eén richtlijn en nowait-optie in Fortran
Voorbeeld 43a. Copyprivate-optie in C-taal
Voorbeeld 43b. copyprivate-optie in Fortran

2.3.5. hoofdrichtlijn

Voorbeeld 44a. Masterrichtlijn in C-taal
Voorbeeld 44b. hoofdrichtlijn in Fortran

2.4. Gegevensmodel

Voorbeeld 45a. Privéoptie in C-taal
Voorbeeld 45b. De privéoptie in Fortran
Voorbeeld 46a. Gedeelde optie in C-taal
Voorbeeld 46b. De gedeelde optie in Fortran
Voorbeeld 47a. eerste privéoptie in C-taal
Voorbeeld 47b. eerste privéoptie in Fortran
Voorbeeld 48a. threadprivate-richtlijn in C-taal
Voorbeeld 48b. threadprivate-richtlijn in Fortran
Voorbeeld 49a. Kopieeroptie in C-taal
Voorbeeld 49b. kopieeroptie in Fortran

2.5. Werkverdeling

2.5.1. Parallellisatie op laag niveau

Voorbeeld 50a. Procedures omp_get_num_threads en omp_get_thread_num in C-taal
Voorbeeld 50b. Procedures omp_get_num_threads en omp_get_thread_num in Fortran

2.5.2. Parallelle lussen

Voorbeeld 51a. voor richtlijn in C-taal
Voorbeeld 51b. De do-richtlijn in Fortran
Voorbeeld 52a. Planningsoptie in C-taal
Voorbeeld 52b. planningsoptie in Fortran
Voorbeeld 53a. Planningsoptie in C-taal

MPI-functies

Afgeleid type, bewerkingen, gegevenstypen

Bsend Buffer_attach Get_count ANY_SOURCE Sendrecv_replace ANY_TAG Probe

Allgetherv Alltoall Alltoallv Rduce_scatter-scan verminderen

Een afgeleid type wordt opgebouwd uit vooraf gedefinieerde MPI-typen en eerder gedefinieerde afgeleide typen met behulp van speciale constructorfuncties

MPI_Type_contiguous, MPI_Type_vector, MPI_Type_hvector, MPI_Type_indexed, MPI_Type_hindexed, MPI_Type_struct.

Een nieuw afgeleid type wordt geregistreerd door de functie MPI_Type_commit aan te roepen. Pas na registratie kan een nieuw afgeleid type worden gebruikt in communicatieroutines en bij de constructie van andere typen. Vooraf gedefinieerde MPI-typen worden als geregistreerd beschouwd.

Wanneer een afgeleid type niet langer nodig is, wordt het vernietigd met de functie MPI_Type_free.

1) MPI_Init - initialisatiefunctie. Als resultaat van het uitvoeren van deze functie wordt een procesgroep gecreëerd waarin alle applicatieprocessen worden geplaatst, en wordt een communicatiegebied gecreëerd, beschreven door de vooraf gedefinieerde communicator MPI_COMM_WORLD.

MPI_Type_commit - typeregistratie, MPI_Type_free - typevernietiging

int MPI_Init(int *argc, char ***argv);

2) MPI_Finalize - Functie voor het voltooien van MPI-programma's. De functie sluit alle MPI-processen af ​​en elimineert alle communicatiegebieden.

int MPI_Finalize(ongeldig);

3) Functie voor het bepalen van het aantal processen in het communicatiebereik MPI_Comm_grootte . De functie retourneert het aantal processen in het communicatiegebied van de communicatorcomm.

int MPI_Comm_size(MPI_Comm comm, int *grootte);

4) Functie voor het detecteren van procesnummers MPI_Comm_rank . De functie retourneert het nummer van het proces dat deze functie heeft aangeroepen. Procesnummers liggen in het bereik 0..grootte-1.

int MPI_Comm_rank(MPI_Comm comm, int *rang);

5) Berichtfunctie MPI_Verzenden. De functie verzendt telelementen van het berichtgegevenstype met identificatietag om de bestemming in het communicatiegebied van de communicatorcomm te verwerken.

int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm);

6) Functie voor berichtontvangst MPI_Ont. De functie ontvangt telelementen van het berichtgegevenstype met identificatietag van het bronproces in het communicatiegebied van de communicatorcomm.

int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)

7) Timingfunctie (timer) MPI_Wtime. De functie retourneert de astronomische tijd in seconden die is verstreken sinds een bepaald punt in het verleden (referentiepunt).

dubbele MPI_Wtime(ongeldig)

Functies voor het doorgeven van berichten tussen processen zijn onderverdeeld in:

Voorvoegsel S (synchroon)

betekent synchrone gegevensoverdrachtmodus. De datatransmissiebewerking eindigt pas wanneer de dataontvangst eindigt. De functie is niet-lokaal.

Prefix B (gebufferd)

betekent gebufferde gegevensoverdrachtmodus. Er wordt een klembord gemaakt in de adresruimte van het verzendproces met behulp van een speciale functie, die wordt gebruikt bij uitwisselingsbewerkingen. De verzendbewerking eindigt wanneer gegevens in deze buffer worden geplaatst. De functie is lokaal van aard.

Voorvoegsel R (klaar)

overeengekomen of voorbereide wijze van gegevensoverdracht. De gegevensoverdrachtbewerking begint pas wanneer de ontvangende processor het teken van gereedheid voor het ontvangen van gegevens heeft gezet, waardoor de ontvangstbewerking wordt geïnitieerd. De functie is niet-lokaal.

Voorvoegsel I (onmiddellijk)

verwijst naar niet-blokkerende bewerkingen.

MPI_Status-structuur

Na het lezen van een bericht kunnen sommige parameters onbekend zijn, zoals het aantal gelezen items, de bericht-ID en het adres van de afzender. Deze informatie kan worden verkregen met behulp van de statusparameter. Statusvariabelen moeten expliciet worden gedeclareerd in het MPI-programma. In de C-taal is status een structuur van het type MPI_Status met drie velden MPI_SOURCE, MPI_TAG, MPI_ERROR.

8) Om het aantal daadwerkelijk ontvangen berichtelementen te bepalen, moet u een speciale functie gebruiken MPI_Get_count .

int MPI_Get_count (MPI_Status *status, MPI_Datatype gegevenstype, int *count);

9) U kunt de parameters van het ontvangen bericht bepalen zonder het te lezen met behulp van de MPI_Probe-functie. int MPI_Probe (int bron, int tag, MPI_Comm comm, MPI_Status *status);

10) In situaties waarin u gegevens tussen processen moet uitwisselen, is het veiliger om een ​​gecombineerde bewerking te gebruiken MPI_Sendrecv . Bij deze bewerking worden de verzonden gegevens uit de buf-array vervangen door de ontvangen gegevens.

int MPI_Sendrecv(void *sendbuf, int sendcount, MPI_Datatype sendtype, int dest, int sendtag, void *recvbuf, int recvcount, MPI_Datatype recvtype, int source, MPI_Datatype recvtag, MPI_Comm comm, MPI_Status *status);

11) Functie voor het controleren van de voltooiing van een niet-blokkerende operatie MPI_Test.

int MPI_Test(MPI_Request *verzoek, int *vlag, MPI_Status *status);

Dit is een lokale niet-blokkerende bewerking. Als de bewerking die aan het verzoek is gekoppeld, is voltooid, wordt flag = true geretourneerd en bevat de status informatie over de voltooide bewerking. Als de gecontroleerde bewerking niet is voltooid, wordt flag = false geretourneerd en is de waarde van status in dit geval niet gedefinieerd.

12) Functie voor het annuleren van een verzoek zonder te wachten op de voltooiing van een niet-blokkerende operatie MPI_Request_free.

int MPI_Request_free(MPI_Request *verzoek);

De aanvraagparameter is ingesteld op MPI_REQUEST_NULL.

13) Het bereiken van een efficiënte uitvoering van een gegevensoverdrachtoperatie van één proces naar alle processen van een programma (data-uitzending) kan worden bereikt met behulp van de MPI-functie:

int MPI_Bcast(ongeldig *buf,int aantal,MPI_Datatype type,int root,MPI_Comm comm)

De MPI_Bcast-functie zendt gegevens uit vanuit de buf-buffer die telelementen van het typetype bevat vanaf een procesgenummerde root naar alle processen die zijn opgenomen in de comm-communicator.

14) Als u van iemand een bericht wilt ontvangen Voor het verzendproces kan de waarde MPI_ANY_SOURCE zijn opgegeven voor de bronparameter

15) Als het nodig is om een ​​bericht met een tag te ontvangen, kan de waarde voor de tagparameter worden opgegeven MPI_ANY_TAG

16) Met de statusparameter kunt u een aantal kenmerken van het ontvangen bericht definiëren:

- status.MPI_SOURCE – rang het verzendproces van het ontvangen bericht,

- status.MPI_TAG - tag van het ontvangen bericht.

17) Functie

MPI_Get_coun t(MPI_Status *status, MPI_Datatype type, int *count)

retourneert in de count-variabele het aantal elementen van het type type in het ontvangen bericht.

18) Bewerkingen die gegevens van alle processen naar één proces overbrengen. In deze operatie op de verzamelde

waarden voeren een of andere gegevensverwerking uit (om het laatste punt te benadrukken, deze bewerking wordt ook wel de gegevensreductiebewerking genoemd)

int MPI_Reduce (void *sendbuf, void *recvbuf,int count,MPI_Datatype type, MPI_Op op,int root,MPI_Comm comm)

19) Processynchronisatie, d.w.z. gelijktijdige verwezenlijking door processen van bepaalde punten van het berekeningsproces wordt verzekerd met behulp van de MPI-functie: int MPI_Barrier(MPI_Comm comm); De functie MPI_Barrier definieert een collectieve bewerking en moet daarom, wanneer deze wordt gebruikt, door alle processen van de gebruikte communicator worden aangeroepen. Bij het aanroepen van de functie MPI_Barrier

procesuitvoering is geblokkeerd; procesberekeningen gaan pas verder nadat alle processen van de communicator de functie MPI_Barrier hebben aangeroepen.

20) Om de gebufferde overdrachtsmodus te gebruiken, moet een MPI-geheugenbuffer worden gemaakt en overgedragen

om berichten te bufferen – de functie die hiervoor wordt gebruikt ziet er als volgt uit: int MPI_Buffer_attach (void *buf, int size),

- buf geheugenbuffer voor het bufferen van berichten,

- grootte – buffergrootte.

21) Nadat u klaar bent met het werken met de buffer, moet deze worden losgekoppeld van MPI met behulp van de functie:

int MPI_Buffer_detach (ongeldig *buf, int *size).

22) Met de MPI-functie kunt u een efficiënte en gegarandeerde gelijktijdige uitvoering van datatransmissie- en -ontvangstbewerkingen bereiken:

int MPI_Sendrecv (void *sbuf,int scount,MPI_Datatype stype,int dest, int stag, void *rbuf,int rcount,MPI_Datatype

rtype,int source,int rtag, MPI_Comm comm, MPI_Status *status)

23) Wanneer berichten van hetzelfde type zijn, heeft MPI de mogelijkheid om een ​​enkele buffer te gebruiken: intMPI_Sendrecv_replace (void *buf, int count, MPI_Datatype type, int dest,

int stag, int source, int rtag, MPI_Comm comm, MPI_Status* status)

24) De algemene werking van het verzenden van gegevens van één proces naar alle processen (gegevensdistributie) verschilt van uitzenden doordat het proces verschillende gegevens naar de processen verzendt (zie figuur 4.4). Deze bewerking kan worden uitgevoerd met behulp van de functie:

int MPI_Scatter (void *sbuf,int sccount,MPI_Datatype stype,

25) De werking van gegeneraliseerde gegevensoverdracht van alle processors naar één proces (gegevensverzameling) is het omgekeerde van de gegevensdistributieprocedure (zie figuur 4.5). Om deze bewerking in MPI uit te voeren is er een functie:

int MPI_Gather (nietig *sbuf,int sccount,MPI_Datatype stype,

void *rbuf,int rcount,MPI_Datatype rtype, int root, MPI_Comm comm)

26) Opgemerkt moet worden dat bij gebruik van de functie MPI_Gather alleen gegevensverzameling wordt uitgevoerd

op één proces. Om alle verzamelde gegevens over elk van de communicatorprocessen te verkrijgen

u moet de verzamel- en distributiefunctie gebruiken:

int MPI_Allgather (void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, MPI_Comm comm)

27) Het overbrengen van gegevens van alle processen naar alle processen is de meest gebruikelijke handeling voor gegevensoverdracht (zie figuur 4.6). Deze bewerking kan worden uitgevoerd met behulp van de functie:

int MPI_Alltoall (void *sbuf,int scount,MPI_Datatype stype, void *rbuf,int rcount,MPI_Datatype rtype,MPI_Comm comm)

28) De functie MPI_Reduce biedt resultaten voor gegevensreductie

slechts op één proces. Om de resultaten van datareductie op elk van de communicatorprocessen te verkrijgen, moet u de reductie- en distributiefunctie gebruiken:

int MPI_Allreduce (void *sendbuf, void *recvbuf,int count,MPI_Datatype type, MPI_Op op,MPI_Comm comm).

29) En een andere versie van de gegevensverzameling en -verwerking, die ervoor zorgt dat alle gedeeltelijke reductieresultaten worden verkregen, kan worden verkregen met behulp van de functie:

int MPI_Scan (void *sendbuf, void *recvbuf,int count,MPI_Datatype type, MPI_Op op,MPI_Comm comm).

Het algemene uitvoeringsdiagram van de MPI_Scan-functie wordt getoond in Fig. 4.7. Elementen van ontvangen berichten vertegenwoordigen de resultaten van het verwerken van de overeenkomstige elementen van berichten die door processen worden verzonden, en om resultaten te verkrijgen voor een proces met rang i, 0≤i

30) De initiële waarde van de bufpos-variabele moet worden gevormd voordat het verpakken begint en wordt vervolgens door de functie ingesteld MPI_Pack. De functie MPI_Pack wordt opeenvolgend aangeroepen om alle benodigde gegevens in te pakken.

int MPI_Pack_size (int aantal, MPI_Datatype type, MPI_Comm comm, int *size)

31) Nadat alle noodzakelijke gegevens zijn ingepakt, kan de voorbereide buffer worden gebruikt in gegevensoverdrachtsfuncties waarbij het MPI_PACKED-type is opgegeven.

Na ontvangst van een bericht met het type MPI_PACKED kunnen de gegevens worden uitgepakt met behulp van de functie:

int MPI_Unpack (void *buf, int bufsize, int *bufpos, void *data, int count, MPI_Datatype type, MPI_Comm comm)

Complexe instructiesetcomputer

CISC (Engelse complexe instructiesetcomputer, of Engelse complexe instructiesetcomputer -

computer met een volledige set instructies) is een processorontwerpconcept dat wordt gekenmerkt door de volgende reeks eigenschappen:

een relatief klein aantal registers voor algemene doeleinden;

· een groot aantal machine-instructies, waarvan sommige semantisch worden geladen, vergelijkbaar met de operators van programmeertalen op hoog niveau, en worden uitgevoerd in vele klokcycli;

· een groot aantal adresseringsmethoden;

· een groot aantal opdrachtformaten met verschillende bitgroottes;

· het overwicht van het opdrachtformaat met twee adressen;

· aanwezigheid van typeverwerkingsopdrachten register-geheugen.

Gebreken :

hoge hardwarekosten; problemen met parallellisatie van berekeningen.

De bouwtechniek van het CISC-instructiesysteem is het tegenovergestelde van een andere techniek: RISC. Het verschil tussen deze concepten ligt in de programmeermethoden, niet in de daadwerkelijke processorarchitectuur. Bijna alle moderne processors emuleren zowel instructiesets van het RISC- als CISC-type.

Gereduceerde instructies Set Computer

Het is gebaseerd op de principes van de RISC-architectuur: vast instructieformaat, registerbewerkingen, uitvoering van instructies in één cyclus, eenvoudige adresseringsmethoden en een groot registerbestand. Tegelijkertijd zijn er verschillende belangrijke kenmerken die deze architectuur onderscheiden van de architecturen van andere RISC-processors. Deze omvatten: een onafhankelijke set registers voor elk van de actuatoren; opname van individuele CISC-achtige instructies in het systeem; gebrek aan een mechanisme voor “vertraagde transitie”; een originele manier om voorwaardelijke sprongen te implementeren. De belangrijkste toepassingen van microprocessorarchitecturen zijn krachtige servers en supercomputers.

Dergelijke computers waren gebaseerd op een architectuur die verwerkingsinstructies scheidde van geheugeninstructies en de nadruk legde op efficiënte pipelining. Het instructiesysteem is zo ontworpen dat de uitvoering van welke instructie dan ook een klein aantal machinecycli in beslag nam (bij voorkeur één machinecyclus). De logica zelf voor het uitvoeren van opdrachten om de prestaties te verbeteren was meer gericht op hardware dan op firmware-implementatie. Om de logica voor het decoderen van commando's te vereenvoudigen, werden commando's met een vaste lengte gebruikt

En vast formaat.

IN Wat is het nut van degie?

IN De processor biedt een mechanisme voor het dynamisch voorspellen van de richting van overgangen. Hiermee

Het doel op de chip is een klein cachegeheugen dat een branch target buffer (BTB) wordt genoemd, en twee onafhankelijke paren instructieprefetch-buffers (twee 32-bits buffers per pijplijn). De vertakkingsdoeladresbuffer slaat de adressen op van instructies die zich in de vooraf ophaalbuffers bevinden. De werking van de vooraf ophaalbuffers is zo georganiseerd dat instructies op elk gegeven moment slechts in één van de buffers van het corresponderende paar worden opgehaald. Wanneer een aftakkingsoperatie wordt gedetecteerd in de instructiestroom, wordt het berekende aftakkingsadres vergeleken met de adressen die zijn opgeslagen in de BTB. Als er een overeenkomst is, wordt voorspeld dat de vertakking zal plaatsvinden en wordt een andere prefetch-buffer ingeschakeld en begint hij opdrachten te geven aan de overeenkomstige pijplijn voor uitvoering. Als er een mismatch is, wordt aangenomen dat de vertakking niet zal worden uitgevoerd en dat de prefetch-buffer niet wordt omgeschakeld, waardoor de normale opdrachtuitgifteopdracht wordt voortgezet. Dit voorkomt stilstand van de transportband

Structurele conflicten en manieren om deze te minimaliseren

De gecombineerde wijze van commando-uitvoering vereist over het algemeen het pipelinen van functionele eenheden en het dupliceren van bronnen om alle mogelijke combinaties van commando's in de pipeline op te lossen. Als een combinatie van opdrachten mislukt

geaccepteerd wordt vanwege conflicten met hulpbronnen, dan zou er sprake zijn van een structureel conflict met de machine. Het meest typische voorbeeld van machines waarin structurele conflicten kunnen ontstaan, zijn machines met functionele apparaten die niet volledig van transportbanden zijn voorzien.

Minimalisatie: De pijplijn pauzeert de uitvoering van een van de opdrachten totdat het vereiste apparaat beschikbaar komt.

Dataconflicten, pijplijnstops en implementatie van het bypass-mechanisme

Een van de factoren die een aanzienlijke invloed hebben op de prestaties van transportsystemen zijn de logische afhankelijkheden tussen instructies. Gegevensconflicten ontstaan ​​wanneer het gebruik van pijplijnverwerking de volgorde van operandoproepen kan veranderen, zodat deze volgorde verschilt van de volgorde die wordt waargenomen wanneer instructies opeenvolgend worden uitgevoerd op een niet-gepijplijnde machine. Het probleem dat in dit voorbeeld wordt gesteld, kan worden opgelost met behulp van een vrij eenvoudige hardwaretechniek, genaamd data forwarding, data bypassing of soms kortsluiting.

Gegevensconflicten waardoor de pijplijn wordt onderbroken

In plaats daarvan hebben we aanvullende hardware nodig, genaamd pipeline interlook hardware, om ervoor te zorgen dat het voorbeeld correct wordt uitgevoerd. Over het algemeen detecteert dit soort apparatuur conflicten en pauzeert het de pijplijn zolang er een conflict bestaat. In dit geval pauzeert deze hardware de pijplijn, beginnend met de instructie die de gegevens wil gebruiken, terwijl de vorige instructie, waarvan het resultaat een operand voor de onze is, dat resultaat oplevert. Deze apparatuur zorgt ervoor dat een productielijn vastloopt of dat er een "bubbel" ontstaat, net zoals bij structurele conflicten.

Voorwaardelijke vertakkingsvoorspellingsbuffers

De voorwaardelijke aftakkingsvoorspellingsbuffer is een klein geheugen dat adresseerbaar is door de minst significante bits van het adres van de aftakkingsinstructie. Elke cel van dit geheugen bevat één bit, die aangeeft of de vorige tak is uitgevoerd of niet. Dit is het eenvoudigste type buffer van deze soort. Het heeft geen tags en is alleen nuttig voor het verminderen van de latentie van de vertakking als de vertraging langer is dan de tijd die nodig is om de waarde van het vertakkingsdoeladres te berekenen. De vertakkingsvoorspellingsbuffer kan worden geïmplementeerd als een kleine speciale cache waartoe toegang wordt verkregen door het instructieadres tijdens de instructieophaalfase van de pijplijn (IF), of als een paar bits die zijn geassocieerd met elk instructiecacheblok en worden opgehaald bij elke instructie.