Inleiding tot parallelle programmeertechnologieën (MPI). Belangrijkste functies van MPI

Toevallig kreeg ik te maken met de studie van parallel computing en in het bijzonder MPI. Misschien is deze richting vandaag veelbelovend, dus ik wil de habray-gebruiker de basis van dit proces laten zien.

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

Deze formule kan gemakkelijk worden geparallelliseerd, aangezien het vereiste aantal de som van individuele termen is, en hierdoor kan elke individuele processor de individuele termen berekenen.

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 aan het rekenproces kan deelnemen. Als de lengte van het interval bijvoorbeeld n = 4 is en er zijn vijf processors (k = 5) betrokken bij de berekeningen, dan krijgen de eerste tot de vierde processors elk één term en de vijfde niet betrokken. Als n = 10 en k = 5, krijgt elke processor twee termen om te berekenen.

Aanvankelijk gebruikt de eerste processor de uitzendfunctie MPI_Bcast om de anderen de waarde van de door de gebruiker gespecificeerde variabele n te sturen. 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 is het aantal elementen, datatype is het corresponderende datatype in MPI, root is de rangorde van de zending van de hoofdverwerker, en comm is de naam van de communicator.
In mijn geval, zoals eerder vermeld, zal de eerste processor met rang 0 fungeren als de hoofdprocessor.

Daarna wordt het nummer n met succes verzonden, elke processor begint zijn voorwaarden te berekenen. Om dit te doen, wordt bij elke stap van de cyclus het getal i, dat aanvankelijk gelijk is aan de rang van de processor, aangevuld met een getal gelijk aan het aantal processors dat aan de berekeningen deelneemt. Als het getal in de volgende stappen het getal n overschrijdt dat door de gebruiker is opgegeven, stopt de lusuitvoering voor deze processor.

Tijdens de uitvoering van de cyclus worden de termen toegevoegd aan een aparte variabele en na voltooiing wordt het resulterende bedrag naar de hoofdprocessor gestuurd. Hiervoor wordt de functie MPI_Reduce van de cast-bewerking gebruikt. In het algemeen ziet het er als volgt uit:
int MPI_Reduce (void * buf, void * result, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm)

Het voegt de elementen van de invoerbuffer van elk proces in de groep samen met behulp van de bewerking op en retourneert de aaneengeschakelde waarde naar de uitvoerbuffer van de procesgenummerde root. Het resultaat van een dergelijke bewerking is een enkele waarde, daarom heeft de cast-functie zijn naam gekregen.

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

Opgemerkt moet worden dat zowel in parallelle als sequentiële methoden voor het berekenen van de exponent, een recursieve functie wordt gebruikt om de faculteit te vinden. Bij het nemen van een beslissing over de methode om de uitgevoerde taak parallel te laten lopen, overwoog ik de mogelijkheid om de faculteit ook op verschillende processors te vinden, maar uiteindelijk werd deze optie door mij als irrationeel beschouwd.

De primaire taak is nog steeds om de waarde van de exponent te vinden, en als de processors elke faculteit van elke term afzonderlijk beginnen te berekenen, kan dit leiden tot het tegenovergestelde effect, namelijk een aanzienlijk verlies in prestaties en rekensnelheid.
Dit wordt verklaard door het feit dat in dit geval een zeer grote belasting van de communicatieomgeving begint, die al vaak een zwakke schakel is in parallelle computersystemen. Als de berekening van de faculteit op elke processor privé plaatsvindt, zal de belasting van de communicatielijnen minimaal zijn. Dit geval kan een goed voorbeeld worden genoemd van het feit dat het parallellisatieprobleem soms zijn grenzen moet hebben.

Algoritme van code-uitvoering
1. De waarde van het getal n wordt vanuit de visuele shell naar het programma verzonden, dat vervolgens met behulp van de uitzendfunctie naar alle processors wordt verzonden.
2. Wanneer de eerste hoofdprocessor wordt geïnitialiseerd, wordt een timer gestart.
3. Elke processor voert een cyclus uit, waarbij de toename het aantal processors in het systeem is. In elke iteratie van de lus wordt een term berekend en de som van dergelijke termen wordt opgeslagen in de variabele drobSum.
4. Na het einde van de cyclus voegt elke processor zijn drobSum-waarde toe aan de Result-variabele met behulp van de MPI_Reduce cast-functie.
5. Nadat de berekeningen op alle processors zijn voltooid, stopt de eerste hoofdprocessor de timer en stuurt de resulterende waarde van de variabele Resultaat naar de uitvoerstroom.
6. De door onze timer gemeten tijdwaarde in milliseconden wordt ook naar de uitvoerstroom gestuurd.
Codelijst
Het programma is geschreven in C++, we nemen aan dat de argumenten voor uitvoering vanuit de buitenste schil 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 numproc;
int ik;
int rc;
lange dubbele drob, drobSum = 0, Resultaat, som;
dubbele starttijd = 0.0;
dubbele eindtijd;

N = atoi (argv);

if (rc = MPI_Init (& argc, & argv))
{
cout<< "Opstartfout, uitvoering gestopt" << endl;
MPI_Afbreken (MPI_COMM_WORLD, rc);
}

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

als (mijnid == 0)
{

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

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

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

MPI_Finalize ();
retourneer 0;
}


* Deze broncode is gemarkeerd met Source Code Highlighter.
Uitgang:
Zo kregen we een eenvoudig programma om de exponent te berekenen met meerdere processors tegelijk. Waarschijnlijk is het knelpunt de opslag van het resultaat zelf, want met de toename van het aantal cijfers zal het niet triviaal zijn om de waarde te bevatten met behulp van standaardtypen, en deze plaats vereist uitwerking. Misschien is een nogal rationele oplossing om het resultaat naar een bestand te schrijven, hoewel je, gezien de puur educatieve functie van dit voorbeeld, je hier niet speciaal op kunt concentreren.

annotatie: De lezing is gewijd aan de beschouwing van MPI-technologie als een parallelle programmeerstandaard voor gedistribueerde geheugensystemen. De belangrijkste wijzen van gegevensoverdracht worden beschouwd. Begrippen als procesgroepen en communicators worden geïntroduceerd. Basisgegevenstypen, point-to-point-bewerkingen, collectieve bewerkingen, synchronisatie- en tijdmetingsbewerkingen komen aan bod.

Het doel van de lezing: De lezing is gericht op het bestuderen van de algemene methodologie voor de ontwikkeling van parallelle algoritmen.

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

5.1. MPI: basisconcepten en definities

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

5.1.1. Parallel programma concept

Onder parallel programma in het kader van MPI, een reeks gelijktijdig uitgevoerde processen... Processen kunnen op verschillende processors worden uitgevoerd, maar er kunnen ook meerdere processen op één processor worden uitgevoerd (in dit geval wordt de uitvoering uitgevoerd in een timesharing-modus). In het uiterste geval kan één processor worden gebruikt om een ​​parallel programma uit te voeren - in de regel wordt deze methode gebruikt om in eerste instantie de juistheid van een parallel programma te controleren.

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

Het aantal gebruikte processen en het aantal processors wordt bepaald op het moment dat een parallel programma wordt gestart door middel van de uitvoeringsomgeving van MPI-programma's en kan tijdens de berekeningen niet veranderen (de MPI-2-standaard voorziet in de mogelijkheid om dynamisch te veranderen het aantal processen). Alle processen van het programma worden opeenvolgend hernummerd van 0 tot p-1, waar P er is een totaal aantal processen. Het procesnummer heet rang Verwerken.

5.1.2. Bewerkingen voor gegevensoverdracht

MPI is gebaseerd op bewerkingen voor het doorgeven van berichten. Onder de functies in MPI zijn verschillende gekoppeld (punt naar punt) bewerkingen tussen twee processen en collectief (collectief) communicatieacties voor de gelijktijdige interactie van meerdere processen.

Om gepaarde bewerkingen uit te voeren, kunnen verschillende transmissiemodi worden gebruikt, waaronder synchroon, blokkeren, enz. - een volledige overweging van mogelijke transmissiemodi: wordt uitgevoerd in paragraaf 5.3.

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

5.1.3. Het concept van communicators

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

In de regel worden gekoppelde gegevensoverdrachtbewerkingen uitgevoerd voor processen die tot dezelfde communicator behoren. Collectieve operaties worden gelijktijdig toegepast op alle communicatorprocessen. Als gevolg hiervan is de vermelding van de gebruikte communicator verplicht voor gegevensoverdrachtbewerkingen in MPI.

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

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

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

5.1.4. Gegevenstypen

Bij het uitvoeren van bewerkingen voor het verzenden van berichten, om de verzonden of ontvangen gegevens in de MPI-functies aan te geven, is het noodzakelijk om te specificeren: soort van gegevens verzonden. MPI bevat een grote set basistypen data die grotendeels samenvallen met de datatypes in de algoritmische talen C en Fortran. Bovendien heeft MPI de mogelijkheid om nieuwe 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 alle processen van de communicator nemen deel aan de 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 paragraaf 3), is het raadzaam om voor de presentatie en daaropvolgende analyse van een aantal parallelle algoritmen het bestaande communicatienetwerk logisch weer te geven in de vorm van bepaalde topologieën.

MPI heeft de mogelijkheid om een ​​verscheidenheid aan processen in de vorm weer te geven rooster willekeurige dimensie (zie paragraaf 5.7). In dit geval kunnen de grensprocessen van de roosters aangrenzend worden verklaard en dus, op basis van de roosters, structuren van het type torus.

Daarnaast beschikt MPI over faciliteiten voor het vormen 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 MPI gaan overwegen:

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

Beginnen met het bestuderen van MPI, kan worden opgemerkt dat MPI aan de ene kant behoorlijk gecompliceerd is - de MPI-standaard voorziet in de aanwezigheid van meer dan 125 functies. Aan de andere kant is de MPI-structuur goed doordacht - de ontwikkeling van parallelle programma's kan worden gestart na slechts 6 MPI-functies te hebben overwogen. Alle extra functies van MPI kunnen worden beheerst naarmate de complexiteit van de ontwikkelde algoritmen en programma's toeneemt. Genoemd in deze stijl - van eenvoudig tot complex - en verder zal al het trainingsmateriaal op MPI worden gepresenteerd.

5.2. Inleiding tot parallelle programma-ontwikkeling met behulp van MPI

5.2.1. MPI Basis

Hier is de minimaal vereiste set MPI-functies, voldoende voor de ontwikkeling van vrij eenvoudige parallelle programma's.

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

De eerste functie om aan te roepen MPI zou een functie moeten zijn:

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

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

Laatste functie aangeroepen MPI moet een functie zijn:

int MPI_Finalize (ongeldig);

Dientengevolge kan worden opgemerkt dat de structuur van een parallel programma dat is ontwikkeld met behulp van MPI als volgt zou moeten zijn:

#include "mpi.h" int main (int argc, char * argv) (<программный код без использования MPI функций>MPI_Init (& agrc, & argv);<программный код с использованием MPI функций>MPI_Finalize ();<программный код без использования MPI функций>retourneer 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_Finalize zijn verplicht en moeten worden uitgevoerd (en slechts één keer) door elk proces van het parallelle programma,
  3. Voordat u belt MPI_Init de 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 in de functienaam begint met een hoofdletter, 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_WORLD, zoals eerder opgemerkt, wordt standaard gemaakt en vertegenwoordigt alle processen van het parallelle programma dat wordt uitgevoerd,
  • De rang verkregen door de functie MPI_Comm_rank, is de rangorde van het proces dat deze functie heeft aangeroepen, d.w.z. variabele ProcRank zal verschillende waarden aannemen in verschillende processen.

Belangrijkste functies van MPI

De meest gebruikelijke programmeertechnologie voor parallel gedistribueerde geheugensystemen is momenteel MPI (Message Passing Interface). De belangrijkste manier waarop parallelle processen in dergelijke systemen met elkaar omgaan, is het doorgeven van berichten. In wezen is MPI een bibliotheek en runtime voor parallelle programma's in C of Fortran. Deze tutorial beschrijft voorbeelden van C-programma's.

In eerste instantie maakt MPI het gebruik van het MIMD-programmeermodel (Multiple Instruction Multiple Data) mogelijk - veel stromen instructies en gegevens, d.w.z. het combineren van verschillende programma's met verschillende gegevens. Maar programmeren voor zo'n model blijkt in de praktijk te ingewikkeld, dus wordt meestal het SIMD-model (Single Program Multiple Data) gebruikt: één programma en veel datastromen. Hier wordt een parallel programma geschreven zodat verschillende delen ervan tegelijkertijd hun deel van de taak kunnen uitvoeren, waardoor parallellisme wordt bereikt. Omdat alle MPI-functies in de bibliotheek zijn opgenomen, is het nodig om de bijbehorende modules te koppelen bij het samenstellen van een parallel programma.

In het kader van MPI wordt een parallel programma opgevat als een reeks gelijktijdig uitgevoerde processen. Processen kunnen op verschillende processors worden uitgevoerd, maar er kunnen ook meerdere processen op één processor worden uitgevoerd (in dit geval wordt de uitvoering uitgevoerd in een timesharing-modus). Wanneer een MPI-programma op een cluster wordt gestart, voert elk van zijn knooppunten zijn eigen kopie van het programma uit en voert het zijn eigen deel van de taak uit. Hieruit volgt dat een parallel programma een reeks op elkaar inwerkende processen is, die elk werken in zijn eigen adresruimte. In het uiterste geval kan één processor worden gebruikt om een ​​parallel programma uit te voeren - in de regel wordt deze methode gebruikt om in eerste instantie de juistheid van een parallel programma te controleren.

Het aantal gebruikte processen en het aantal processors wordt bepaald op het moment van het starten van een parallel programma door middel van de uitvoeringsomgeving van MPI-programma's en kan tijdens de berekeningen niet veranderen. Alle processen in het programma zijn opeenvolgend genummerd van 0 tot np-1, waarbij np het totale aantal processen is. Het procesnummer wordt de procesrang genoemd.

Parallelle processen communiceren met elkaar door berichten te verzenden. Er zijn twee soorten verzendmethoden (ze worden communicatie genoemd) - collectief en point-to-point. Bij collectieve communicatie stuurt een proces de benodigde informatie tegelijkertijd naar een hele groep processen; er is ook een meer algemeen geval waarin binnen een groep processen informatie van elk proces naar elk proces wordt verzonden. Eenvoudiger communicatie is point-to-point communicatie, waarbij het ene proces informatie naar het tweede stuurt, of beide processen informatie uitwisselen. Communicatiefuncties zijn de belangrijkste functies van de MPI-bibliotheek. Daarnaast zijn de verplichte functies de MPI-initialisatie- en beëindigingsfuncties - MPI_Init en MPI_Finalize. MPI_Init moet helemaal aan het begin van programma's worden aangeroepen en MPI_Finalize moet helemaal aan het einde worden aangeroepen. Tussen deze twee functies moeten alle andere MPI-functies worden aangeroepen.

Hoe weet een proces welk deel van de berekening het moet doen? Elk proces dat op een cluster wordt uitgevoerd, heeft zijn eigen unieke nummer - rang. Wanneer een proces zijn rangorde en het totale aantal processen kent, kan het zijn deel van het werk bepalen. Hiervoor heeft MPI speciale functies - MPI_Comm_rank en MPI_Comm_size. MPI_Comm_rank retourneert een geheel getal - de rangorde van het proces dat het heeft aangeroepen, en MPI_Comm_size retourneert het totale aantal lopende processen.

De processen van het parallelle gebruikersprogramma dat wordt opgespoord, zijn gegroepeerd. In MPI is een communicator een speciaal gemaakt serviceobject dat een groep processen en een aantal aanvullende parameters (context) combineert die worden gebruikt bij het uitvoeren van gegevensoverdrachtbewerkingen. De communicator die automatisch wordt gemaakt wanneer het programma start en alle processen op het cluster omvat, wordt MPI_COMM_WORLD genoemd. Tijdens berekeningen kunnen nieuwe procesgroepen en communicators worden aangemaakt en bestaande procesgroepen en communicators worden verwijderd. Hetzelfde proces kan tot verschillende groepen en communicatoren behoren. Collectieve bewerkingen worden gelijktijdig toegepast voor alle processen van de communicator, dus voor hen zal een van de parameters altijd de communicator zijn.

Bij het uitvoeren van bewerkingen voor het overbrengen van berichten in MPI-functies, is het noodzakelijk om het type gegevens aan te geven dat wordt overgedragen. MPI bevat een groot aantal basisgegevenstypen op basis van de standaardgegevenstypen van de taal C. Bovendien kan de programmeur hun gegevenstypen construeren met behulp van speciale MPI-functies. Hieronder vindt u een correspondentietabel voor basisgegevenstypen.

MPI-constanten C taal gegevenstype
MPI_INT aangemeld bij
MPI_UNSIGNED niet aangemeld
MPI_SHORT aangemeld bij
MPI_LONG ondertekend lang int
MPI_UNSIGNED_SHORT niet aangemeld
MPI_UNSIGNED_LONG niet ondertekend lang int
MPI_FLOAT vlot
MPI_DOUBLE dubbele
MPI_LONG_DOUBLE lange dubbele
MPI_UNSIGNED_CHAR Ongetekend char
MPI_CHAR ondertekend char

Een voorbeeld van het starten van de MPI-bibliotheek: student login, wachtwoord s304.

#erbij betrekken

#erbij betrekken

int hoofd (int argc, char * argv)

/ * MPI initialiseren * /

MPI_Init (& argc, & argv);

/ * verkrijg de rang van het proces * /

MPI_Comm_rank (MPI_COMM_WORLD, & rang);

/ * verkrijg het totale aantal processen * /

MPI_Comm_size (MPI_COMM_WORLD, & grootte);

printf ("Hallo wereld van proces% d of% d \ n", rang, grootte);

/ * MPI beëindigen * /

Compiler en linker worden gebruikt voor compilatie. Opdrachtregel mpic. (zie mpicc ... .- hulp)

Elk van de lopende processen moet zijn rangorde en het totale aantal processen weergeven. Laten we proberen dit programma te compileren en uit te voeren.

$ mpicc hallo.c –o hallo.o

$ mpicc hallo.o –o hallo

Het hallo-bestand is het uitvoerbare bestand van het voorbeeld. Je kunt het op één machine uitvoeren en zien dat het aantal processors 1 is en de rangorde van het proces 0:

$ ./hallo

Hallo wereld van proces 0 van 1

Wanneer u op de server werkt, wordt het commando gebruikt om te starten mpirun ... Het heeft twee hoofdargumenten - de naam van het bestand met de adressen van de knooppunten en het aantal knooppunten waarop het programma zal draaien.

$ mpirun n0-6 –v hosts hallo

Hallo wereld vanaf proces 0 van 7

Hallo wereld vanaf proces 3 van 7

Hallo wereld vanaf proces 5 van 7

Hallo wereld vanaf proces 4 van 7

Hallo wereld vanaf proces 2 van 7

Hallo wereld vanaf proces 6 van 7

Hallo wereld vanaf proces 1 van 7

Het programma zal draaien op 7 nodes (inclusief de server), en de adressen van deze nodes zijn in hosts-bestand. Afdrukken op het scherm werd uitgevoerd door processen die niet in de volgorde van hun rangen waren. Dit komt doordat de start van processen niet gesynchroniseerd is, maar er zijn speciale functies in MPI voor het synchroniseren van processen.

Het eenvoudigste programma bevat geen berichtenfuncties. Bij taken in de echte wereld moeten de processen met elkaar communiceren. Natuurlijk kost het verzenden van berichten tijd, wat de parallellisatiefactor van de taak vermindert. Hoe hoger de snelheid van de berichteninterface (bijv. Ethernet 10Mb/sec en Gigabit Ethernet), hoe lager de kosten voor gegevensoverdracht. Omdat de tijd van gegevensuitwisseling tussen processen is veel (orden van grootte) langer dan de tijd van toegang tot het eigen geheugen, de werkverdeling tussen processen moet 'grof' zijn en onnodige gegevensoverdracht moet worden vermeden.

Onder de problemen van numerieke analyse zijn er veel problemen, waarvan de parallellisatie duidelijk is. Zo wordt numerieke integratie in feite gereduceerd tot (talrijke) berekening van de integrand (wat natuurlijk is om toe te vertrouwen aan afzonderlijke processen), terwijl het hoofdproces het berekeningsproces bestuurt (de strategie bepaalt van de verdeling van integratiepunten over de processen en gedeeltelijke bedragen). Problemen met zoeken en sorteren in een lineaire lijst, het numeriek vinden van de wortels van functies, het zoeken naar de extrema van een functie van veel variabelen, het berekenen van reeksen en andere hebben dezelfde parallellisatie. In dit lab zullen we kijken naar twee parallelle algoritmen voor het berekenen van π.

Berekening van het getal π door de methode van numerieke integratie

Het is bekend dat

Als we de berekening van de integraal vervangen door eindige sommatie, hebben we , waarbij n het aantal sommatiesecties in numerieke integratie is. Het gebied van elke sectie wordt berekend als het product van de breedte van de 'strook' door de waarde van de functie in het midden van de 'strook', vervolgens worden de gebieden opgeteld door het hoofdproces (een uniform raster is gebruikt).

Het is duidelijk dat het parallelliseren van deze taak gemakkelijk is als elk proces zijn gedeeltelijke som berekent en vervolgens het berekeningsresultaat doorgeeft aan het hoofdproces. Hoe vermijd je hier repetitieve berekeningen? Het proces moet zijn rangorde kennen, het totale aantal processen en het aantal intervallen waarin het segment zal worden verdeeld (hoe meer intervallen, hoe hoger de nauwkeurigheid). Vervolgens berekent het proces in een cyclus van 1 tot het aantal intervallen het gebied van de strip op het i-de interval en gaat dan niet naar het volgende i + 1-interval, maar naar het interval i + m , waarbij m het aantal processen is. Zoals we al weten, zijn er functies om de rang en het totale aantal processen te krijgen MPI_Comm_rank en MPI_Comm_size ... Alvorens met de berekeningen te beginnen, moet het hoofdproces het aantal intervallen overdragen aan de rest en na de berekeningen de ontvangen deelsommen daarvan verzamelen en optellen, in MPI wordt dit geïmplementeerd door berichten te verzenden. Het is handig om de functie voor collectieve interactie te gebruiken om berichten hierheen te sturen. MPI_Bcast die dezelfde gegevens van het ene proces naar alle anderen stuurt. Er zijn 2 opties om deelbedragen te innen - u kunt gebruik maken van MPI_Verzamel , die gegevens van alle processen verzamelt en deze aan één geeft (een array van m-elementen wordt verkregen, waarbij m het aantal processen is) of MPI_Verminderen . MPI_Verminderen handelt op dezelfde manier MPI_Verzamel - verzamelt gegevens van alle processen en geeft deze aan één, maar niet in de vorm van een array, maar voert voorlopig een bepaalde bewerking uit tussen de elementen van de array, bijvoorbeeld sommatie en geeft dan één element. Voor deze taak lijkt het handiger in gebruik MPI_Verminderen ... De tekst van het programma staat hieronder:

#include "mpi.h"

#erbij betrekken

#erbij betrekken

dubbele f (dubbele a)

rendement (4,0 / (1,0 + a * a));

int hoofd (int argc, char * argv)

int n, myid, numprocs, ik;

dubbele PI25DT = 3.141592653589793238462643;

dubbele mypi, pi, h, som, x;

dubbele starttijd, eindtijd;

MPI_Init (& argc, & argv);

MPI_Comm_size (MPI_COMM_WORLD, & numprocs);

MPI_Comm_rank (MPI_COMM_WORLD, & mijnid);

startwtime = MPI_Wtime ();

MPI_Bcast (& n, 1, MPI_INT, 0, MPI_COMM_WORLD);

h = 1,0 / (dubbel) n;

voor (i = myid + 1; i<= n; i += numprocs)

x = h * ((dubbel) i - 0,5);

MPI_Reduce (& mijnpi, & pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);

printf ("pi is ongeveer% .16f, fout is% .16f \ n",

pi, fabs (pi - PI25DT));

eindtijd = MPI_Wtijd ();

printf ("wandkloktijd =% f \ n",

eindtijd-starttijd);

Laten we de functieaanroepen eens nader bekijken MPI_Bcast en MPI_Verminderen :

MPI_Bcast (& n, 1, MPI_INT, 0, MPI_COMM_WORLD) - de inhoud van variabele n en één element van het type MPI_INT van een proces met rang 0 worden verzonden naar alle andere processen (MPI_COMM_WORLD - alle processen in de communicator) naar dezelfde variabele N. Na deze oproep weet elk proces het totale aantal slots. Aan het einde van de berekeningen somt MPI_Reduce (& mypi, & pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD) (parameter MPI_SUM) de waarden op van de mypi-variabelen van het MPI_DOUBLE-type van elk proces en schrijft het resultaat naar de proces pi variabele met rang 0. Het proces gebruikt de functie MPI_Wtime.

dubbele MPI_Wtime ();

MPI_Wtime retourneert het aantal seconden, in drijvende-kommanotatie, die de tijd vertegenwoordigt die is verstreken sinds de start van het programma.

Berekenen (volgens de Monte Carlo-methode)

De 'shooting'-methode kan worden gebruikt om de waarde van π te berekenen. Zoals toegepast op dit geval, bestaat de methode uit het genereren van punten die uniform verdeeld zijn over een tweedimensionaal gebied en het bepalen.

De op deze manier berekende waarde van π is bij benadering, in het algemeen neemt de nauwkeurigheid van het berekenen van de gewenste waarde toe met een toename van het aantal 'shots' en de kwaliteit van de random number generator; soortgelijke methoden worden gebruikt in het geval van problemen bij nauwkeurige numerieke schatting.

Het parallelle algoritme voor het berekenen van het getal π met deze methode is in veel opzichten vergelijkbaar met het vorige algoritme dat we hebben overwogen. Om willekeurige getallen te genereren, moet je de srand-functie gebruiken, die de rangorde van het proces als argument (het begin van de reeks) instelt, zodat elk proces zijn eigen reeks heeft.

#erbij betrekken

void srand (niet-ondertekend zaad);

De functie srand () stelt het beginnummer in voor de reeks die wordt gegenereerd door de functie rand ().

#erbij betrekken

int rand (ongeldig);

De functie rand () genereert een reeks pseudo-willekeurige getallen. Elke aanroep van de functie retourneert een geheel getal tussen nul en de RAND_MAX-waarde.

Om het resultaat te verzamelen, is het ook handig om MPI_Reduce te gebruiken met de specificatie van de sommatiebewerking (MPI_SUM), en vervolgens de resulterende som te delen door het aantal processors, om het rekenkundig gemiddelde te krijgen.

int MPI_Reduce (void * sendbuf, void * recvbuf, int count,

MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm);

Opties:

sendbuf adres van de verzendbuffer

recvbuf bufferadres ontvangen

op reductie operatie

com-communicator

int MPI_Bcast (void * buffer, int count, MPI_Datatype datatype, int root,

MPI_Comm com);

Opties:

bufferadres van de verzend-/ontvangstbuffer

tel aantal elementen in de verzendbuffer (geheel getal)

datatype datatype van de elementen van de verzendbuffer

root hoofdprocesnummer (geheel getal)

com-communicator

Oefening: in overeenstemming met het variantnummer, compileer en voer het parallelprogramma uit dat het nummer π berekent volgens het gegeven algoritme.

Voer de taak uit op één knoop punt en vanuit een cluster, op een opgegeven aantal knoop punten. Schat de rekentijd, nauwkeurigheid en parallellisatiefactor van Amdahl, rekening houdend met de netwerklatentie theoretisch en op basis van de resultaten van het werk.

Job opties

Optie nr. Algoritme Aantal processors Aantal iteraties op elke processor
Numerieke integratie
Monte Carlo
Numerieke integratie
Monte Carlo
Numerieke integratie
Monte Carlo
Numerieke integratie
Monte Carlo
Numerieke integratie
Monte Carlo
Numerieke integratie
Monte Carlo
Numerieke integratie
Monte Carlo
Numerieke integratie
Monte Carlo
Numerieke integratie
Monte Carlo
Numerieke integratie
Monte Carlo

· Verklaring van het probleem, optie.

· De tekst van het parallelprogramma in de taal C volgens de taak.

· Resultaten van het draaien van het programma op één knooppunt, uitvoeringstijd t i, resultaat van de berekening, fout.

· Resultaten van het draaien van het programma op de server, uitvoeringstijd, resultaat van de berekening, fout.

· Beschrijf het parallelle algoritme, de informatiestromen tijdens de uitvoering van het programma en het laden van het cachegeheugen van knooppunten. Bereken de Amdahl-coëfficiënt - Kj op basis van de resultaten van het programma.

· Maak, rekening houdend met de resultaten van het werk van een groep leerlingen, een histogram van de afhankelijkheid van K j, t i van het aantal processors dat bij de berekeningen betrokken is.

  • zelfstudie

In dit bericht zullen we het hebben over het organiseren van gegevensuitwisseling met behulp van MPI aan de hand van het voorbeeld van de Intel MPI-bibliotheek. We denken dat deze informatie interessant is voor iedereen die in de praktijk kennis wil maken met het veld van parallelle high-performance computing.

We zullen een korte beschrijving geven van hoe gegevensuitwisseling is georganiseerd in parallelle applicaties op basis van MPI, evenals links naar externe bronnen met een meer gedetailleerde beschrijving. In het praktische gedeelte vindt u een beschrijving van alle fasen van de ontwikkeling van de "Hello World" demo MPI-applicatie, van het opzetten van de vereiste omgeving tot het starten van het programma zelf.

MPI (Message Passing Interface)

MPI is een interface voor het doorgeven van berichten tussen processen die dezelfde taak uitvoeren. Het is voornamelijk bedoeld voor gedistribueerde geheugensystemen (MPP) in tegenstelling tot bijvoorbeeld OpenMP. Een gedistribueerd (cluster) systeem is in de regel een set rekenknooppunten die zijn verbonden door krachtige communicatiekanalen (bijvoorbeeld InfiniBand).

MPI is de meest gebruikte interfacestandaard voor gegevensoverdracht bij parallel programmeren. MPI-standaardisatie wordt verzorgd door het MPI Forum. Er zijn MPI-implementaties voor de meeste moderne platforms, besturingssystemen en talen. MPI wordt veel gebruikt bij het oplossen van verschillende problemen op het gebied van computationele fysica, farmaceutica, materiaalkunde, genetica en andere kennisgebieden.

Een parallel programma vanuit het oogpunt van MPI is een reeks processen die op verschillende rekenknooppunten worden uitgevoerd. Elk proces komt voort uit dezelfde programmacode.

De belangrijkste bewerking in MPI is het doorgeven van berichten. MPI implementeert bijna alle basiscommunicatiepatronen: point-to-point, collectief en eenzijdig.

Werken met MPI

Laten we eens kijken naar een levend voorbeeld van hoe een typisch MPI-programma werkt. Als demotoepassing nemen we de broncode van het voorbeeld dat bij de Intel MPI-bibliotheek wordt geleverd. Voordat we ons eerste MPI-programma uitvoeren, moeten we een werkomgeving voor experimenten voorbereiden en opzetten.

Een geclusterde omgeving opzetten

Voor experimenten hebben we een paar rekenknooppunten nodig (bij voorkeur met vergelijkbare kenmerken). Als je geen twee servers bij de hand hebt, kun je altijd gebruik maken van clouddiensten.

Voor deze demonstratie heb ik gekozen voor Amazon Elastic Compute Cloud (Amazon EC2). Voor nieuwe gebruikers biedt Amazon een gratis proefjaar voor servers op instapniveau.

Werken met Amazon EC2 is intuïtief. Als u vragen heeft, kunt u de gedetailleerde documentatie (in het Engels) raadplegen. Als u wilt, kunt u elke andere soortgelijke dienst gebruiken.

We creëren twee werkende virtuele servers. Selecteer in de bedieningsconsole EC2 virtuele servers in de cloud, dan Instantie starten("Instance" verwijst naar een virtuele serverinstantie).

De volgende stap is het selecteren van het besturingssysteem. Intel MPI Library ondersteunt zowel Linux als Windows. Voor de eerste kennismaking met MPI kiezen we voor OS Linux. We kiezen Red Hat Enterprise Linux 6.6 64-bit of SLES11.3 / 12.0.
We kiezen Instantietype(server type). Voor experimenten is t2.micro geschikt voor ons (1 vCPU's, 2,5 GHz, Intel Xeon-processorfamilie, 1 GiB RAM). Als nieuw geregistreerde gebruiker kon ik dit type gratis gebruiken - het teken "In aanmerking komen voor gratis niveau". wij zetten Aantal instanties: 2 (aantal virtuele servers).

Nadat de service ons heeft gevraagd om te rennen Instanties starten(geconfigureerde virtuele servers), sla de SSH-sleutels op die nodig zijn om van buitenaf met virtuele servers te communiceren. De status van virtuele servers en IP-adressen voor communicatie met de servers van de lokale computer kan worden gecontroleerd in de beheerconsole.

Een belangrijk punt: in de instellingen Netwerk en beveiliging / Beveiligingsgroepen het is noodzakelijk om een ​​regel te maken waarmee we poorten openen voor TCP-verbindingen - dit is nodig voor de MPI-procesmanager. De regel kan er als volgt uitzien:

Type: Aangepaste TCP-regel
Protocol: TCP
Poortbereik: 1024-65535
Bron: 0.0.0.0/0

Om veiligheidsredenen zou een meer beperkende regel kunnen worden gespecificeerd, maar voor onze demo is dit voldoende.

En tot slot een kleine enquête over mogelijke onderwerpen voor toekomstige publicaties over high performance computing.

Alleen geregistreerde gebruikers kunnen deelnemen aan het onderzoek. , Alsjeblieft.