12.07.2015 Aufrufe

Vergleich von Message Passing und Distributed Shared Memory ...

Vergleich von Message Passing und Distributed Shared Memory ...

Vergleich von Message Passing und Distributed Shared Memory ...

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

Eidgenössische Technische Hochschule ZürichInstitut für Computersysteme<strong>Vergleich</strong> <strong>von</strong> <strong>Message</strong> <strong>Passing</strong> <strong>und</strong><strong>Distributed</strong> <strong>Shared</strong> <strong>Memory</strong> unter Windows NTSendAsyncsuccess/pending/errorSendSync RecvSync CallBack CheckRequestssuccess/pending/errorpending/errorsuccess/errorsuccess/pending/errorReady?noyesNwSendSyncSendListReadynoyesnoReady? Norm?yessuccess/pending/errorAsyncRecvSending ListSyncRecvNwCheckRequestsdoneRecvListSendDoneRequestReplyControllZero Copy Layer Network LayerEine Diplomarbeit<strong>von</strong> Roman RothWS 1998/99Professor: Prof. Thomas M. StrickerAssistent: Christian Kurmann


Institut für ComputersystemeETH ZürichSeite 2Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeZusammenfassungImmer leistungsfähigere PCs <strong>und</strong> Workstations <strong>und</strong> immer schnellere Netzwerke ermöglichenimmer breitere Einsatzgebiete für PC-Clusters. Doch sehr oft leidet die Performance der Hardwareunter der eingeschränkten Leistung der Software. Diverse Studien haben gezeigt, dass meist dieKopiervorgänge, die in konventionellen Kommunikationsmodellen vorkommen, für die schlechteLeistung der Software verantwortlich sind. Gefordert ist also sogenannte Zero-Copy-Kommunikation, das Senden <strong>von</strong> Datenpaketen aus dem Speicher des Senders direkt in denSpeicher des Empfängers also.Zero-Copy ist jedoch nur dann möglich, wenn Hardware <strong>und</strong> Betriebssystem dies aktivunterstützen. Auf seiten der Hardware müssen Techniken wie Direct-<strong>Memory</strong>-Access oder Device-Speicher vorhanden sein. Diese Techniken müssen durch das Betriebssystem effizient eingesetztwerden können. Eine Untersuchung der Kernel-Strukturen <strong>von</strong> Microsoft Windows NT 4.0 hatgezeigt, dass dieses Betriebssystem alle Funktionalitäten bietet, die eine effiziente Implementation<strong>von</strong> Zero-Copy-Kommunikation fordert.Diese Arbeit sollte eigentlich auf einem Myrinet-Netzwerk unter Windows NT basieren. Um dasaufwendige Schreiben <strong>von</strong> Kernel-Mode-Treibern zu umgehen, wollte man auf das GM-API <strong>von</strong>Myricom aufgebauen. Leider stand im Verlaufe der Arbeit keine lauffähige Version zur Verfügung.Deshalb wurde die Zero-Copy-Kommunikation zuerst in der Theorie betrachtet. Das eigentlicheSenden <strong>und</strong> Empfangen wird <strong>von</strong> der Hardware bzw. <strong>von</strong> Software wie GM übernommen. DasProblem liegt viel mehr in der Frage, wohin wird empfangen? Gr<strong>und</strong>sätzlich können zweiunterschiedliche Kommunikationsarten zum Einsatz kommen:ŒŒDer Empfänger stellt immer genügend Speicher für den Empfang zur Verfügung, so dass derSender jederzeit senden kann. Diese Kommunikationsart eignet sich nur für kleinere Pakete.Der Empfänger teilt dem Sender seine Empfangsbereitschaft für jedes Datenpaket mit. Es wirdalso synchronisiert. Dies lohnt sich nur für grössere Pakete.Diese beiden Kommunikationsarten sind in einem sogenannten Zero-Copy-Layer implementiertworden. Dieser Layer wurde netzwerktechnologieunabhängig entwickelt. Zwischen den Layer <strong>und</strong>dem Netzwerktreiber oder -API wurde ein Netzwerk-Layer geschoben, der die netzwerkabhängigenTeile der Zero-Copy-Kommunikation enthält. Mangels einer lauffähigen GM-Version wurden zweiNetzwerk-Layer implementiert, die an sich nichts mit Zero-Copy zu tun haben, jedoch stabile <strong>und</strong>(im ersten Fall) lokal ebenso schnelle Kommunikation erlauben: Es handelt sich um Named-Pipes<strong>und</strong> Windows Sockets.Diese Arbeit hat gezeigt, dass sich unterschiedliche Kommunikationsmodelle wie <strong>Message</strong>-<strong>Passing</strong> <strong>und</strong> <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong> durchaus auf einen solchen Zero-Copy-Layer aufbauenlassen. Die Differenzen der Anforderungen der beiden Modelle an die Schnittstelle zum Zero-Copy-Layer sind äusserst gering, so dass eine Schnittstelle definiert werden kann, die <strong>von</strong> beidenModellen benutzt werden kann.Dies beweisen die Implementation <strong>von</strong> zwei Prototypen, die im Rahmen dieser Arbeit entwickeltwurden. Für das <strong>Message</strong>-<strong>Passing</strong> wurde eine Minimalimplementation <strong>von</strong> MPI erstellt. Dieseenthält sowohl blockierendes wie nicht-blockierendes Senden <strong>und</strong> Empfangen, sowie eine Barrier.Studien haben gezeigt, dass die kollektiven Kommunikationsformen <strong>von</strong> MPI durchaus auf denimplementierten Punkt-zu-Punkt-Funktionen effizient aufbauen können.Als Schnittstelle für einen <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong>-Prototypen wurden Teile <strong>von</strong> OpenMPverwendet. Der Prototyp enthält einen lauffähigen <strong>Memory</strong>-Manager, der nach dem Prinzip derLazy-Release-Consistency <strong>von</strong> TreadMarks funktioniert. Auf Multi-Writer-Funktionalität, wie sieTreadMarks enthält, wurde auf Gr<strong>und</strong> <strong>von</strong> Widersprüchlichkeit mit der Zero-Copy-Kommunikationverzichtet.Messungen der Bandbreiten <strong>und</strong> Latenzzeiten (über Named-Pipes) haben gezeigt, dass der Zero-Copy-Layer durchaus eine leistungsfähige Variante für echte Zero-Copy-Kommunikation sein kann.Zwei konkrete Applikationen, die für die beiden Prototypen geschrieben wurden, zeigen, dass derMPI-Prototyp vernünftig einsetzbar ist. Der OpenMP-Prototyp jedoch ist für gewisse Probleme zuwenig mächtig. Die Leistungsfähigkeit liesse sich durch entsprechende Erweiterungen jedocherheblich steigern.Diplomarbeit <strong>von</strong> Roman Roth Seite 3


Institut für ComputersystemeETH ZürichSeite 4Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeInhaltsverzeichnis1 Einleitung.........................................................................................................................92 Zero-Copy-Kommunikation .........................................................................................112.1 Die unsynchronisierte Kommunikation kleiner Pakete ......................................112.2 Die synchronisierte Kommunikation grosser Pakete.........................................122.3 Der Zero-Copy-Layer in der Theorie .................................................................122.4 Ein alternativer Ansatz für Zero-Copy-Kommunikation .....................................142.4.1 Fehlende Unterstützung durch Windows NT .......................................142.4.2 Fehlende Unterstützung durch höhere Layer ......................................143 Zero-Copy unter Windows NT 4.0 ...............................................................................173.1 Die Windows NT 4.0 Kernel-Architektur............................................................173.2 Das Windows NT Interruptmodell......................................................................183.3 Die Windows NT-Treibermodelle.......................................................................183.3.1 Das generische Treibermodell .............................................................183.3.2 Das NDIS-Modell..................................................................................203.3.3 Beurteilung aus der Sicht <strong>von</strong> Zero-Copy ............................................213.4 Das Speichermanagement <strong>von</strong> Windows NT 4.0..............................................213.4.1 Datenstrukturen <strong>und</strong> System-Threads .................................................213.4.2 Beurteilung aus der Sicht <strong>von</strong> Zero-Copy ............................................234 Die Schnittstellen zum <strong>Message</strong>-<strong>Passing</strong>- <strong>und</strong> DSM-Layer .....................................274.1 Die Schnittstelle zwischen <strong>Message</strong>-<strong>Passing</strong>- <strong>und</strong> Zero-Copy-Layer...............274.1.1 Punkt-zu-Punkt-Kommunikation...........................................................274.1.2 Kollektive Kommunikation ....................................................................294.1.3 Synchronisation....................................................................................304.1.4 Die Schnittstelle....................................................................................304.2 Die Schnittstelle zwischen DSM- <strong>und</strong> Zero-Copy-Layer ...................................314.2.1 Der Page-Request................................................................................314.2.2 Der Page-Reply....................................................................................324.2.3 Synchronisation....................................................................................324.2.4 Die Schnittstelle....................................................................................334.3 Ein <strong>Vergleich</strong> der Schnittstellen.........................................................................335 Der Zero-Copy-Layer ....................................................................................................355.1 Die Requests .....................................................................................................355.2 Die Abläufe ........................................................................................................365.2.1 Initialisieren der Layer <strong>und</strong> Erstellen der Verbindungen ......................365.2.2 Call-Back-Funktion...............................................................................365.2.3 Asynchrones Senden <strong>und</strong> Empfangen.................................................375.2.4 Synchrones Senden <strong>und</strong> Empfangen ..................................................375.2.5 Die Zero-Copy-Layer-Variante für OpenMP ........................................395.3 Die Schnittstellen...............................................................................................405.3.1 Die Requests........................................................................................405.3.2 Der Status ............................................................................................415.3.3 Die obere Schnittstelle des Netzwerk-Layers ......................................415.3.4 Die untere Schnittstelle des Zero-Copy-Layers ...................................435.3.5 Die obere Schnittstelle des Zero-Copy-Layers ....................................445.4 Die Implementation der beiden Layer ...............................................................476 Der OpenMP-Prototyp ..................................................................................................496.1 Die Konzepte des OpenMP-Prototyps ..............................................................496.1.1 Kommunikation.....................................................................................496.1.2 Threading .............................................................................................496.1.3 <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong> .................................................................506.1.4 Private <strong>Memory</strong> ....................................................................................56Diplomarbeit <strong>von</strong> Roman Roth Seite 5


Institut für ComputersystemeETH Zürich6.2 Das API des OpenMP-Prototypen .................................................................... 576.2.1 OMP_INIT / OMP_EXIT ...................................................................... 576.2.2 OMP_ALLOC / OMP_ALLOC_NEW_PAGE....................................... 596.2.3 OMP_PARALLEL / OMP_END_PARALLEL....................................... 606.2.4 OMP_DO / OMP_END_DO................................................................. 616.2.5 OMP_SECTIONS / OMP_END_SECTIONS....................................... 636.2.6 OMP_MASTER / OMP_END_MASTER ............................................. 646.2.7 OMP_BARRIER .................................................................................. 656.2.8 OMP_PRIVATE / OMP_FIRSTPRIVATE / OMP_LASTPRIVATE...... 656.2.9 OMP_GET_NUM_THREADS.............................................................. 666.2.10 OMP_GET_MAX_THREADS............................................................ 666.2.11 OMP_GET_THREAD_NUM.............................................................. 666.2.12 OMP_GET_xxx_NODES / OMP_GET_NODE_NUM ....................... 676.2.13 OMP_LOCK / OMP_UNLOCK .......................................................... 676.3 Im Prototypen nicht enthaltene Funktionalität .................................................. 686.4 Mögliche Erweiterungen <strong>und</strong> Verbesserungen................................................. 686.5 Die Anwendung des OpenMP-Prototypen........................................................ 697 Der MPI-Prototyp.......................................................................................................... 717.1 Die Konzepte des MPI-Prototypen ................................................................... 717.1.1 Threading............................................................................................. 717.1.2 Kommunikation.................................................................................... 727.1.3 Synchronisation ................................................................................... 737.2 Das API des MPI-Prototypen............................................................................ 737.2.1 MPI_Register_routine.......................................................................... 747.2.2 MPI_Run.............................................................................................. 757.2.3 MPI_Init................................................................................................ 757.2.4 MPI_Finalize........................................................................................ 757.2.5 MPI_Send............................................................................................ 757.2.6 MPI_Isend............................................................................................ 767.2.7 MPI_Recv ............................................................................................ 767.2.8 MPI_Irecv............................................................................................. 767.2.9 MPI_Test ............................................................................................. 777.2.10 MPI_Testall........................................................................................ 777.2.11 MPI_Wait ........................................................................................... 777.2.12 MPI_Waitall........................................................................................ 777.2.13 MPI_Get_count.................................................................................. 777.2.14 MPI_Barrier ....................................................................................... 787.2.15 MPI_Comm_size ............................................................................... 787.2.16 MPI_Comm_rank............................................................................... 787.3 Die Anwendung des MPI-Prototypen................................................................ 788 Performancemessungen ............................................................................................. 798.1 Bandbreitenmessungen über Sockets.............................................................. 798.1.1 Die Windows Sockets als Basis .......................................................... 798.1.2 Der Zero-Copy-Layer........................................................................... 818.1.3 Der MPI-Prototyp................................................................................. 828.1.4 Der OpenMP-Prototyp ......................................................................... 838.2 Profiling über Sockets....................................................................................... 848.2.1 MPI-Prototyp <strong>und</strong> Zero-Copy-Layer .................................................... 848.2.2 OpenMP-Prototyp <strong>und</strong> Zero-Copy-Layer ............................................ 878.3 Messungen über Named-Pipes ........................................................................ 888.3.1 Latenzmessung ................................................................................... 888.3.2 Bandbreitenmessung........................................................................... 888.3.3 Profiling................................................................................................ 909 Die Applikationen......................................................................................................... 939.1 Quick-Sort......................................................................................................... 939.1.1 Quick-Sort mit MPI .............................................................................. 939.1.2 Quick-Sort mit OpenMP....................................................................... 939.1.3 Die Performance.................................................................................. 94Seite 6Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme9.2 Gauss-Elimination .............................................................................................959.2.1 Gauss-Elimination mit MPI...................................................................959.2.2 Gauss-Elimination mit OpenMP ...........................................................959.2.3 Die Performance ..................................................................................969.3 Bessere Testapplikationen ................................................................................96A Messresultate ...............................................................................................................97A.1 Bandbreitenmessungen über Sockets ..............................................................97A.1.1 100 mbit Ethernet.................................................................................97A.1.2 GigaBit Ethernet...................................................................................98A.1.3 Lokale Loopback-Messung..................................................................99A.1.4 Myricom GM-Sockets ........................................................................100A.2 Bandbreitenmessungen über Named-Pipes ...................................................101A.3 Profiling-Informationen über Named-Pipes.....................................................102A.3.1 Asynchrone Kommunikation ..............................................................102A.3.2 Synchrone Kommunikation................................................................103B Aufgabenstellung.......................................................................................................105C Quellenverzeichnis.....................................................................................................107Diplomarbeit <strong>von</strong> Roman Roth Seite 7


Institut für ComputersystemeETH ZürichSeite 8Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme1 EinleitungPCs werden Monat für Monat leistungsfähiger <strong>und</strong> noch schneller fallen ihre Preise. ModerneNetzwerke weisen respektable Bandbreiten <strong>und</strong> immer kleinere Latenzzeiten auf. Die logischeFolge dieser beiden Tendenzen ist der vermehrte Einsatz <strong>von</strong> PC-Clustern anstelle <strong>von</strong>Hochleistungsrechnern.Schnelle Hardware alleine sagt jedoch noch nichts über die Leistungsfähigkeit solcher verteilterSysteme aus. Nur wenn die Software diese Performance auszunützen vermag, sind solcheSysteme auch wirklich brauchbar. Sowohl das Betriebssystem wie auch dieKommunikationsmodelle müssen sich den neuen Anforderungen stellen.Im Rahmen dieser Arbeit wurden zwei Kommunikationsmodelle betrachtet: <strong>Message</strong>-<strong>Passing</strong> istein relativ einfacher <strong>und</strong> verständlicher Ansatz. <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong> ist wesentlichkomplexer, aber deswegen nicht uninteressanter. Denn beide Systeme definieren im wesentlichennur ein Frontend. Erst die Implementation entscheidet über die Brauchbarkeit der Systeme.Traditionelle Kommunikationsmodelle orientierten sich häufig am OSI-Protokoll-Stack. Zuversendende Daten werden im Stack nach unten gereicht, empfangene nach oben. Ein oder garmehrmaliges Kopieren der Daten <strong>von</strong> einem Puffer in den nächsten sind in solchen Stacks dieRegel. Dabei entstehen beträchtliche Performanceeinbussen. Es muss nach anderen Modellengesucht werden. Insbesondere muss diese Kopiererei umgangen werden: Zero-Copy also.Kapitel 2 dieser Dokumentation befasst sich mit der Theorie zur Zero-Copy-Kommunikation, dennso simpel, wie es sich anhört, ist es nicht. Es gibt klare Regeln, wie eine solche Kommunikationabzulaufen hat.Doch mit einem Kommunikationsmodell <strong>und</strong> dem Wissen um Zero-Copy ist es noch nicht getan.Diese Art Kommunikation ist ganz wesentlich auf die Unterstützung des Betriebssystemsangewiesen. Im Rahmen dieser Arbeit wurde Microsoft Windows NT 4.0 eingesetzt. Ein intensivesStudium der internen Abläufe <strong>und</strong> Strukturen war notwendig. Insbesondere interessierten dieHandhabung <strong>von</strong> Hardwareressourcen, die Treibermodelle <strong>und</strong> das Speichermanagement. Kapitel3 gibt einen kurzen Abriss der im Zusammenhang mit Zero-Copy wichtigen Eigenschaften <strong>von</strong>Windows NT.Die Brauchbarkeit <strong>von</strong> Zero-Copy hängt auch da<strong>von</strong> ab, wie gut bestehende höhereKommunikationsmodelle wie <strong>Message</strong>-<strong>Passing</strong> <strong>und</strong> <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong> auf das Zero-Copy-Modell aufbauen können. Kapitel 4 bespricht die Schnittstellen zwischen diesen dreiModellen.Die Verneinung des OSI-Protokoll-Stack heisst nicht, dass die Kommunikation nicht durch einSchichtenmodell dargestellt werden könnte. Dies beweist der im Laufe dieser Arbeitimplementierte <strong>und</strong> in Kapitel 5 vorgestellte Zero-Copy-Layer. Er beinhaltet die in Kapitel 2vorgestellten Kommunikationsmuster. Dank eines weiteren Layers, der zwischen den Zero-Copy-Layer <strong>und</strong> die verwendete Netzwerktechnologie geschoben wurde, konnte der Zero-Copy-Layerweitgehend unabhängig vom Netzwerk implementiert werden.Dieser Zero-Copy-Layer bildet die Gr<strong>und</strong>lage für die nächsten Layer. Denn die beiden erwähntenFrontends wurden als Prototypen implementiert. Dabei beschränkte man sich jeweils auf diewichtigsten Primitiven.Das <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong>-Modell wurde in Form eines OpenMP-Prototypen implementiert.Die Programmierschnittstelle des Prototypen wurde so weit als möglich an die Vorgaben desOpenMP-Standards angelehnt. Für die Implementation mussten jedoch weitere Techniken, wie siebeispielsweise in TreadMarks enthalten sind, zugezogen werden. Kapitel 6 erläutert dieverwendeten Techniken <strong>und</strong> beschreibt das API des Prototypen.Fast schon unproblematisch war die Implementation des MPI-Prototypen, der das <strong>Message</strong>-<strong>Passing</strong>-Modell verwendet. Beschrieben wird die Implementation <strong>und</strong> das API in Kapitel 7.Die Theorie ist das eine, die Praxis das andere. Die Leistungsfähigkeit der implementiertenSchichten wird in Kapitel 8 betrachtet, <strong>und</strong> praktische Anwendungen der beiden Prototypen sind inKapitel 9 vorgestellt.Diplomarbeit <strong>von</strong> Roman Roth Seite 9


Institut für ComputersystemeETH ZürichSeite 10Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme2 Zero-Copy-KommunikationDie Hardware im PC-Bereich ist in den letzten Jahren immer leistungsfähiger <strong>und</strong> preiswertergeworden. Nicht nur die PCs wurden schneller, auch die Netzwerktechnologien, die diese PCsverbinden, haben an Bandbreite zugelegt <strong>und</strong> an Latenzzeit verloren. Messungen zeigen heutejedoch, dass die Software diese Spitzenleistungen nicht mehr auszunützen vermag. Nicht zuletztsind dafür fehlende Betriebssystemunterstützung <strong>und</strong> nicht mehr zeitgemässeKommunikationsmodelle verantwortlich. Viele dieser Modelle basieren auf den Überlegungen desOSI-Referenz-Modells. Zu kommunizierende Daten werden Schicht für Schicht den Protokoll-Stackherunter <strong>und</strong> beim Empfänger wieder heraufgereicht. In den meisten Fällen ist dieser Vorgang mitmindestens einem, wenn nicht sogar mit mehreren Kopiervorgängen belastet. Dieses Kopierenstellt einen wesentlichen Gr<strong>und</strong> für die Ineffizienz klassischer Modelle dar. Es müssen also neueModelle her.Ein zentrales Thema dieser Arbeit ist daher die Zero-Copy-Kommunikation. Das heisst, dasVerschieben <strong>von</strong> Information direkt aus dem Speicher des Senders in den Speicher desEmpfängers ohne aufwendiges Kopieren in Zwischenspeicher.Das Verwenden <strong>von</strong> Puffern hat neben dem Nachteil des Kopierens jedoch auch einen grossenVorteil: Das Zwischenspeichern in Sender <strong>und</strong>/oder Empfänger entkoppelt die Sende- <strong>und</strong>Empfangsfunktion. Das heisst im wesentlichen, dass der Sender praktisch jederzeit Senden kann,ohne Rücksicht darauf nehmen zu müssen, ob der Empfänger bereit zum Empfangen ist odernicht.Ganz anders verhält sich Zero-Copy-Kommunikation. Es gibt zwei wesentlicheGr<strong>und</strong>sätze, die Zero-Copy-Kommunikation zubefolgen hat:Kleine PaketeSendenŒŒDer Empfänger ist dafür verantwortlich, dassimmer genug Empfangsspeicher zurVerfügung steht.Der Sender darf erst senden, wenn derEmpfänger Speicher für den Empfang zurVerfügung gestellt hat.Auf den ersten Blick scheinen diese beidenAussagen ungefähr das selbe zu bedeuten, doches gibt gewichtige Unterschiede, die zwei ganzverschiedene Kommunikationsarten mit Vor- <strong>und</strong>Nachteilen mit sich ziehen.Grosse PaketeSenderBereit zum EmpfangenSendenAbb. 2.1: Zwei KommunikationsartenEmpfänger2.1 Die unsynchronisierte Kommunikation kleiner PaketeDie erste der obigen Aussagen verlangt vom Empfänger, immer genügend Speicher für denEmpfang zur Verfügung zu stellen (siehe Abbildung 2.1, obere Darstellung). Doch was istgenügend? Und wie gross müssen bzw. dürfen diese Empfangspuffer sein? Speicher ist auchheute noch eine begrenzte Ressource. Daher ist es zwingend, dass sowohl Grösse wie auchAnzahl beschränkt sind. Zudem muss Speicher, der für den Empfang zur Verfügung gestellt wird,durch das System „gelocked“ werden. Das heisst, der mit dem Puffer verb<strong>und</strong>ene physischeSpeicher wird dem <strong>Memory</strong>-Manager quasi entzogen. Wird zu viel Speicher gelocked, dann kanndas massive Auswirkungen auf die Performance der Applikationen <strong>und</strong> des Systems bedeuten.Diese Art der Kommunikation eignet sich also nur für kleinere Pakete. Die Anzahl solcher kleinerPuffer hängt <strong>von</strong> der Reaktionszeit ab, die ein System aufweist, um bei Knappheit weitere Pufferzur Verfügung stellen zu können. Gr<strong>und</strong>sätzlich sollten es nur so viele wie unbedingt nötig sein.Neben der Grössenbeschränkung hat diese Kommunikationsart noch einen weiteren Nachteil: Mitdem Empfangen kann nicht gewartet werden, bis die Applikation eine Empfangsfunktion aufruft.Die Puffer müssen also vom System <strong>und</strong> nicht <strong>von</strong> der Applikation zur Verfügung gestellt werden.Eine Applikation hat also nicht die Möglichkeit, selbst zu bestimmen, wohin sie die Daten ablegenmöchte. Für die Applikation bieten sich dann nur zwei Möglichkeiten: Entweder sie gibt sich mitDiplomarbeit <strong>von</strong> Roman Roth Seite 11


Institut für ComputersystemeETH Zürichdem Puffer des Systems zufrieden, oder sie kopiert die Daten aus dem Puffer in den gewünschtenSpeicher.Grosser Vorteil: Der Sender braucht sich nicht um die Empfangsbereitschaft derEmpfängerapplikation zu kümmern. Er kann jederzeit einfach drauflossenden. Dies entkoppelt diebeiden Applikationen. Zudem muss der Empfänger sich nicht festlegen, <strong>von</strong> wem er Datenempfangen möchte. Es reicht, wenn ihm das System nach dem Empfang mitteilen kann, wer derAbsender war.Eine mögliche Schnittstelle einer solchen Kommunikationsart könnte folgendermassen aussehen:Send([in] int nRecvID, [in] void* pBuffer, [in] int nLength);Recv([out] int& nSendID, [out] void*& pBuffer, [out] int& nLenght);Die Sendefunktion definiert den Empfänger <strong>und</strong> gibt Position <strong>und</strong> Länge des Sendepuffers an. DieEmpfangsfunktion gibt eine Senderidentifikation sowie Position <strong>und</strong> Länge des Empfangspufferszurück. nLength ist natürlich beschränkt auf die maximale Grösse der Empfangspuffer.Diese unsynchronisierte Kommunikationsart kleiner Pakete wird in diesem Dokument oft auch mitasynchronem Senden <strong>und</strong> Empfangen bezeichnet.2.2 Die synchronisierte Kommunikation grosser PaketeDie zweite der oben gemachten Aussagen legt die Verantwortung für den korrekten Empfang in dieHände des Senders. Dieser darf nicht einfach drauflossenden, sondern muss solange warten, biser vom Empfänger die Bestätigung bekommen hat, dass er bereit ist für den Empfang. Sender <strong>und</strong>Empfänger werden als bewusst synchronisiert (siehe Abbildung 2.1, untere Darstellung).Der Nachteil liegt auf der Hand: Ein zusätzliches, kleines Paket, das natürlich unsynchronisiertverschickt werden muss, ist notwendig, um dem Sender die Bereitschaft des Empfängersmitzuteilen. Bei grossen Latenzzeiten kann dies eine erhebliche Performanceeinbusse bedeuten.Deshalb kann eine solche Kommunikationsart auch nur für grosse Pakete vorteilhaft sein, so dassdie zusätzliche Latenzzeit nicht mehr ins Gewicht fällt.Doch nicht nur die Latenz ist ein Nachteil der Synchronisation, sondern auch der Umstand, dassder Empfänger sich beim Aufruf der Empfangsfunktion bereits im klaren sein muss, <strong>von</strong> wem erDaten empfangen möchte.Wenn man im Zusammenhang mit synchronisierter Kommunikation überhaupt <strong>von</strong> Vorteilensprechen kann, dann ist das die Möglichkeit des Empfängers, festzulegen, wohin er die Datenempfangen möchte. Als kleiner Pluspunkt kann zusätzlich erwähnt werden, dass durch dieSynchronisierung erreicht wird, dass auch grosse Pakete verschickt werden können, ohne dasSystem durch viele, grosse Puffer (wie dies die unsynchronisierte Kommunikation tun würde) zubelasten.Eine mögliche Schnittstelle einer synchronisierten Kommunikationsart könnte folgendermassenaussehen:Send([in] int nRecvID, [in] void* pBuffer, [in] int nLength);Recv([in] int nSendID, [in] void* pBuffer, [in/out] int& nLenght);Der Sendefunktion wird der Empfänger sowie die Position <strong>und</strong> die Länge des Sendepuffersmitgegeben. Die Empfängerfunktion definiert den Sender wie auch die Position <strong>und</strong> Grösse desEmpfangspuffers <strong>und</strong> gibt nach dem Empfang die Grösse des gebrauchten Teils desEmpfangspuffers in nLength zurück.2.3 Der Zero-Copy-Layer in der TheorieEin allgemeines Zero-Copy-System beruht also auf zwei Kommunikationsarten mit ganzunterschiedlichen Eigenschaften:Seite 12Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeEigenschaft unsynchronisiert synchronisiertSynchronisation ist notwendig nein jaAbsender muss im voraus bestimmt werden nein jaEmpfangspuffer kann selber bestimmt werden nein jaPaketgrösse ist beschränkt ja nein *)Tab. 2.1: Eigenschaften der beiden Kommunikationsarten*) nur durch SpeichergrösseSoll ein solches System implementiert werden, dann muss irgendwann die maximale Grösse derunsynchronisierten Pakete festgelegt werden. Dabei spielen Werte wie die Grösse desphysikalischen Speichers <strong>und</strong> die Bandbreiten <strong>und</strong> Latenzzeiten der beiden Kommunikationsarteneine wesentliche Rolle. Je nach Gesamtsystem dürfte die resultierende Grösse zwischen 8 <strong>und</strong> 64kB liegen.Die Anzahl der Puffer ergibt sich aus Puffer- <strong>und</strong> Speichergrösse sowie der Rechenleistung <strong>und</strong>der erwarteten Belastung des Systems. Unter Umständen kann es sinnvoll sein, die gesamteKommunikation in einen Thread (eventuell mit erhöhter Priorität) zu verpacken, der dafür zuständigist, dass immer genügend Empfangspuffer zur Verfügung stehen.Gr<strong>und</strong>sätzlich lässt sich die beschriebene Zero-Copy-Kommunikation gut in einenKommunikationslayer verpacken. Ein solcher Layer würde folgende Aufgaben wahrnehmen:ŒŒŒEr alloziert den Speicher für den Empfang <strong>von</strong> kleinen, unsynchronisierten Paketen <strong>und</strong> istdafür verantwortlich, dass immer genügend freie Puffer zur Verfügung stehen.Er meldet die Bereitschaft des Empfängers an den Sender <strong>von</strong> synchronisierten Paketen.Er verzögert das Senden <strong>von</strong> synchronisierten Paketen bis die Bereitschaft des Empfängersbestätigt wurde.Ein solcher Layer könnte als Netzwerktreiber direkt auf einem Netzwerkadapter aufbauen, oder imUser-Space auf einen Netzwerktreiber oder ein Netzwerk-API zurückgreifen. Wichtig ist nur, dasssowohl die Hardware, wie auch allfällige Software Zero-Copy unterstützen. Im Bereich Hardwareheisst das, dass der Netzwerk-Adapter über Direct-<strong>Memory</strong>-Access (DMA) <strong>und</strong> eventuell überDevice-Speicher verfügen muss. Der Treiber oder das API müssen ungefähr folgende Funktionenanbieten können:ŒŒŒŒLock<strong>Memory</strong>ForRecv(void* pBuffer, int nLength);Der Layer übergibt mit dieser Funktion dem Treiber/API einen Speicherbereich, der für denEmpfang <strong>von</strong> unsynchronisierten Paketen verwendet werden kann.Send(int nRecvID, void* pBuffer, int nLength);Mit dieser Sendefunktion verschickt der Layer kleine unsynchronisierte Pakete an einenbestimmten Empfänger. Für den Empfang wird irgend ein mit Lock<strong>Memory</strong>ForRecvfreigegebener Puffer verwendet.Lock<strong>Memory</strong>ForRecvDirect(void* pBuffer, int nLength);Speicherbereiche, die für den Empfang <strong>von</strong> synchronisierten Paketen vorgesehen sind,werden mit dieser Funktion für den Empfang freigegeben.SendDirect(int nRecvID, void* pSource, void* pTarget, int nLength);Sendet einen bestimmten, durch pSource <strong>und</strong> nLength definierten Speicherbereich direkt andie durch pTarget definierte Stelle im Speicher der Empfängerapplikation. DerSpeicherbereich des Empfängers muss zuvor mit Lock<strong>Memory</strong>ForRecvDirect freigegebenworden sein.Ein API, das eine solche Schnittstelle anbietet, ist GM <strong>von</strong> Myricom [15]. Mit den Funktionengm_dma_malloc, gm_dma_free, gm_register_memory, gm_deregister_memory, gm_send<strong>und</strong> gm_directed_send kann die oben beschriebene Schnittstelle (bzw. deren Funktionalität)zwischen API <strong>und</strong> Zero-Copy-Layer realisiert werden.Eine konkrete Implementation eines solchen Zero-Copy-Layers ist in Kapitel 5 ausführlichbeschrieben.Diplomarbeit <strong>von</strong> Roman Roth Seite 13


Institut für ComputersystemeETH Zürich2.4 Ein alternativer Ansatz für Zero-Copy-KommunikationDie soeben vorgestellte Form der Zero-Copy-Kommunikation geht <strong>von</strong> einer ganz wichtigenAnnahme aus: Aller Speicher, der für den Empfang <strong>von</strong> Meldungen verwendet werden soll, wird<strong>von</strong> der Applikation (bzw. vom Zero-Copy-Layer) alloziert <strong>und</strong> dem empfangenden Treiber oderAPI zur Verfügung gestellt. Ein Top-Down-Verfahren sozusagen.Man könnte sichjedoch auch denumgekehrten Wegvorstellen: Der fürALLOCFREEFREEden Empfang ApplikationUser-ModeverantwortlicheTreiber alloziert denTreiberKernel-Modenötigen SpeicherALLOCFREE<strong>und</strong> stellt diesen derBottom-Up-Applikation zurVerfügung. DiesesNICTop-Down-VerfahrenNICBottom-Up-VerfahrenVerfahren wäreeigentlich derAbb. 2.2: Die zwei Verfahren für die Allokation des Empfangspuffersnatürlichste Weg zur Zero-Copy-Kommunikation. Durch ein Remapping könnte derEmpfangsspeicher des Treibers in den virtuellen Adressraum der Applikation eingeblendet werden,ohne dass kopiert werden muss. Doch es gibt zwei gewichtige Punkte, die gegen einen solchenAnsatz sprechen.2.4.1 Fehlende Unterstützung durch Windows NTVorab: Leser, die mit den internen Abläufen <strong>von</strong> Windows NT nicht oder nur wenig vertraut sind,wird empfohlen, sich zuerst mit Kapitel 3 auseinanderzusetzen oder allenfalls auf das Buch „InsideWindows NT“ <strong>von</strong> David A. Solomon [8] zurückzugreifen.In modernen Systemen wird der Empfang <strong>von</strong> Daten in der Regel durch einen (Hardware-)Interruptsignalisiert. Für Windows NT bedeutet dies, dass der Empfang <strong>von</strong> Daten auf dem Interrupt-LevelDIRQL oder nach dem Aufruf einer DpcForIsr-Routine auf DISPATCH_LEVEL behandelt wird. Dadie zentralen Funktionen des <strong>Memory</strong>-Managers ebenfalls auf DISPATCH_LEVEL laufen, ist dieAllokation <strong>von</strong> Speicher (im Paged-Pool) nur unterhalb dieses Levels möglich. Das heisst dann,dass Speicherallokation während der Interruptbehandlung nicht möglich ist.Der Speicher müsste also vorgängig <strong>und</strong> ohne das Wissen über die Grösse der zu empfangendenNachrichten auf PASSIVE_LEVEL vorgenommen werden. Da alle Threads (auch die im User-Mode) auf PASSIVE_LEVEL laufen, kommt dieses Vorgehen dem Top-Down-Verfahren gleich.Eine Möglichkeit, ein Bottom-Up-Verfahren unter Windows NT doch zu realisieren, wäre aufInterrupts zu verzichten <strong>und</strong> statt dessen zu pollen. Ein System-Thread (ein Thread also, derausschliesslich im Kernel-Mode existiert), könnte das Polling übernehmen. Dieser Thread arbeitetgarantiert immer unterhalb des DISPATCH_LEVEL. Daher sollte auch die Speicherallokation keinProblem darstellen. Genaueres zu System-Threads kann im Buch „The Windows NT Device DriverBook“ <strong>von</strong> Art Baker [9] in Kapitel 14 nachgelesen werden. Auf Gr<strong>und</strong> des Pollings <strong>und</strong> der nötigenSynchronisation des System-Threads mit den User-Threads ist es zu bezweifeln, dass auf diesemWege eine effiziente Implementation möglich ist.2.4.2 Fehlende Unterstützung durch höhere LayerKommunikationsprotokolle, die mit Kopiervorgängen behaftet sind, verwenden fast ausnahmslosdas Top-Down-Verfahren. Der Treiber empfängt die Daten in einen Puffer, welcher später in dendurch die Applikation zur Verfügung gestellten Speicherbereich kopiert wird. Aufbauend auf solcheKommunikationsprotokolle sind höhere Layer entstanden, die naturgemäss ebenfalls das Top-Down-Verfahren anwenden. MPI kann hier als klassisches Beispiel genannt werden. EinemMPI_Recv wird ein Pointer auf einen Applikationsspeicher <strong>und</strong> dessen Grösse mitgeteilt. Ist derSeite 14Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeEmpfang vollständig, dann erwartet die MPI-Applikation, dass die Daten in diesem Speicherbereichabgelegt sind.Für ein Bottom-Up-Verfahren müsste die Signatur der Empfangsfunktion geändert werden, dennder Pointer <strong>und</strong> die Länge sind nicht länger Input für die Funktion, sondern deren Output. Zudemist es nicht mehr Sache der Applikation, den Speicher zu allozieren, sondern die des Treibers.Solche Layer – insbesondere deren Schnittstellen – <strong>und</strong> alle darauf basierenden Applikationenmüssten umgeschrieben werden. Im Namen der Performance wäre dies zwar wünschenswert,doch werden diese Massnahmen wohl kaum auf Akzeptanz stossen.Ein Verändern der Schnittstellen-Signaturen muss also umgangen werden. Ein verführerischerAnsatz könnte der folgende sein: Eine Applikation alloziert Speicher für den Empfang <strong>von</strong> Daten<strong>und</strong> übergibt Pointer <strong>und</strong> Grösse des Speicherbereichs an den Treiber. Dieser dealloziert denSpeicher wieder <strong>und</strong> mapped statt dessen seinen Empfangsspeicher an die selbe Stelle. DasProblem dieser Variante ist jedoch, dass der Speicher seitenweise <strong>und</strong> nicht byteweise verwaltetwird. Man könnte zwar die Speicherallokation so gestalten, dass die Speicherbereiche immerpage-aligned sind. Doch niemand kann einer Applikation vorschreiben, die Daten auch an denAnfang des allozierten Speichers empfangen zu müssen. Empfängt der Treiber Daten nach demAufruf der Receive-Funktion, dann könnte er zwar auf die Verschiebung reagieren, indem die erste<strong>und</strong> letzte Speicherseite speziell behandelt werden. Im umgekehrten Fall (zuerst der Empfang,dann erst die Receive-Funktion) ist dies jedoch unmöglich. Mit diesem Ansatz könnten dieSignaturen zwar belassen werden, doch müssten genaue Richtlinien für die Speicherverwendungbeim Empfangen gelten.Eine befriedigende Lösung für dieses Problem scheint es nicht zu geben. Es muss hier jedochgesagt werden, dass nicht alle höheren Layer dieses Problem aufweisen. Ein DSM-System zumBeispiel könnte durchaus auf ein solches Bottom-Up-Verfahren aufgebaut werden, vorausgesetzt,der DSM-Layer kann bestimmen, an welche virtuelle Adresse eine empfangene Speicherseitegemapped werden soll.Damit ist die Zero-Copy-Kommunikation auf theoretischer Ebene charakterisiert. Wenn in denfolgenden Kapiteln <strong>von</strong> Kommunikation gesprochen wird, dann sind immer die in den Kapiteln 2.1<strong>und</strong> 2.2 beschriebenen Formen gemeint, es sei denn, es wird ausdrücklich auf eine andere Formverwiesen.Diplomarbeit <strong>von</strong> Roman Roth Seite 15


Institut für ComputersystemeETH ZürichSeite 16Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme3 Zero-Copy unter Windows NT 4.0Das im vorhergehenden Kapitel vorgestellte Kommunikationsmodell mit synchronisierten, grossen<strong>und</strong> unsynchronisierten, kleinen Paketen ist zwar ein wichtiges Konzept was Zero-Copy betrifft,doch es ist eigentlich nur die oberste Schicht. Alle Komponenten darunter, insbesondere Hardware<strong>und</strong> allfällige Treiber- <strong>und</strong> API-Software muss ebenso auf Zero-Copy-Kommunikation ausgerichtetsein. Das kann jedoch nur realisiert werden, wenn das Betriebssystem entsprechendeUnterstützung bietet. In diesem Kapitel wird deshalb versucht, den Kernel <strong>von</strong> Microsoft WindowsNT 4.0 etwas zu durchleuchten <strong>und</strong> auf Zero-Copy-Unterstützung hin zu prüfen.3.1 Die Windows NT 4.0 Kernel-ArchitekturEinführend wird hier eine Übersicht über dieKernel-Komponenten <strong>von</strong> Windows NT 4.0(Abbildung 3.1) aufgezeigt <strong>und</strong> derenFunktionen kurz erläutert:ŒŒŒŒŒŒŒŒŒHAL: Der Hardware-Abstraction-Layer isteine schmale Schicht direkt über derHardware. Sie soll eine abstrakte Sicht derHardware (insbesondere Ports, Interrupts<strong>und</strong> DMAs) gewährleisten, um möglichsthardwareunabhängigen Code schreiben zukönnen.I/O-ManagerVirtual<strong>Memory</strong>ManagerDriversExecutiveCacheManagerHardware Abstraction Layer (HAL)Abb. 3.1: Kernel-ArchitekturProcessThreadsKernelSecurityKernel: Der Kernel enthält allen plattformabhängigen Code. Windows NT ist heute auf dreiPlattformen lauffähig: Intel 486 <strong>und</strong> höhere Prozessoren, Compaq (vormals Digital) Alpha <strong>und</strong>MIPS. Plattformabhängig ist im wesentlichen alles, was prozessorabhängig ist. Dazu zählenTeile des Schedulers (Thread-Kontext) <strong>und</strong> des <strong>Memory</strong>-Managers (Translation-Lookaside-Buffer, Page-Faults, u.s.w.).Drivers: Windows NT kennt neben einem generischen Device-Driver-Modell eine Vielzahl <strong>von</strong>spezialisierten Treibermodellen für Grafikkarten, Netzwerkkarten, Mass-Storage-Controller,File-Systeme, Multimedia-Devices, Drucker usw. Die meisten dieser Modelle unterstützensogenannte Miniport-Treiber: Das heisst, Windows NT bietet ein Framework, das ein Grossteilder gängigen Funktionen übernimmt, so dass der Treiber nur noch die hardwarespezifischeFunktionalität implementieren muss.I/O-Manager: Der I/O-Manager organisiert <strong>und</strong> sequenzialisiert I/O-Anfragen aus dem User-Mode <strong>und</strong> Kernel-Mode. Da es pro Device nur eine Queue gibt, in der pendente Anfragenzwischengespeichert werden, <strong>und</strong> auch nur eine Anfrage zur gleichen Zeit bearbeitet werdenkann, unterstützt Windows NT <strong>von</strong> Haus aus nur halbduplex Input/Output. Für allfälligeFullduplex-Kommunikation müssen Treiber selber eine zweite Queue unterhalten.Virtual <strong>Memory</strong> Manager: Der VMM übernimmt die Abbildung des virtuellen Adressraums aufdie physisch vorhandenen Speicher (RAM, Disk).Cache Manager: Windows NT verfügt über einen bewusst allgemein gehaltenen Cache, dermit beliebigen File-System-Treiber <strong>und</strong> Network-Redirectors zusammenarbeiten kann <strong>und</strong> dieCachegrösse dynamisch durch den <strong>Memory</strong>-Manager zugeteilt bekommt.Process/Threads: Verwaltung <strong>von</strong> Process- <strong>und</strong> Threadinformationen, Scheduling, ContextSwitching.Security Manager: Jedes Objekt (wie z.B. Prozesse, Files, u.s.w.) hat ein Token, dasbeschreibt, zu welchem Sicherheitskontext es gehört. Nur autorisierte Benutzer, die in diesemKontext die entsprechenden Zugriffsberechtigungen besitzen, wird der Zugriff gewährt.Executive: Executive ist ein Pool <strong>von</strong> Funktionalität, die hardware- <strong>und</strong> plattformunabhängigist. Insbesondere enthält diese Komponente auch die (<strong>und</strong>okumentierte) Schnittstelle zwischenKernel-Mode <strong>und</strong> der NTDLL.DLL im User-Mode.Diplomarbeit <strong>von</strong> Roman Roth Seite 17


Institut für ComputersystemeETH Zürich3.2 Das Windows NT InterruptmodellDie Ausführung <strong>von</strong> User- <strong>und</strong> Kernel-Code ist stark abhängig vom Interruptmodell. Windows NT4.0 kennt verschiedene sogenannte Interrupt-Request-Levels (IRQL). Jedes Stück Code läuft aufeinem solchen Level. Ausgeführt wird immer gerade das Stück, das den höchsten Level hat <strong>und</strong>Ready-to-run ist. Software- <strong>und</strong> Hardwareinterrupts können asynchron neuen Code zur Ausführungbringen, der den Interrupt behandeln soll. Hat dieser Code (bzw. der auslösende Interrupt) einenhöheren Level, als der gerade ausgeführte, dann wird der aktuelle unverzüglich unterbrochen, <strong>und</strong>dem neuen Code die Kontrolle übergeben. Erst wenn alle Interruptbehandlungen mit höherenLevels beendet oder in den Wait-Zustand übergegangen sind, wird der unterbrochene Codeweitergeführt.Welche Interrupt-Request-Levels es gibt, <strong>und</strong> welche wichtigen Tasks auf diesen Levels laufen, istin Tabelle 3.1 zusammengefasst:HardwareSoftwareIRQLHIGHEST_LEVELPOWER_LEVELIPI_LEVELTasksRechner-Checks <strong>und</strong> BusfehlerPower-Fail InterruptCLOCK2_LEVEL Interval Clock 2CLOCK1_LEVELPROFILE_LEVELDIRQLDISPATCH_LEVELAPC_LEVELPASSIVE_LEVELInterprocessor Doorbell für MultiprozessorsystemeInterval Clock 1 (auf i386 Systemen nicht gebraucht)Profiling TimerPlattformabhängige Anzahl <strong>von</strong> Interrupt-Levels für I/O-DevicesThread-Scheduler, Page-Fault-Handling, Deferred-Procedure-Calls (DPC)Asynchronous-Procedure-Calls (APC)User-Mode- <strong>und</strong> Kernel-Mode-ThreadsTab. 3.1: Interrupt-Request-Levels (<strong>von</strong> oben nach unten nimmt die Priorität der Levels ab)Die Positionierung des Thread-Schedulers auf DISPATCH_LEVEL hat zur Konsequenz, dass alleInterruptbehandlungsroutinen, die auf dem selben oder höherem Level laufen, im Kontext desThreads (<strong>und</strong> Prozesses) ausgeführt werden, der zur Zeit aktiv ist. Zudem dürfen solche Routinenkeine Page-Faults verursachen, da diese erst behandelt würden, wenn die aktuelle Routinebeendet ist, was sofort zu einem Deadlock (bzw. Blue Screen) führen würde.3.3 Die Windows NT-TreibermodelleWie bereits oben angetönt, unterhält Windows NT neben einem generischen Modell für Device-Treiber eine beschränkte Anzahl <strong>von</strong> spezialisierten Treibermodellen. Speziell betrachtet werdensoll hier neben dem generischen nur das Modell für Netzwerktreiber, genannt NDIS.3.3.1 Das generische TreibermodellDas generische Treibermodell erlaubt das Schreiben <strong>von</strong> beliebigen I/O-Treibern, die das gesamteSet <strong>von</strong> Funktionen, welches Executive, Kernel <strong>und</strong> HAL zur Verfügung stellen, vollumfänglichgebrauchen können.Zentrale Funktion eines Treibers ist DriverEntry. Dies ist die einzige Funktion, die zwingenddiesen Namen tragen muss. Sie wird beim Laden des Treibers aufgerufen. Die Aufgaben dieserRoutine sind, dem System weitere Routinen für die Behandlung <strong>von</strong> User-Requests <strong>und</strong> Interruptsbekannt zu geben <strong>und</strong> die zu kontrollierenden Devices zu initialisieren <strong>und</strong> nach aussen bekanntzu machen.Für den User-Mode-Programmierer sind die Device-Treiber quasi ein Teil des Filesystems.Deshalb werden solche Treiber auch mit den selben Win32-Funktionen angesprochen. MitSeite 18Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeCreateFile(„\\\\.\\DeviceX“,..) wird ein Handler auf ein Device erzeugt. Dabei kann einTreiber synchron oder asynchron geöffnet werden, je nach dem, ob Requests blockierend odernicht-blockierend abgearbeitet werden sollen. Innerhalb des Treibers wird beim Öffnen eine zuvorbekanntgegebene Routine aufgerufen, die auf PASSIVE_LEVEL im Kontext des aufrufendenProzesses läuft <strong>und</strong> die nötigen Initialisierungen <strong>und</strong> Allokationen vornimmt.Mit ReadFile, WriteFile <strong>und</strong> DeviceIoControl können Requests an den Treiber geschicktwerden (Abbildung 3.2). Diese Requests werden vorerst vom I/O-Manager abgefangen. Dieser istdafür verantwortlich, dass die dem Treiber übergebenen Speicherbereiche entweder gelocked(Direct-I/O) oder in den Kernel-Mode kopieren (Buffered-I/O) werden. Danach wird die zugehörigeDispatch-Routine aufgerufen <strong>und</strong> ihr ein I/O Request Packet (IRP), das den Request beschreibt,mitgegeben.User-RequestHardware-InterruptSuccessI/O-ManagerIRPIRPIRPIRPDispatch-RoutineI/O-Start-RoutineInterruptServiceRoutineDpcForIsrPASSIVE_LEVEL DISPATCH_LEVEL DIRQL DISPATCH_LEVELAbb. 3.2: Ein möglicher Ablauf eines User-RequestsAnders als der Name vermuten liesse, läuft die Dispatch-Routine im Kontext des aufrufendenProzesses auf PASSIVE_LEVEL. Dies ist oft die letzte Gelegenheit, Speicher zu allozieren oder zulocken. Falls das für den Request benötigte Device gerade durch einen anderen Request belegtist, wird das IRP mit dem Status STATUS_PENDING dem I/O-Manager zurückgegeben, um es ineine Queue zu hängen, anderenfalls kann direkt die I/O-Start-Routine aufgerufen werden.Die I/O-Start-Routine ist sehr oft dafür verantwortlich, das Device für den Datentransfervorzubereiten <strong>und</strong> den Transfer zu starten. So kann zum Beispiel ein DMA-Transfer ausgelöstwerden. Diese Routine läuft bereits auf DISPATCH_LEVEL. Es kann also nicht mehr da<strong>von</strong>ausgegangen werden, dass man sich im Kontext des aufrufenden Prozesses befindet <strong>und</strong> vorallem dürfen keine Page-Faults ausgelöst werden. In vielen Fällen wird diese Routine beendet,ohne dass der Request vollständig abgearbeitet wurde. Der Gr<strong>und</strong> kann darin liegen, dass aufeinen Interrupt vom Device gewartet werden muss. Deshalb wird auch nach dieser Routine dasIRP mit Status STATUS_PENDING an den I/O-Manager zurückgegeben.Trifft der Interrupt ein, wird eine zuvor registrierte Interrupt-Service-Routine (ISR) aufgerufen. Diesehat zuerst zu klären, ob das <strong>von</strong> ihr kontrollierte Device den Interrupt ausgelöst hat oder nicht. Fallsja, ist es die wichtigste Aufgabe dieser Routine, den aktuellen Status des Device auszulesen <strong>und</strong>zwischenzuspeichern. Da die ISR auf dem Level DIRQL läuft, ist es sehr wichtig, dass nur diewichtigste Arbeit in dieser Routine vorgenommen wird, um andere Routinen nicht zu lange zublockieren. Deshalb wird häufig das IRP an den I/O-Manager zurückgegeben mit der Bitte, baldmöglichst eine DpcForIsr-Routine aufzurufen.Diese Deferred-Procedure-Calls (DPC) sind Routinen, die auf DISPATCH_LEVEL laufen, <strong>und</strong> soInterrupts anderer Devices nicht blockieren. Eine DPC-Routine sollte, wenn immer möglich, dieeigentliche Interruptbehandlung vornehmen. Falls der Request nun beendet ist, wird der IRP mitStatus STATUS_SUCCESS (oder allenfalls mit einem Fehlercode) dem I/O-Managerzurückgegeben. Falls nicht, kann auf dem Device ein neuer Transfer gestartet <strong>und</strong> wiederum aufeinen Interrupt gewartet werden, indem der IRP mit Status STATUS_PENDING zurückgegebenwird.Dieser Ablauf ist nicht für jeden Treiber zwingend. So muss es nicht unbedingt eine ISR oder eineDpcForIsr geben. Auch das Queuing <strong>von</strong> pendenten Requests muss nicht zwingend durch den I/O-Manager erfolgen. Fullduplex-Treiber – Treiber also, die gleichzeitig Senden <strong>und</strong> EmpfangenDiplomarbeit <strong>von</strong> Roman Roth Seite 19


Institut für ComputersystemeETH Zürichkönnen – müssen eine zweite Queue implementieren, da der I/O-Manager pro Device nur einenaktiven Request verwaltet. Alle anderen Requests werden nach der Dispatch-Routine solangegequeued, bis der aktive einen Status ungleich STATUS_PENDING zurückgeliefert hat.Ein Win32 CloseHandle schliesst den Handle auf das Device <strong>und</strong> löst im Treiber eine Close-Routine aus. Zusätzlich kann der Treiber je eine Routine enthalten, die das Entladen des Treibersermöglicht oder beim Shutdown die Devices in einen Gr<strong>und</strong>zustand zurücksetzt.3.3.2 Das NDIS-ModellNachdem nun das generische Modell vorgestellt wurde, ist die Frage gerechtfertigt, wieso es nochzusätzliche, spezielle Treibermodelle braucht. Welche Vorteile können solche Modelle bringen?ŒŒŒŒSpezielle Modelle können optimiert sein <strong>und</strong> so eine höhere Leistungsfähigkeit aufweisen.Das System kann dem Treiber ein Framework zur Verfügung stellen, das Routinen enthält, diejeder Treiber dieser Klasse implementieren müsste. So kann sich der Treiberprogrammiererauf die hardwareabhängigen Teile konzentrieren.Ein solches Framework kann die Plattformunabhängigkeit steigern.Es kann eventuell sogar betriebssystemneutraler Source geschrieben werden.Kurz: Performance <strong>und</strong> Portabilität können gesteigert <strong>und</strong> der Programmieraufwand gesenktwerden. Ein aus kommerzieller Sicht durchaus sinnvolles Konzept also.An dieser Stelle soll nur auf das NDIS-Modell (Network-Driver-Interface-Specification) eingegangen werden.NDIS bietet ein oben erwähntesFramework, in welches verschiedeneTreiber eingebettet werden können(vgl. Abbildung 3.3). Den Treibern wirdeine Vielzahl <strong>von</strong> Funktionen zurVerfügung gestellt. Die Treiberexistieren quasi in einer Sandbox. Esist NDIS-Treibern nicht gestattet, aufandere als auf NDIS-Funktionenzuzugreifen. Dadurch wird einePortabilität erreicht, durch die nicht nurPlattformunabhängigkeit möglich ist,sondern theoretisch auch der Einsatzunter anderen Windows-Systemen wieWindows 95/98 möglich sein sollte.NetBios-EmulatorNetBios-EmulatorUser-Mode-ClientSocket-EmulatorSocket-EmulatorNetwork Interface Card (NIC)DerLAN-Protocol-Treiberimplementiert das Transport-Driver-Abb. 3.3: NDIS-Treiber-ModellInterface (TDI). Dieser Treiber istverantwortlich für die Umwandlung des Datenstroms einer Applikation in Pakete <strong>und</strong> umgekehrt.Unterstützt werden jedoch nur gängige Paketformate wie sie beispielsweise Ethernet, Token-Ringoder FDDI verwenden.NDIS-Miniport-Treiber übernehmen alle hardwareabhängigen Funktionalitäten <strong>und</strong> bieten nachoben ein klar definiertes Interface an. Sie sind verantwortlich für das Senden <strong>und</strong> Empfangen <strong>von</strong>Datenpaketen. Das Format dieser Pakete ist im Prinzip beliebig.Falls der Miniporttreiber keines der oben erwähnten Standardpaketformate verwendet, kann einNDIS-Intermediate-Treiber dazwischen geschaltet werden, um das proprietäre Format in einunterstütztes umzusetzen. Dabei verhält sich der Intermediate-Treiber nach oben wie einMiniporttreiber.Welche zentralen Aufgaben übernimmt nun aber das NDIS-Framework eigentlich?NDIS InterfaceLAN ProtocolsLAN Media TypeNDIS IntermediateNative Media TypeNDIS MiniportUser-ModeKernel-ModeTransport DriverInterface (TDI)Seite 20Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeŒŒAlle notwendige Synchronisation <strong>und</strong> Sequenzialisierung (insbesondere auf Maschinen mitmehreren Prozessoren) wird durch NDIS vorgenommen. Die Treiber brauchen sich nichtdarum zu kümmern.NDIS übernimmt das Binding zwischen den Treibern der verschiedenen Schichten, sorgt alsodafür, dass ein Paket den richtigen Weg durch die Treiber findet.Œ NDIS ermöglicht Full-Duplex-Kommunikation (was ja, wie oben erwähnt, durch den I/O-Manager nicht direkt unterstützt wird).Œ NDIS übernimmt das Loopback bei Kommunikation zwischen Prozessen auf der selbenMaschine.ŒNDIS bietet den Treibern eine vollständige Funktionsbibliothek an, so dass sie unabhängig <strong>von</strong>Kernel, Executive <strong>und</strong> HAL entwickelt werden können, <strong>und</strong> deshalb portabel sind.3.3.3 Beurteilung aus der Sicht <strong>von</strong> Zero-CopyEs überrascht natürlich nicht, dass das generische Modell gr<strong>und</strong>sätzlich für die Implementationeines Treiber für die Unterstützung <strong>von</strong> Zero-Copy-Kommunikation geeignet ist, da es demProgrammierer die grösst möglichen Freiheiten lässt. Das Modell an sich ist also durchausverwendbar.NDIS jedoch kann für Zero-Copy-Kommunikation, trotz seiner Vorteile, nicht verwendet werden.Der Gr<strong>und</strong> liegt darin, dass mehrere Protokolle verwendet werden. Der Datenstrom der Applikationwird im LAN-Protocol-Layer in Pakete aufgeteilt, mit zusätzlicher Protokollinformation versehen <strong>und</strong>an den nächst tieferen Treiber weitergeleitet. Dieses Vorgehen ist zwingend mit Kopierarbeitverb<strong>und</strong>en, was der Zero-Copy-Anforderung widerspricht.3.4 Das Speichermanagement <strong>von</strong> Windows NT 4.0Ein kurzer Abriss des Speichermanagements <strong>von</strong> Windows NT 4.0 soll helfen, die nachfolgendenAusführungen zur Zero-Copy-Unterstützung besser zu verstehen. Eine ausführliche Beschreibungist Kapitel 5 des Buchs „Inside Windows NT“ <strong>von</strong> David A. Solomon [8] zu entnehmen.3.4.1 Datenstrukturen <strong>und</strong> System-ThreadsDer virtuell adressierbare Speicherraum ist unter Windows NT(Workstation) in zwei je 2 GB grosse Teile unterteilt (siehe Abb.3.4). Der untere Teil, der User-Space, steht jedem Prozess zurVerfügung. Der obere Teil, der Kernel-Space, ist demBetriebssystem vorbehalten. Wichtiges Detail am Rande: Auchder Kernel arbeitet mit virtuellen Adressen.Der Kernel-Space enthält neben dem Kernel-Code(NTOSKRNL.EXE, HAL.DLL, Device-Treiber,...), den Page-Tables(siehe unten) <strong>und</strong> dem Cache zwei wichtige Regionen, diePaged-Pool bzw. Nonpaged-Pool genannt werden. Bei beidenhandelt es sich um Kernel-Mode-Heaps, die, wie die Namenbereits andeuten, auslagerbar (paged) bzw. fest imphysikalischen Speicher verankert (nonpaged) sind. Wichtig istder Nonpaged-Pool vor allem für Routinen, die aufDISPATCH_LEVEL oder höher laufen, da dies der einzigeSpeicher ist, der garantiert zugreifbar ist, ohne dass er explizitgelockt werden müsste. Auf der anderen Seite sollte dieserSpeicher sehr zurückhaltend verwendet werden, um dem Restdes System nicht zu viel physischer Speicher zu entziehen.Cache,Paged pool,Nonpaged poolPage TablesKernel, Executive,HAL, Drivers2 GB per processuser spaceFFFFFFFFC0800000C0000000800000007FFFFFFF00000000Abb. 3.4: Virtueller AdressraumDie Abbildung <strong>von</strong> virtuellen auf physikalische Adressen übernimmt der Virtual-<strong>Memory</strong>-Manager(VMM). Dieser nutzt eine Reihe <strong>von</strong> Datenstrukturen (wie in der Übersicht in Abbildung 3.5dargestellt), um den Zustand des virtuellen <strong>und</strong> physischen Speichers festzuhalten.Diplomarbeit <strong>von</strong> Roman Roth Seite 21


Institut für ComputersystemeETH ZürichVirtual Address 10 bit 10 bit 12 bitPage framePage framePage DirectoryPage tablesPhysical <strong>Memory</strong>Page Frame DatabaseVADZeroed ListVADVADFree Listper processVADVADVirtual Address DescriptorsPrototypePage TableStandby ListModified ListAbb. 3.5: Datenstrukturen des VMMDer VMM <strong>von</strong> Windows NT benutzt eine zweistufige Page-Table für die Auflösung <strong>von</strong> virtuellenAdressen in ihre physikalischen. Die obersten 10 Bits der virtuellen Adresse werden als Index imsogenannten Page-Directory benützt. Das Page-Directory enthält 1024 Einträge. Jeder <strong>von</strong> diesenEinträgen verweist auf eine Speicherseite, die die zugehörige Page-Table enthält. Page-Tableswerden jedoch nur dann alloziert, wenn sie auch wirklich Einträge enthalten.Die zweiten 10 Bits werden als Index in dieser Page-Table verwendet. Der Eintrag verweist imNormalfall auf einen Page-Frame im physikalischen Speicher. Im Falle <strong>von</strong> <strong>Shared</strong>-<strong>Memory</strong> zeigendie Einträge der Page-Tables aller beteiligten Prozesse nicht direkt auf den Page-Frame, sondernin eine Prototype-Page-Table, die dann ihrerseits auf den Page-Frame verweist. Diese zusätzlicheIndirektion hat den Vorteil, dass Zustandsinformationen nur an einem Ort verändert werdenmüssen.Die restlichen 12 Bits stellen den Offset innerhalb des 4 kB grossen Page-Frames dar.Ein Baum <strong>von</strong> Virtual-Address-Descriptors (VAD) hält für jeden Prozess fest, welche virtuellenAdressbereiche bereits vergeben sind. Windows NT unterstützt ein zweistufiges Allozieren <strong>von</strong>Speicher. Speicherbereiche können reserviert werden. Das heisst, der entsprechende virtuelleAdressbereich gilt als gebraucht, er wird jedoch noch nicht mit physischem Speicher hinterlegt <strong>und</strong>kann dementsprechend auch noch nicht zugegriffen werden. Mit einem Commit kann demreservierten Speicherbereich, oder auch nur einem oder mehreren beliebigen Teilen da<strong>von</strong>,physikalischer Speicher (ein beliebiges File oder das Page-File <strong>und</strong> nach einem Page-Fault auchRAM) zugeordnet werden. Dieses Verfahren erlaubt es, grosse, virtuell zusammenhängendeSpeicherbereiche zu reservieren <strong>und</strong> nach Bedarf mit Speicher zu hinterlegen.Für die Verwaltung der physikalischen Speicherseiten (Page-Frames genannt) gibt es die Page-Frame-Database, die für jede Speicherseite deren Zustand festhält. Die Einträge dieser Datenbankkönnen durch Pointer verkettet sein, falls sie zu einer speziellen Liste <strong>von</strong> Seiten gehören. Es gibtvier solche Listen, die nennenswert sind:ŒStandby-List: Windows NT bemüht sich, das Working-Set eines jeden Prozesses klein zuhalten. Prozesse, die keine Page-Faults produzieren, sind Kandidaten, um Speicherseiten zurequirieren. Wenn einem Prozess eine unveränderte Seite weggenommen wird, bleibt dieseeine Zeit in der Standby-List, <strong>von</strong> wo sie ohne grossen Aufwand dem Prozess zurückgegebenwerden könnte. Scheint der Prozess an der Seite nicht mehr interessiert zu sein, wird sie nacheiniger Zeit in die Free-List eingefügt.Seite 22Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeŒŒŒModified-List: Diese Liste enthält, wie die Standby-List, Seiten, die einem Prozessweggenommen wurden. Diese Seiten wurden jedoch verändert <strong>und</strong> müssen deshalb vomModified-Page-Writer-Thread ins Page-File geschrieben werden.Free-List: Seiten, die aus den obigen beiden Listen entfernt wurden, werden in die Free-Listgesetzt.Zeroed-List: An Prozesse im User-Mode werden aus Sicherheitsgründen (C2-Zertifizierung)nur Seiten abgegeben, die mit Nullen initialisiert wurden. Diese Liste enthält solchevorbereiteten Seiten.Neben diesen Datenstrukturen gibt es eine ganze Reihe <strong>von</strong> System-Threads, die für dasManagement zuständig sind:ŒŒBalance-Set-Manager: Dieser Thread ist zuständig für die Trimmung der Working-Sets derThreads.Swapper: Die Working-Sets <strong>von</strong> Threads, die längere Zeit im Wait-Zustand verbracht haben,werden ins Page-File ausgelagert bzw. beim Reaktivieren des Threads wieder eingelesen.Œ Modified-Page-Writer / Mapped-Page-Writer: Diese beiden Threads schreiben dieveränderten Speicherseiten der Modified-List ins Page-File bzw. ins entsprechende Mapped-File.ŒZero-Page-Thread: Wenn die Seiten in der Zeroed-List knapp werden, wird der Zero-Page-Thread Seiten aus der Free-List entfernen, diese mit Nullen initialisieren <strong>und</strong> in die Zeroed-Listeinfügen.Die Komplexität der Datenstrukturen, sowie die Vielzahl <strong>von</strong> Threads, die auf diesen Strukturenoperieren <strong>und</strong> dementsprechend auch synchronisiert werden müssen, verbietet es quasi <strong>von</strong>selbst, diese Strukturen dem Programmierer <strong>von</strong> Device-Treibern offenzulegen. Vielmehr wird eineArt Kernel-Programming-Interface zur Verfügung gestellt, das unter anderem Operationen auf derSpeicherverwaltung erlaubt. An dieser Stelle wird darauf jedoch nicht im Detail eingegangen. DerLeser wird dazu auf das Microsoft Windows NT 4.0 DDK [13] verwiesen.3.4.2 Beurteilung aus der Sicht <strong>von</strong> Zero-CopyNach dieser Einführung in den <strong>Memory</strong>-Manager <strong>von</strong> Windows NT wird der Fokus wieder mehr aufZero-Copy gelegt. Wie schon erwähnt, gibt es zwei wichtige, hardwareunterstützte Techniken imZusammenhang mit Zero-Copy: <strong>Memory</strong>-Mapping <strong>und</strong> Direct-<strong>Memory</strong>-Access. Was diese beidenTechniken so auszeichnet, ist die Möglichkeit, dass Speicherinhalt ohne Kopieren, sprich ohneCPU-Einsatz, verschoben werden kann – Zero-Copy eben. Beide Techniken werden <strong>von</strong> WindowsNT unterstützt.3.4.2.1 Mapping <strong>von</strong> Device-<strong>Memory</strong>Netzwerkkarten (wie zum Beispiel die Myrinet-Karten <strong>von</strong> Myricom [16]) können über eineneigenen Speicher verfügen. Es kann durchaus interessant sein, diesen Speicher direkt in denvirtuellen Adressraum des entsprechenden Prozesses zu mappen.Diese Möglichkeit gibt es unter Windows NT tatsächlich. Beim Laden des Treibers wird der Device-Speicher vorerst in den Nonpaged-Pool des Kernel-Space eingeblendet. Die DriverEntry-Routine müsste folgende Funktionen enthalten:ŒŒŒIoQueryDeviceConfigurationData: Mit dieser Funktion werden Informationen zu einerKarte in einem bestimmten Slot auf einem bestimmten Bus ermittelt, insbesondere diebusspezifische Adresse des Device-Speichers.HalTranslateBusAddress: Übersetzt die busspezifische Adresse in eine systemweite,logische Adresse.MmMapIoSpace: Die Funktion mapped den Device-Speicher in den virtuellen Adressraum desKernels, genauer gesagt in den Nonpaged-Pool.Diplomarbeit <strong>von</strong> Roman Roth Seite 23


Institut für ComputersystemeETH ZürichBeim Öffnen eines Device durch einen Prozess (mit CreateFile) oder durch eine Kontrollroutine(mit DeviceIoControl) könnte der gesamte oder ein Teil des Device-Speichers in denAdressraum eines Prozesses gemapped werden:Œ IoAllocateMdl: DieseFunktion alloziert eineStart Virtual Addresssogenannte <strong>Memory</strong>-Byte OffsetDescriptor-List (MDL). MDLsByte Countsind Strukturen, deren Inhaltzwar <strong>und</strong>okumentiert ist, dieaber über spezielle FunktionenSizeProcesszugreifbar sind. Eine MDLMapped System VAenthält die zu einem virtuellenPhysical Address 1Adressbereich gehörenden…physikalischen Seitennummern.MitPhysical Address NVirtual SpaceIoAllocateMdl wird der<strong>Memory</strong> Descriptor ListMDL die virtuelle Kernel-Space-Adressedes Abb. 3.6: <strong>Memory</strong>-Descriptor-List [9]gemappten Device-Speichers<strong>und</strong> dessen Grösse mitgegeben.ŒŒPhysical <strong>Memory</strong>MmBuildMdlForNonPagedPool: Mit dieser Funktion werden der MDL die physikalischenAdressen hinzugefügt, die den gewünschten virtuellen Adressbereich aus dem Nonpaged-Poolenthalten.MmMapLockedPages: Schliesslich können mit dieser Funktion die durch die MDLbeschriebenen physikalischen Speicherseiten in den virtuellen Speicherbereich des Prozesseseingeblendet werden. Die Funktion liefert eine virtuelle Adresse zurück.Es ist darauf zu achten, dass diese Funktionen nur bei IRQL < DISPATCH_LEVEL aufgerufenwerden dürfen, weil sie im Kontext des aufrufenden Prozesses abgearbeitet werden müssen!Es ist zwar nicht dokumentiert, wie das Mapping <strong>von</strong> MmMapLockedPages funktioniert, doch kannda<strong>von</strong> ausgegangen werden, dass die bei <strong>Shared</strong>-<strong>Memory</strong> ebenfalls verwendeten Prototype-Page-Tables zum Einsatz kommen.Der umgekehrte Weg, das heisst das Unmapping, ist mit den Funktionen MmUnmapLockedPages,IoFreeMdl <strong>und</strong> MmUnmapIoSpace zu begehen.3.4.2.2 Direct-<strong>Memory</strong>-Access (DMA)Direct-<strong>Memory</strong>-Access erlaubt es, ohne CPU-Belastung Daten zwischen dem Speicher <strong>und</strong> einemDevice zu verschieben. Theoretisch wären auch das Verschieben <strong>von</strong> Device zu Device möglich,Windows NT unterstützt diese Möglichkeit jedoch nicht.Der Hardware-Abstraction-Layer <strong>von</strong> Windows NT abstrahiert die DMA-Controller in sogenanntenAdapter-Objects, die mit dem Aufruf der Funktion HalGetAdapter durch einen Treiber lokalisiertwerden können. Will ein Treiber einen DMA-Kanal benutzen, dann muss er mitIoAllocateAdapterChannel diesen temporär für sich reservieren lassen.Die bereits oben erwähnten <strong>Memory</strong>-Descriptor-Lists (MDL) spielen auch beim DMA eine zentraleRolle, da DMA-Controller nicht mit virtuellen Adressen programmiert werden können. Es ist alsowichtig, dass man vom gewünschten virtuellen Speicherbereich eine MDL hat, die die Verweise aufdie entsprechenden physikalischen Seiten enthält. Um <strong>von</strong> einem Speicher im Kernel-Space eineMDL zu bekommen kann MmCreateMdl oder MmBuildMdlForNonPagedPool verwendetwerden. User-Space durch eine MDL abzubilden, ist etwas komplizierter, da der Aufbau der MDLim Kontext des aufrufenden Prozesses geschehen muss. Zudem ist es wichtig, dass derverwendete Speicher gelockt <strong>und</strong> allenfalls in den System-Space gemapped wird. Handelt es sichum einen Direct-I/O-Treiber, dann wird der I/O-Manager den vom User übergebenenSpeicherbereich automatisch locken <strong>und</strong> eine entsprechende MDL generieren. Möchte mananderen Speicher verwenden, dann muss zuerst mit IoAllocateMdl eine MDL alloziert <strong>und</strong> derSpeicher mit MmProbeAndLockPages gelocked werden. Mit MmGetSystemAddressForMdlSeite 24Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersystemekann der Speicher, falls gewünscht, in den Kernel-Space gemapped werden, wo er unabhängigvom aktuellen Thread-Kontext zugreifbar ist.Ein wesentliches Problem beim DMA-Transfer ist die Tatsache, dass zusammenhängendervirtueller Speicher nicht unbedingt auch zusammenhängender physischer Speicher sein muss. Diemeisten DMA-Controller erlauben lediglich die Programmierung einer physischen Startadresse <strong>und</strong>einer Länge, manche Controller können mehrere solche Paare aufnehmen. Bei sehr grossenSpeicherbereichen wird es jedoch wegen Fragmentierung unweigerlich mehrere DMA-Transfersbrauchen. Es wäre deshalb nützlich, gezielt physisch zusammenhängender Speicher allozieren zukönnen. Windows NT erlaubt durch die Funktion MmAllocateContiguous<strong>Memory</strong> die Allokation<strong>von</strong> zusammenhängendem Speicher im Nonpaged-Pool des Kernel-Space. Je grösser dergewünschte Speicherbereich ist <strong>und</strong> je später die Funktion aufgerufen wird, destounwahrscheinlicher wird es, dass das System dem Wunsch entsprechen kann. Deshalb wirdempfohlen, diese Funktion bereits in der DriverEntry-Routine aufzurufen.Ein wichtiges Detail sind die Caches. Da ein DMA-Transfer ohne Kontrolle des CPUs abläuft,müssen Cache-Einträge, die zum betroffenen, physischen Speicher gehören, vor dem Transfergeleert werden, um sicher zu gehen, dass der Speicher die neusten Daten enthält.KeFlushIoBuffers übernimmt diese Funktion.Beim eigentlichen Transfer spielt die Funktion IoMapTransfer eine zentrale Rolle. DieseFunktion bestimmt aus der übergebenen MDL einen physisch zusammenhängendenSpeicherbereich <strong>und</strong> ermittelt die Adresse <strong>und</strong> die Länge des Bereichs. Handelt es sich beim DMA-Controller um einen Slave-DMA-Controller (auf dem Motherboard), dann programmiertIoMapTransfer diesen Controller automatisch <strong>und</strong> startet den Transfer. Ist der DMA-Contollerjedoch ein Master-DMA-Controller (auf einem Device), dann gibt IoMapTansfer die Adresse <strong>und</strong>Länge zurück, <strong>und</strong> der Treiber muss den Controller selber programmieren. Danach stellt derTreiber seine Arbeit ein <strong>und</strong> wartet auf den Interrupt, der das Ende des Transfers signalisiert. Beifragmentiertem, physischem Speicher muss IoMapTransfer natürlich mehrmals aufgerufenwerden.Am Ende des oder der Transfers muss mit IoFreeAdapterChannel der DMA-Kanal wiederfreigegeben werden.Ausführliche Informationen über DMA unter Windows NT 4.0 können im „The Windows NT DeviceDriver Book“ <strong>von</strong> Art Baker [9] Kapitel 12 „DMA Drivers“ nachgelesen werden.Diplomarbeit <strong>von</strong> Roman Roth Seite 25


Institut für ComputersystemeETH ZürichSeite 26Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme4 Die Schnittstellen zum <strong>Message</strong>-<strong>Passing</strong>- <strong>und</strong> DSM-LayerAuf die Zero-Copy-Kommunikation sollen zweiunterschiedliche Modelle der parallelenProgrammierung aufbauen: <strong>Message</strong>-<strong>Passing</strong><strong>und</strong> <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong> (DSM). Derenoberen Schnittstellen sind durch gängigeDefinitionen festgelegt: Für <strong>Message</strong>-<strong>Passing</strong>wird MPI [7], für <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong>OpenMP [2] angenommen.Zwei so unterschiedliche Modelle sollen aufeinem Kommunikationslayer aufbauen. Dieses Abb. 4.1: Die inneren SchnittstellenKapitel klärt ab, wie die Schnittstellen zwischenden beiden Modellen <strong>und</strong> einem Zero-Copy-Layer (Abbildung 4.1) auszusehen haben, welcheAnforderungen gestellt werden, sowie welche Übereinstimmungen bzw. Differenzen zwischen denSchnittstellen bestehen.Gr<strong>und</strong>sätzlich muss jedoch <strong>von</strong> den beiden Zero-Copy-Kommunikationsarten bzw. derenSchnittstellen ausgegangen werden. Auf Gr<strong>und</strong> der folgenden Analyse kann es jedoch sein, dassdiese Schnittstellen etwas erweitert werden müssen.MPIOpenMP<strong>Message</strong> <strong>Passing</strong><strong>Distributed</strong><strong>Shared</strong> <strong>Memory</strong>€ óZero-Copy-CommunicationNetwork4.1 Die Schnittstelle zwischen <strong>Message</strong>-<strong>Passing</strong>- <strong>und</strong> Zero-Copy-LayerDie zu betrachtende Schnittstelle definiert sich einerseits durch die Zero-Copy-Aspekte,andererseits durch die MPI-Funktionalitäten, die der <strong>Message</strong>-<strong>Passing</strong>-Layer nicht implementierenkann. Die folgende Betrachtung beschränkt sich auf die Kommunikationsaspekte des MPI-Paketes.Dabei können im wesentlichen drei Typen unterschieden werden:ŒŒŒPunkt-zu-Punkt-Kommunikation: MPI bietet Senden <strong>und</strong> Empfangen in vielen Varianten an:blockierend oder nicht-blockierend, gepuffert oder synchronisiert.Kollektive Kommunikation (Punkt-zu-mehr-Punkt, mehr-Punkt-zu-mehr-Punkt): Neben demBroadcast sind diverse Varianten <strong>von</strong> Scatter, Gather <strong>und</strong> AllToAll vorhanden.Synchronisation: Die Barrier als einziges Synchronisationsobjekt ist ein Spezialfall derkollektiven Kommunikation.4.1.1 Punkt-zu-Punkt-KommunikationDie Punkt-zu-Punkt-Kommunikation ist die einfachste Kommunikationsart. Paarweise treten je eineSende- <strong>und</strong> eine Empfangsfunktion folgender Art auf:MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm);MPI_Recv(void* buf, int count, MPI_Datatype datatype, int dest, int tag,MPI_Comm comm, MPI_Status *status);Dieses Funktionspaar gibt es blockierend <strong>und</strong> nicht-blockierend. Zudem werden verschiedeneModi unterstützt: Buffered ist ein unsynchronisierter Modus. Die Meldung wirdzwischengespeichert, falls der Empfänger nicht empfangsbereit ist. Der Synchronous-Modussynchronisiert Sender <strong>und</strong> Empfänger bevor gesendet wird. Im Ready-Modus ist Senden nur dannerfolgreich, wenn der Empfänger bereit ist.Das Vorhandensein <strong>von</strong> nicht-blockierenden MPI-Funktionen zieht automatisch nach sich, dassauch die Funktionen der betrachteten Schnittstelle nicht-blockierend sein müssen (es sei denn, dieKommunikation wird in zusätzlichen Threads untergebracht). Blockierende MPI-Funktionen könnenohne weiteres auf die nicht-blockierenden Funktionen <strong>und</strong> MPI_Wait aufgebaut werden.MPI kennt eine Thread-zu-Thread-Kommunikation (Abbildung 4.2). Jeder MPI-Thread wird miteinem Rank, einer eindeutigen Nummer, identifiziert. Untere Schichten der Kommunikation werdenmit diesem Rank kaum etwas anfangen können. Deshalb muss innerhalb des <strong>Message</strong>-<strong>Passing</strong>-Layers eine Abbildung vorgenommen werden. An der Schnittstelle wird also nicht ein Rank,sondern eine <strong>von</strong> den unteren Schichten verstandene Adressform übergeben. Im folgendenDiplomarbeit <strong>von</strong> Roman Roth Seite 27


Institut für ComputersystemeETH Zürichwerden diese Adressen mit SendID <strong>und</strong>RecvID bezeichnet. Erlauben die unterenSchichten nur eine Knoten-zu-Knoten-Kommunikation (wie z.B. GM), dann kann<strong>von</strong> diesen bestenfalls der Knoten desAbsenders identifiziert werden. Ohnezusätzliche Information im Datenpaketkann weder der exakte Absender-Thread,noch der gewünschte Empfänger-Threadermittelt werden.Thread-zu-ThreadKnoten-zu-KnotenAbb. 4.2: Zwei unterschiedliche Kommunikationsmuster4.1.1.1 Der Buffered-Modus (MPI_Bsend)Dieser Modus widerspricht der Zero-Copy-Kommunikation eigentlich <strong>von</strong> Gr<strong>und</strong> auf, denn gepuffertsoll ja nicht mehr werden, da dies in der Regel Kopiervorgänge nach sich zieht.Gr<strong>und</strong>sätzlich kann dieser Modus mittels der unsynchronisierten Kommunikationsart des Zero-Copy-Layers implementiert werden. Die beiden Nachteile dieser Kommunikationsart müssenjedoch uneingeschränkt an die MPI-Schnittstelle weitergegeben werden:ŒŒUnsynchronisierte Datenpakete unterliegen einer maximalen Grösse. Diese Beschränkungmuss daher auch für MPI-Meldungen, die im Buffered-Modus verschickt werden, gelten.Der Zero-Copy-Layer ist zuständig für die Empfangspuffer. Es wird also nicht direkt in denSpeicherbereich der MPI-Applikation empfangen. Daher ist Kopieren beim Empfängerunumgänglich.Auch auf Senderseite ist Kopieren notwendig, da die eigentliche Meldung <strong>und</strong> das Tag nichtzwingend in einem zusammenhängenden Speicherbereich plaziert sein müssen. Bei Knoten-zu-Knoten-Kommunikation muss dem Datenpaket zudem eine Sender- <strong>und</strong> Empfängeridentifikationmitgegeben werden. Nach dem Kopieren könnte eine Meldung folgendermassen aussehen:SendID RecvID Tag Data 0 ... Data Len-1Würde über klassische Protokolle kommuniziert, dann müsste diese Meldung im Header noch dieLänge des nachfolgenden Datenblocks (Len) enthalten. Bei Zero-Copy-Kommunikation ist dasnicht notwendig. Jede Meldung muss ja in einen separaten Empfangspuffer abgelegt werden. Dasheisst also, dass die unteren Schichten verantwortlich sind, die Länge des Paketes zu verwalten.Diese Länge wird beim Recv den oberen Schichten mitgeteilt. Aus dieser Länge minus der Grössedes Headers errechnet sich Len.Die Schnittstelle müsste ungefähr folgende Funktionen enthalten. Diese decken sich vollständig mitden Funktionen des unsynchronisierten Sendens, wie es in Kapitel 2 vorgestellt wurde:SendAsync(int nRecvID, void* pBuffer, int nLen);RecvAsync(int& nSendID, void*& pBuffer, int& nLen);Von Zero-Copy kann bei diesem Modus nicht mehr gesprochen werden. Doch da die Meldungensowieso grössenmässig beschränkt sind, ist der Performanceverlust durch das Kopieren nicht allzu tragisch.4.1.1.2 Der Synchronous-Modus (MPI_Ssend)Dieser Modus entspricht vollständig der synchronisierten Kommunikationsart des Zero-Copy-Layers: Es wird erst dann gesendet, wenn der Empfänger bereit ist. Die Synchronisation wird durchden Zero-Copy-Layer übernommen. Der Empfangsbereitschaftsmeldung müssen jedoch ein paarDaten mitgegeben werden: Neben der virtuellen Adresse des Empfangspuffers (<strong>und</strong>sicherheitshalber dessen Grösse) muss auch das Tag <strong>und</strong> bei Knoten-zu-Knoten-Kommunikationeine Identifikation des Senders <strong>und</strong> Empfängers enthalten sein. DieEmpfangsbereitschaftsmeldung hat also folgenden Aufbau:Seite 28Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeAdresse des EmpfangspufferGrösse des EmpfangspufferTagSendIDRecvIDDiese Daten müssen der Empfangsfunktion mitgegeben werden. Auch die Sendefunktion verlangtdas Tag <strong>und</strong> eventuell die Sender- <strong>und</strong> Empfängeridentifikation. Im Zero-Copy-Layer können sodie Informationen der Sendefunktion mit denen der Empfangsfunktion verglichen werden. Stimmenalle Daten überein, dann ist die Empfangsbereitschaft für eine bestimmte Meldung bestätigt, <strong>und</strong>es kann gesendet werden.Die Funktionen der Schnittstelle (der erste Parameter ist nur bei Knoten-zu-Knoten-Kommunikationnotwendig):SendSync(int nSendID, int nRecvID, int nTag, void* pBuffer, int nLen);RecvSync(int nRecvID, int nSendID, int nTag, void* pBuffer, int& nLen);Damit ist der eigentliche Transport der Meldung realisierbar, <strong>und</strong> in den meisten Fällen würde diesauch genügen. Nur wenn die Empfangsfunktion mit den Wildcards MPI_ANY_SOURCE oderMPI_ANY_TAG verwendet wird, gibt esein Problem: Welchem Sender solleine Empfangsbestätigung gesendetwerden? Und welches Tag hat nun dieempfangene Meldung? Gelöst wirddieses Problem, indem der <strong>Message</strong>-<strong>Passing</strong>-Layer des Senders einekleine Meldung an denentsprechenden Empfänger-Layerschickt, die seine Sendeabsichten <strong>und</strong>das Tag enthalten (Abbildung 4.3).Nun kennt der Empfänger denAbsender <strong>und</strong> das Tag <strong>und</strong> kann seineEmpfangsbereitschaft mitteilen.4.1.1.3 Der Ready-Modus (MPI_Rsend)Der Ready-Modus ist wie MPI_Ssend eine synchronisierte Variante. Die Bereitschaft desEmpfängers muss jedoch bereits bestätigt sein, bevor die Sendefunktion aufgerufen wird, sonst istder Sendevorgang fehlerhaft. Das heisst dann aber auch, dass der Empfänger nicht mit Wildcardsarbeiten kann. Deshalb muss auch keine Sendeabsicht an den Empfänger geschickt werden.Das bedeutet für den Zero-Copy-Layer, dass es zwei Varianten der synchronisiertenKommunikation gibt: Eine, die das Senden verzögert, bis die Empfangsbereitschaft signalisiertwird, <strong>und</strong> eine, die nur sendet, wenn die Bereitschaft bereits signalisiert ist <strong>und</strong> sonst mit einerFehlermeldung zurückkehrt.Die Schnittstelle erweitert sich also leicht:MPI_SsendSendAsyncSendSyncSendIDRecvIDTagLenSendIDRecvIDTagAddressLen<strong>Message</strong>Abb. 4.3: Kommunikationsmuster <strong>von</strong> MPI_SsendSendSync(int nSendID, int nRecvID, int nTag, void* pBuffer, int nLen, BOOL fWait);RecvSync(int nRecvID, int nSendID, int nTag, void* pBuffer, int& nLen);Für den Synchronous-Modus ist fWait gleich TRUE, für den Ready-Modus FALSE.MPI_RecvRecvAsyncRecvSyncDie allgemeine MPI_Send-Funktion kann gemäss Spezifikation auf eine dieser Modi zurückgreifen.4.1.2 Kollektive KommunikationAls MPI-Funktionen für die kollektive Kommunikation gelten MPI_Bcast, MPI_Scatter,MPI_Gather, MPI_Alltoall <strong>und</strong> Varianten da<strong>von</strong>. Es ist unschwer einzusehen, dass all dieseDiplomarbeit <strong>von</strong> Roman Roth Seite 29


Institut für ComputersystemeETH ZürichFunktionen mittels den oben besprochenen (nicht-blockierenden) Punkt-zu-Punkt-Funktionenemuliert werden können, so dass die Schnittstelle nicht verändert werden müsste. Der <strong>Message</strong>-<strong>Passing</strong>-Layer wäre dann für die Implementation dieser Funktionen auf der bestehendenSchnittstelle verantwortlich. Aber ist das auch sinnvoll?4.1.2.1 MPI_BcastViele lokale Netzwerktechnologien bauen auf sogenannten Broadcast-Medien auf. VerschiedeneKommunikationsprotokolle können auch Broadcast-Funktionen anbieten. Doch das Problem ist,dass ein MPI-Broadcast kein netzweiter Broadcast sein muss. Eine MPI-Applikation muss nochlange nicht auf allen Rechnern des Netzwerks laufen. Es müsste also <strong>von</strong> einer unteren Schichteine Art Multicast angeboten werden, was leider nur all zu selten der Fall ist, <strong>und</strong> hier deshalb nichtweiter betrachtet wird. Es wird also der Aufbau auf den Punkt-zu-Punkt-Funktionen empfohlen.4.1.2.2 MPI_Scatter, MPI_Gather, MPI_AlltoallDiese drei Funktionen (<strong>und</strong> alle Varianten <strong>von</strong> diesen) erscheinen bei flüchtigem hinsehen alsMulticast-Funktionen. Dem ist aber nicht so, denn es wird nicht ein Paket an mehrere geschickt,sondern mehrere verschiedene Pakete an verschiedene Empfänger. Allfällige Multicast-Funktionender unteren Schichten können also nicht ausgenutzt werden. So bleibt eigentlich nur der Aufbauauf den Punkt-zu-Punkt-Funktionen. Es fällt nur noch eine Diskrepanz ins Auge: Die kollektivenFunktionen benutzen kein Tag. Das Tag kann deshalb dafür verwendet werden, dem Empfängermitzuteilen, dass die empfangene Meldung nicht durch MPI_Send verschickt wurde, sondern durcheine kollektive Form. Dies bedingt jedoch, dass einige Tags reserviert werden, <strong>und</strong> damit demBenutzer nicht mehr zur Verfügung stehen. Doch das ist kein Problem, da MPI mit MPI_TAG_UBeine Obergrenze für Tags kennt, die implementationsabhängig sein kann.Kurz: Alle kollektiven Kommunikationsformen werden auf den Punkt-zu-Punkt-Funktionenaufgebaut. Die bereits definierte Schnittstelle wird nicht erweitert. Einzig die Tags werdeneingeschränkt, doch das ist eine Sache der (oberen) MPI-Schnittstelle.4.1.3 SynchronisationAls explizites Synchronisationselement existiert in MPI nurdie sogenannte Barrier. Sie ist eigentlich nur einSpezialfall einer kollektiven Kommunikation: Entweder isteine Barrier eine spezielle MPI_Alltoall-Funktion, dieallen mitteilt, dass der Sender an der Barrier angelangt ist<strong>und</strong> gleichzeitig darauf wartet, dass er <strong>von</strong> allen anderendie selbe Mitteilung erhält (dezentale Variante). Oder eswird mittels MPI_Gather die Ankunft an der Barriereinem dedizierten Koordinator mitgeteilt <strong>und</strong> danach mitMPI_Scatter darauf gewartet, dass der Koordinator dieAnkunft aller Knoten bestätigt (zentrale Variante).MPI_AlltoallMPI_GatherMPI_ScatterAbb. 4.4: Dezentrale bzw. zentraleBarrier-VarianteDa an Synchronisationspunkten sowieso immer Wartezeiten auftreten, lohnt sich eine Optimierungan dieser Stelle nicht. Eine Barrier wird also auf den kollektiven MPI-Funktionen bzw. auf denPunkt-zu-Punkt-Funktionen aufbauen, wobei wiederum ein Tag für diesen Zweck reserviert werdenmuss.4.1.4 Die SchnittstelleZusammenfassend kann also folgende Schnittstelle zwischen <strong>Message</strong>-<strong>Passing</strong>-Layer <strong>und</strong> Zero-Copy-Layer definiert werden:ŒŒSendAsync([in] int nRecvID, [in] void* pBuffer, [in] int nLen);Es wird ein durch pBuffer <strong>und</strong> nLen definiertes Datenpaket an den Empfänger nRecvIDunsynchronisiert verschickt. Die Grösse des Paketes ist beschränkt.RecvAsync([out] int& nSendID, [out] void*& pBuffer, [out] int& nLen);Empfängt ein unsynchronisiert verschicktes Datenpaket <strong>von</strong> einem beliebigen Sender. Nebendem Pointer auf das Datenpaket (pBuffer) <strong>und</strong> dessen Länge (nLen) wird auch derSeite 30Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeŒŒAbsender (nSendID) identifiziert. Bei Knoten-zu-Knoten-Kommunikation identifiziert nSendIDjedoch nur den Knoten. Allfällige weitere Informationen über Absender <strong>und</strong> Empfänger müssenim Datenpaket enthalten sein.SendSync([in] int nSendID, [in] int nRecvID, [in] int nTag,[in] void* pBuffer,[in] int nLen, [in] BOOL fWait);Synchronisiertes Senden eines Datenpaketes (pBuffer, nLen). Gesendet wird das Paket,wenn eine Empfangsbereitschaftsmeldung mit übereinstimmenden Parametern empfangenwurde. fWait bestimmt, ob auf eine Bestätigung gewartet wird, oder ob bei fehlenderBestätigung mit einer Fehlermeldung reagiert wird. nSendID ist nur bei Knoten-zu-Knoten-Kommunikation notwendig.RecvSync([in] int nRecvID, [in] int nSendID, [in] int nTag,[in] void* pBuffer, [in/out] int& nLen);Diese Funktion sendet eine Empfangsbereitschaftsmeldung mit allen Parametern an denSender (nSendID). Danach wird auf den Empfang gewartet. nRecvID ist nur bei Knoten-zu-Knoten-Kommunikation notwendig.4.2 Die Schnittstelle zwischen DSM- <strong>und</strong> Zero-Copy-Layer<strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong> ist zwar ein ganz anderes Modell als <strong>Message</strong>-<strong>Passing</strong>. Da jedeNetzwerktechnologie eine Art <strong>von</strong> <strong>Message</strong>-<strong>Passing</strong> darstellt, bleibt jedoch nichts anderes übrig,als die anfallende Kommunikation auf ein <strong>Message</strong>-<strong>Passing</strong>-Modell abzubilden. Es ist deshalbvorteilhaft, bereits an der zu betrachtenden Schnittstelle auf <strong>Message</strong>-<strong>Passing</strong> aufzubauen.Gr<strong>und</strong>sätzlich würde das heissen, dass die in Kapitel 4.1 definierte Schnittstelle zwischen Zero-Copy-Layer <strong>und</strong> <strong>Message</strong>-<strong>Passing</strong>-Layer verwendet werden könnte. Eine genauere Betrachtungsoll jedoch abklären, ob allenfalls Vereinfachungen möglich sind, oder ob durch Veränderungender Schnittstelle eine grössere Performance resultieren könnte.Was Zero-Copy-Kommunikation betrifft, kann ein DSM-System gegenüber <strong>Message</strong>-<strong>Passing</strong> mitzwei Vorteilen aufwarten. Erstens: Die Kommunikation ist nur sehr lose mit der DSM-Schnittstelle(z.B. OpenMP) gekoppelt. Der DSM-Layer kann also optimal auf die Zero-Copy-Direktiveneingestellt werden. Und zweitens: Obwohl ein DSM-System durchaus mit mehreren lokalenThreads arbeiten kann, ist in vielen Fällen nur Knoten-zu-Knoten-Kommunikation notwendig.Zusätzliche Sender- <strong>und</strong> Empfängeridentifikation ist also nicht unbedingt notwendig. Es wird jedochda<strong>von</strong> ausgegangen, dass der Empfänger den Absenderknoten identifizieren kann.Je nach Implementation kennt ein DSM-System drei typische Kommunikationsmuster, die genauerbetrachtet werden müssen:4.2.1 Der Page-RequestMit dem Page-Request kann ein Knoten eine gültige Kopie einer Speicherseite bei einementfernten Knoten anfordern. Identifiziert wird die gewünschte Seite durch eine Nummer. DieseNummer <strong>und</strong> die lokale, virtuelle Adresse, an der diese Seite positioniert ist, müssen dementfernten Knoten mitgeteilt werden. Da diese Mitteilung als Signalisation derEmpfangsbereitschaft aufgefasst werden kann, könnte der Page-Request in einen RecvSync <strong>und</strong>damit Nummer <strong>und</strong> Adresse in die Empfangsbereitschaftsmeldung verpackt werden. Auf eineLänge kann verzichtet werden, da diese immer der Grösse einer Speicherseite entspricht. DiesesPaket hätte also den folgenden Aufbau:Adresse der SpeicherseiteNummer der SpeicherseiteDer Page-Request würde mit der folgenden Funktion ausgelöst:RecvSync(int nSendID, int nPageNo, void* pPageAddr);Der in Kapitel 2 kurz beschriebene Zero-Copy-Layer hat die Eigenschaft, dassEmpfangsbereitschaftsmeldungen nur innerhalb des Layers existieren, also nicht an obere Layerweitergegeben werden. Dies würde bedeuten, dass der DSM-Layer nicht über den Page-RequestDiplomarbeit <strong>von</strong> Roman Roth Seite 31


Institut für ComputersystemeETH Zürichinformiert würde. Eine Modifikation des Zero-Copy-Layers ist also notwendig: DieEmpfangsbereitschaftsmeldung ist eigentlich nichts anderes als ein unsynchronisiert verschicktesDatenpaket mit speziellem Inhalt. Dieses Paket könnte ohne grossen Aufwand an den oberenLayer propagiert werden. Der obere Layer benutzt dann eine ganz gewöhnliche RecvAsync-Funktion, um das Paket zu empfangen:RecvAsync(int& nSendID, void*& pBuffer, int& nLen);4.2.2 Der Page-ReplyAuf einen empfangenen Page-Request muss ein entsprechender Page-Reply erfolgen: EineSpeicherseite des lokalen Knotens muss an die entsprechende Stelle im entfernten Systemtransportiert werden. Da der Page-Request bereits die Empfangsbereitschaft signalisiert <strong>und</strong> dieEmpfangsadresse dem Zero-Copy-Layer mitgeteilt hat, kann die Seite über einen SendSyncverschickt werden. Die entsprechende Funktion kann folgendermassen aussehen:SendSync(int nRecvID, int nPageNo, void* pSourcePageAddr);Mittels nRecvID <strong>und</strong> nPageNo würde die bereits empfangene Empfangsbereitschaftsmeldung <strong>und</strong>damit auch die Zieladresse auf dem entfernten System identifiziert.Da mit der Empfangsbereitschaftsmeldung auch die Zieladresse an den DSM-Layer propagiertwurde, könnte auch folgende Variante angewendet werden:SendSync(int nRecvID, void* pSourcePageAddr, void* pTargetPageAddr);Damit könnte ein Zwischenspeichern der Empfangsbereitschaftsmeldung im Zero-Copy-Layervermieden werden.4.2.3 SynchronisationAllgemeine DSM-Systeme kennen verschiedene Synchronisationsdirektiven. Dabei können beidein Kapitel 4.1.3 beschriebenen Varianten (zentral oder dezentral) zum Einsatz kommen. Bei vielendieser Direktiven werden auch zusätzliche Information mitgeschickt.Stellvertretend soll hier eine Barrier betrachtet werden, die zusätzlich die Nummern der durcheinen Knoten veränderten Speicherseiten enthalten soll. Wenn die mitgeschickten Daten denUmfang eines unsynchronisierten Paketes nicht übersteigen, dann wird vorteilhaft unsynchronisiertKommuniziert. Eine ID muss das Datenpaket als Barrier identifizieren. Danach kommen dieSeitennummern.SyncID PageNo ... PageNoDieses Paket wird über ein normales SendAsync/RecvAsync-Funktionspaar gesendet bzw.empfangen:SendAsync(int nRecvID, void* pBuffer, int nLen);RecvAsync(int& nSendID, void*& pBuffer, int& nLen);Da auch die propagierten Page-Requests durch RecvAsync empfangen werden, muss die SyncIDso gewählt werden, dass diese keine gültige Speicherseite darstellt.Grössere Pakete können entweder durch mehrere unsynchronisierte Pakete oder abersynchronisiert verschickt werden:SendSync(int nRecvID, int nSyncID, void* pBuffer, int nLen);RecvSync(int nSendID, int nSyncID, void* pBuffer, int& nLen);Da beim Page-Request die Empfangsbereitschaftsmeldung an den DSM-Layer propagiert wird,würde dies auch hier geschehen. Ein solches Paket könnte vom DSM-Layer ignoriert werden. Esmuss jedoch darauf geachtet werden, dass SyncID keine gültige Seitenadresse darstellt, damiteine propagierte Empfangsbereitschaftsmeldung eines Page-Request <strong>von</strong> einer Barrierunterschieden werden könnte.Seite 32Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme4.2.4 Die SchnittstelleZusammenfassend kann also folgende Schnittstelle für ein DSM-System empfohlen werden:ŒŒŒŒSendSync([in] int nRecvID, [in] int nPageNo,[in] void* pSourcePageAddr);Page-Reply: Eine Speicherseite wird an ein entferntes System geschickt.RecvSync([in] int nSendID, [in] int nPageNo,[in] void* pTargetPageAddr);Page-Request: Es wird eine Speicherseite <strong>von</strong> einem entfernten System verlangt. DieEmpfangsbereitschaftsmeldung enthält nPageNo <strong>und</strong> pTargetPageAddr <strong>und</strong> wird an denDSM-Layer propagiert.SendAsync([in] int nRecvID, [in] void* pBuffer, [in] int nLen);Synchronisationsdirektiven wie Barriers verwenden vorteilhaft ein (oder mehrere)unsynchronisierte Datenpakete. Das Paket muss eine ID zur Erkennung tragen.RecvAsync([out] int& nSendID, [out] void*& pBuffer, [out] int& nLen);Empfängt entweder die Pakete <strong>von</strong> Synchronisationsdirektiven oder die propagierten Paketedes Page-Requests.4.3 Ein <strong>Vergleich</strong> der SchnittstellenBetrachtet man die beiden Schnittstellen, dann gibt es einen grossen Unterschied: Während<strong>Message</strong>-<strong>Passing</strong> eine Thread-zu-Thread-Kommunikation benötigt, gibt sich das DSM-System mitKnoten-zu-Knoten-Kommunikation zufrieden. Falls das unter dem Zero-Copy-Layer liegendeKommunikationssystem Thread-zu-Thread-Kommunikation anbietet (zum Beispiel in Form <strong>von</strong>Ports), dann kommt dieser Unterschied nicht wirklich zum Tragen. Falls dies nicht der Fall ist, dannmüssen beim <strong>Message</strong>-<strong>Passing</strong>-Modell zusätzliche Sender- <strong>und</strong> Empfängeridentifikationenkommuniziert werden. Da diese Arbeit auf Myricom-GM aufbaut bzw. aufbauen wollte, wird vomzweiten Fall ausgegangen.Zwei Spezialbehandlungen unterscheiden die beiden Systeme desweiteren: Das DSM-System wirdaus Performancegründen die Empfangsbereitschaftsmeldung des RecvSync an den oberen Layerpropagieren. Das <strong>Message</strong>-<strong>Passing</strong>-System erwartet beim SendSync ein Flag, das aussagt, ob aufeine Empfangsbereitschaftsmeldung gewartet werden soll oder nicht. Diese beiden Spezialfällelassen sich jedoch leicht beim Initialisieren des Zero-Copy-Layers aktivieren bzw. deaktivieren.Eine gemeinsame Schnittstelle könnte in etwa folgende Funktionen enthalten:ŒŒŒSendAsync([in] int nRecvID, [in] void* pBuffer, [in] int nLen);Verschickt ein Datenpaket beschränkter Grösse auf unsynchronisierte Art. Der Header desDatenpaketes wird eine Identifikation (Tag/SyncID) <strong>und</strong> eventuell Sender- <strong>und</strong>Empfängerinformationen enthalten.RecvAsync([out] int& nSendID, [out] void*& pBuffer, [out] int& nLen);Empfängt ein unsynchronisiert verschicktes Datenpaket <strong>von</strong> einem beliebigen Sender. Nebendem Pointer auf das Datenpaket (pBuffer) <strong>und</strong> dessen Länge (nLen) wird auch derAbsender (nSendID) identifiziert. Bei Knoten-zu-Knoten-Kommunikation identifiziert nSendIDjedoch nur den Knoten. Eine Identifikation des Inhalts <strong>und</strong> allfällige weitere Informationen überAbsender <strong>und</strong> Empfänger müssen im Datenpaket enthalten sein.SendSync([in] int nSendID, [in] int nRecvID, [in] int nID,[in] void* pBuffer,[in] int nLen, [in] BOOL fWait);Synchronisiertes Senden eines Datenpakets (pBuffer, nLen). Gesendet wird das Paket,wenn eine Empfangsbereitschaftsmeldung mit übereinstimmenden Parametern empfangenwurde. nID ist eine Identifikation des Datenpaketes (Tag/PageNo). fWait bestimmt, ob aufeine Bestätigung gewartet wird, oder ob bei fehlender Bestätigung mit einer Fehlermeldungreagiert wird. nSendID ist nur im <strong>Message</strong>-<strong>Passing</strong>-Layer unter Verwendung <strong>von</strong> Knoten-zu-Knoten-Kommunikation relevant.Diplomarbeit <strong>von</strong> Roman Roth Seite 33


Institut für ComputersystemeETH ZürichŒRecvSync([in] int nRecvID, [in] int nSendID, [in] int nID,[in] void* pBuffer, [in/out] int& nLen);Diese Funktion sendet eine Empfangsbereitschaftsmeldung mit allen Parametern an denSender (nSendID). Danach wird auf den Empfang gewartet. nID ist eine Identifikation desgewünschten Datenpaketes (Tag/PageNo). nRecvID ist nur im <strong>Message</strong>-<strong>Passing</strong>-Layer unterVerwendung <strong>von</strong> Knoten-zu-Knoten-Kommunikation relevant.Eine gemeinsame Schnittstelle für die beiden Systeme <strong>Message</strong>-<strong>Passing</strong> <strong>und</strong> DSM ist also sehrgut möglich. DSM hat eventuell etwas unnötigen Overhead: Sender-/Empfängeridentifikation sowiedie Paketlänge, die beim DSM konstant die Grösse einer Page ist, wären beim synchronisiertenSenden <strong>und</strong> Empfangen nicht notwendig. Da sich diese Informationen jedoch auf dieEmpfangsbereitschaftsmeldung der RecvSync-Funktion beschränken, kann dieser Overhead ohneBedenken in Kauf genommen werden.Die eigentlichen Kommunikationsfunktionen der Schnittstelle wurden soeben ausführlichbesprochen. Ein vollständig implementierter Layer dürfte über einige weitere Funktionen verfügen.Ein Aspekt, der bisher noch nicht betrachtet wurde, ist die Deallokation der Puffer fürunsynchronisierten Empfang. Die Puffer werden ja nicht <strong>von</strong> der Applikation, sondern vom Zero-Copy-Layer alloziert. Über die Funktion RecvAsync gelangen diese jedoch in den Besitz derApplikation. Entweder wird die Applikation für die Deallokation verantwortlich gemacht, oder bessernoch, es gibt eine Funktion, mittels welcher die Applikation den Puffer dem Layer zurückgebenkann:ReleaseAsyncBuffer(void* pBuffer);Welche weiteren Funktionen nötig sein können, kann der konkreten Implementation, beschriebenim folgenden Kapitel, entnommen werden.Seite 34Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme5 Der Zero-Copy-LayerIn Kapitel 2 wurde ein Kommunikationsmuster vorgestellt, das auf unsynchronisierten kleinen <strong>und</strong>synchronisierten grossen Paketen beruht (siehe Abbildung 2.1). Dieses Konzept wurde im Zero-Copy-Layer realisiert, der sowohl vom MPI- wie auch vom OpenMP-Prototypen (siehe Kapitel 6<strong>und</strong> 7) verwendet wird. Die beschriebene Schnittstelle wurde jedoch nicht vollständig <strong>und</strong> auspraktischen Gründen in einer etwas variierten Form implementiert.Der Zero-Copy-Layer ist unabhängig <strong>von</strong> der verwendeten Netzwerktechnologie. Die abhängigenTeile wurden im darunterliegenden Netzwerk-Layer implementiert.SendAsyncSendSync RecvSync CallBack CheckRequestssuccess/pending/errorsuccess/pending/errorpending/errorsuccess/errorsuccess/pending/errorReady?yesnoNwSendSyncSendListyessuccess/pending/errornoReady?ReadynoNorm?AsyncRecvyesSending ListSyncRecvNwCheckRequestsdoneRecvListSendDoneRequestReplyControllZero Copy Layer Network LayerAbb. 5.1: Schema der Request-Verwaltung im Zero-Copy-LayerDie Abbildung 5.1 zeigt deutlich, dass dieses Konzept in Wirklichkeit deutlich komplexer ist, alsdies Abbildung 2.1 erahnen liesse. Deshalb werden hier die Abläufe <strong>und</strong> Schnittstellen etwasgenauer erklärt.5.1 Die RequestsSenden <strong>und</strong> Empfangen ist immer mit Informationen wie Sender- <strong>und</strong> Empfängeridentifikation,Datenidentifikation (Tag), Länge der Daten <strong>und</strong> den Daten selbst verb<strong>und</strong>en. All dieseInformationen werden in sogenannte Requests verpackt. Für asynchrone Kommunikation enthältder Request die eigentlichen Daten, für synchrone Kommunikation nur einen Pointer auf die Daten.Applikationen, die über den Zero-Copy-Layer senden oder empfangen wollen, müssen zuersteinen Request mit den nötigen Daten initialisieren <strong>und</strong> dem Zero-Copy-Layer übergeben. Dieserkann den Request entweder sofort bearbeiten, oder zurückstellen. Im zweiten Fall bleibt derRequest solange im Besitz des Zero-Copy-Layers, bis er bearbeitet <strong>und</strong> über die Call-Back-Funktion (siehe unten) an die Applikation zurückgegeben wurde.Für jede Operation gibt es einen Request: Für asynchrones Senden (Async-Send) <strong>und</strong> Empfangen(Async-Recv), für synchrones Senden (Sync-Send) <strong>und</strong> Empfangen (Sync-Recv), sowie für dasSynchronisieren (Ready).Der konkrete Aufbau der Requests ist in Kapitel 5.3.1 dargestellt.Diplomarbeit <strong>von</strong> Roman Roth Seite 35


Institut für ComputersystemeETH Zürich5.2 Die AbläufeDer Zero-Copy-Layer kann auf Netzwerktechnologien mit den verschiedensten Eigenschaftenaufbauen. Während das Empfangen zwangsläufig nicht-blockierend sein muss, kann das Sendensowohl blockierend wie auch nicht-blockierend sein. Je nach Netzwerk-Layer kann die Applikationdurch Pollen feststellen, ob Daten empfangen oder (nicht-blockierend) gesendet wurden, oder eswird ihr durch einen anderen Mechanismus (z.B. Asynchronous-Procedure-Calls) ohne Pollingmitgeteilt.5.2.1 Initialisieren der Layer <strong>und</strong> Erstellen der VerbindungenEgal, ob die unter dem Netzwerk-Layer liegende Netzwerkarchitektur verbindungsorientiert arbeitetoder nicht, eine Applikation, die den Zero-Copy-Layer benutzt, hat zu jedem Knoten eine„Verbindung“ aufzubauen. Für das Aufbauen der Verbindung (falls notwendig) ist der Netzwerk-Layer zuständig. Spätestens nach dem Verbindungsaufbau muss der Netzwerk-LayerEmpfangsbereitschaft garantieren können. Das heisst unter anderen auch, dass bereits genügendEmpfangspuffer für den asynchronen Empfang bereitgestellt wurden. Falls die Schicht unter demNetzwerk-Layer nicht verbindungsorientiert arbeitet, kann die Empfangsbereitschaft auch bereitsbeim Initialisieren des Layers erstellt <strong>und</strong> der Wunsch nach Verbindungsaufbau ignoriert, sprich dieentsprechende Funktion leer gelassen werden.5.2.2 Call-Back-FunktionIn jedem Fall hat dieApplikation dem Zero-Copy-Layer den Pointerauf eine Call-Back-Funktion mitzuteilen,welche vom Layeraufgerufen wird, wennDaten empfangen bzw.versendet wurden. Zuwelchem Zeitpunktdiese Call-Back-Funktion aufgerufenwird, hängt <strong>von</strong> derImplementation desApplikation Zero-Copy-Layer Netzwerk-LayerCallBackFuncRegisterCallBackCheckRequestsAsyncRecvSyncRecvSendDoneCheckRequestsCheckRequestsfür jedes unsynchronisiertempfangene Paketfür jedes synchronisiertempfangene Paketfür jedes nicht-blockierendverschickte PaketNetzwerk-Layers ab.Abb. 5.2: Empfangen <strong>von</strong> Requests durch PollenMuss gepollt werden,dann hat die Applikationregelmässig die CheckRequests-Funktion aufzurufen. Innerhalb dieser Funktion wird für jedesempfangene bzw. versendete Paket einmal die Call-Back-Funktion aufgerufen (Abbildung 5.2).Applikation Zero-Copy-Layer Netzwerk-LayerBetriebssystemRegisterCallBackWaitForSingleObjectExKernelCallBackFuncAsyncRecvRecvDoneAPCSyncRecvSendDoneSendDoneAPCAbb. 5.3: Empfangen <strong>von</strong> Requests durch APCsAnders verhalten sich die erwähnten Asynchronous-Procedure-Calls (APC). Ein APC ist imwesentlichen nur ein Aufruf einer Funktion. Der Aufruf geschieht jedoch nicht unbedingt sofort,sondern nur dann, wenn sich der entsprechende Thread in einem sogenannten Alertable WaitSeite 36Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeState befindet. Ein solcher Zustand kann mit WaitForSingleObjectEx oder SleepEx erreichtwerden. APCs können wohl im User-Mode mit QueueUserAPC ausgelöst werden, werden jedochhäufig <strong>von</strong> Kernel-Mode-Treibern verwendet, um das Ende einer nicht-blockierenden Transaktionzu signalisieren. Windows NT stellt für solche Transaktionen die beiden Win32-FunktionenReadFileEx <strong>und</strong> WriteFileEx zur Verfügung. Diese APCs sind also eine Möglichkeit, dasaufwendige Polling zu umgehen.5.2.3 Asynchrones Senden <strong>und</strong> EmpfangenDer Netzwerk-Layer ist dafür verantwortlich, dass jederzeit kleinere Datenpakete empfangenwerden können. Er hat allenfalls genügend Empfangspuffer zur Verfügung zu stellen. Dies erlaubtein sofortiges versenden <strong>von</strong> solchen Paketen. Wie Abbildung 5.1 zeigt, ist beim Senden innerhalbdes Zero-Copy-Layers keine Verarbeitung notwendig. Ein Aufruf <strong>von</strong> SendAsync wird also direktdem Netzwerk-Layer weitergereicht.Dieser ist dafür verantwortlich, das Paket so bald als möglich zu versenden. Tut er dies sofort <strong>und</strong>blockierend, dann gibt er nach erfolgter Kommunikation den Status Success bzw. Error zurück.Erfolgt das Senden zu einem späteren Zeitpunkt, dann gibt er Pending zurück <strong>und</strong> muss das Paketin eine <strong>von</strong> ihm zu verwaltende Queue einfügen. Sobald das Paket versendet ist, muss diesesmittels SendDone dem Zero-Copy-Layer <strong>und</strong> damit der Applikation wieder zurückgeben werden.ApplicationSENDERWaitForSingleObjectExCallBackFuncZero-Copy-LayerSendAsyncSendDoneNetwork-LayerSendAsyncSendDoneAPCOperating SystemWriteFileExWaitNetworkTimeOperating SystemReadFileExWaitNetwork-LayerInitRecvDoneAPCZero-Copy-LayerInitAsyncRecvApplicationRECEIVERCallBackFuncWaitForSingleObjectExAbb. 5.4: Der nicht-blockierende, asynchrone Sende- <strong>und</strong> Empfangsvorgang mit APCAuf Empfängerseite muss das Paket im Netzwerk-Layer in einen Puffer aufgefangen werden.Dieser Puffer wird durch AsyncRecv via Zero-Copy-Layer <strong>und</strong> Call-Back-Funktion der Applikationübergeben.5.2.4 Synchrones Senden <strong>und</strong> EmpfangenEtwas komplexer ist das synchrone Senden <strong>und</strong> Empfangen. Teilt eine Applikation dieEmpfangsbereitschaft via RecvSync dem Zero-Copy-Layer mit, dann wird ein asynchronesDatenpaket (Ready-Request genannt) generiert, das neben den Headerinformationen die virtuelleDiplomarbeit <strong>von</strong> Roman Roth Seite 37


Institut für ComputersystemeETH ZürichAdresse <strong>und</strong> die Grösse des Empfangspuffer enthält. Dieses Paket wird durch das asynchroneSenden des Netzwerk-Layers an den Sender der synchronen Kommunikation geschickt. Dort wirdgeprüft, ob ein entsprechendes SendSync bereits aufgerufen wurde (Abbildung 5.5, rechts). WennAsyncRecvSendSyncReady-Request ?noCallBackFuncyesCheckSendListnot fo<strong>und</strong>Enter Requestinto SendListCheckSendListnot fo<strong>und</strong>Enter Requestinto SendListfo<strong>und</strong>fo<strong>und</strong>Zero-Copy-LayerSendSyncSendSyncNetwork-LayerAbb. 5.5: Der Aufruf <strong>von</strong> SendSync durch eine Applikation bzw.der Aufruf <strong>von</strong> AsyncRecv beim Empfang eines Async-Recv- oder Ready-Requests.ja wird das entsprechende Paket sofort versendet. Wenn nein, wird sich der Zero-Copy-Layer dieEmpfangsbereitschaft in einer Liste merken.Auf Senderseite kann das SendSyncjederzeit aufgerufen werden. Es wirddann geprüft, ob der Empfänger seineBereitschaft bereits mitgeteilt hat(Abbildung 5.5, links). Wenn ja, wirddas Paket sofort verschickt. Wennnein, wird das Versenden solangeaufgeschoben, bis dieEmpfangsbereitschaft mitgeteilt wird.Get Next RequestGet First RequestIsNull ?noyesNo RequestFo<strong>und</strong>Das in Abbildung 5.5 auftretendeCheck SendList wird in Abbildung 5.6genauer dargestellt. Im wesentlichenwird geprüft, ob Absender, Empfänger,Tag <strong>und</strong> Grösse des aktuellenRequests mit einem Request in derListe übereinstimmen. Mit Legal BufferSize ist in diesem Zusammenhanggemeint, dass die Grösse desEmpfangspuffers mindestens so grosssein muss, wie die Grösse der zuversendenden Daten.Ist die Empfangsbereitschaft bestätigt,dann kann NwSendSync desNetzwerk-Layers aufgerufen werden,um das Datenpaket endgültig zuversenden.Wird dann auf Empfängerseite einsynchrones Paket empfangen, dannmuss der Netzwerk-Layer dies durchSyncRecv dem Zero-Copy-Layer <strong>und</strong>der Applikation mitteilen.nonononoSameSendID ?yesSameRecvID ?yesSameTag ?yesLegalBuffersize?yesRequest Fo<strong>und</strong>Abb. 5.6: Ablauf eines Check-RecvListSeite 38Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme5.2.5 Die Zero-Copy-Layer-Variante für OpenMPZero-Copy wird in OpenMP dazu verwendet, Speicherseiten des <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong> <strong>von</strong>einem Knoten zu einem anderen zu verschicken. Knoten A schickt einen Page-Request an KnotenB, der die Speicherseite zurückschickt. Wie Abbildung 5.7 zeigt, müsste A zuerst einen Recv-Sync-Request generieren, um die Empfangsbereitschaft zu signalisieren. Dann hat er einen Async-Request an B zu schicken, der sagt, welche Seite er wünscht. Daraufhin macht B einen Send-Sync-Request, der die Speicherseite versendet. Kompliziert <strong>und</strong> viel Kommunikation, nicht?OpenMP A Zero-Copy-Layer Network Zero-Copy-Layer OpenMP BRequestRecvSync Ready ReadyAsync Async RequestReplyRecvSyncSendSyncReplyAbb. 5.7: OpenMP-Page-Request <strong>und</strong> -Reply ohne ModifikationOpenMP A Zero-Copy-Layer Network Zero-Copy-Layer OpenMP BRequest RecvSync Ready Ready Async RequestReplyRecvSyncSendSyncReplyAbb. 5.8: OpenMP-Page-Request <strong>und</strong> -Reply mit ModifikationEine kleine Modifikation des bestehenden Zero-Copy-Layers macht alles viel einfacher (sieheAbbildung 5.8). Ein Recv-Sync-Request generiert ja einen Ready-Request, mittels welchem dieEmpfangsbereitschaft signalisiert wird. Diesem Request könnte auch gleich beigefügt werden,welche Speicherseite gewünscht wird. Damit kann ein Request gespart werden. Konkret könntedas Tag des Ready-Requests die Nummer der gewünschten Speicherseite enthalten.Normalerweise würde dieser Ready-Request für die Empfängerapplikation niemals sichtbarwerden, sondern würde nur innerhalb des Zero-Copy-Layers existieren. Beim modifizierten Layer(Abbildung 5.9) wird ein Ready-Request wie ein normaler Async-Request behandelt <strong>und</strong> via Call-Back-Funktion an die Applikation weitergereicht.SendAsyncSendSync RecvSync CallBack CheckRequestssuccess/pending/errorsuccess/pending/errorpending/errorsuccess/errorsuccess/pending/errorNwSendSyncsuccess/pending/errorReadyAsyncRecvSending ListSyncRecvNwCheckRequestsdoneRecvListSendDoneRequestReplyControllZero Copy Layer Network LayerAbb. 5.9: Modifizierter Zero-Copy-Layer für OpenMPDiplomarbeit <strong>von</strong> Roman Roth Seite 39


Institut für ComputersystemeETH ZürichDie Tatsache, dass RecvSync garantiert vor dem entsprechenden SendSync aufgerufen wird,macht auch die komplizierte Buchführung r<strong>und</strong> um die SendList unnötig.5.3 Die SchnittstellenUm die Schnittstellen der beiden eben beschriebenen Layer besser verstehen zu können, müssenvorerst einige Datenstrukturen, insbesondere die sogenannten Requests, erläutert werden.5.3.1 Die RequestsEntsprechend den beiden Kommunikationsarten (synchron <strong>und</strong> asynchron) gibt es auch zweiwesentliche Request-Typen:ŒŒAsync-Request: Ein Async-Request behandelt dasSenden bzw. Empfangen <strong>von</strong> kleinen,unsynchronisierten Paketen. Er beinhaltet alsHeaderinformation die Sender- <strong>und</strong>Empfängeridentifikation, ein Tag sowie die Länge desnachfolgenden Body.Jede Sender- bzw. Receiver-ID ist zweigeteilt: Dieoberen 16 Bits enthalten die ID des Knotens, dieunteren eine applikationsabhängige ID.Die Grösse des Bodies ist beschränkt. Bei dervorliegenden Implementation aufZCL_ASYNC_BUFFER_SIZE Bytes.Abb. 5.10: Request-DatenstrukturenEine Applikation kann jederzeit einen Async-Requestvom Zero-Copy-Layer anfordern <strong>und</strong> den Body mitDaten füllen. Übermittelt wird beim Senden der Header, sowie die ersten Len Bytes desBodies.Als Spezialfall eines Async-Requests kann der Ready-Request erwähnt werden. Er wird zumVersenden der Empfangsbereitschaft verwendet <strong>und</strong> enthält als Body nur die 4-Byte-Adressedes Empfangspuffers <strong>und</strong> dessen Grösse. Erkennen kann man einen Ready-Requestinnerhalb des Zero-Copy-Layers am gesetzten most-significant-bit des Tags. Bei Async-Requests darf demnach dieses Bit nicht gesetzt sein.Sync-Request: Der Sync-Request behandelt das synchronisierte Senden <strong>und</strong> Empfangen. Erenthält dieselben Headerinformationen wie der Async-Request. Zusätzlich enthält er je einenSource- <strong>und</strong> Targetpointer. Übermittelt wird jedoch nicht der Request, sondern der Inhalt desSpeicherbereichs, der durch Sourcepointer <strong>und</strong> Länge definiert wird.Diese beiden Request-Typen werden in einen allgemeinen Request verpackt, der folgende Strukturhat:struct ZCL_REQUEST{int nType;ZCL_STATUS Status;union{void* pRequest;PZCL_ASYNC_REQUEST pAsync;PZCL_SYNC_REQUEST pSync;PZCL_REQUEST_HEAD pHead;};};Sender IDReceiver IDTagLen0N-1Async RequestSender IDReceiver IDTagLenSource PtrTarget PtrSync RequestTyp des RequestsStatus <strong>und</strong> Fehlercode des RequestsPointer zum Sync-/Async-RequestPointer zum Async-RequestPointer zum Sync-RequestPointer zum Request-HeaderSeite 40Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeEs gibt die folgenden Request-Typen:TypZCL_SYNC_SENDZCL_SYNC_RECVZCL_ASYNC_SENDZCL_ASYNC_RECVBeschreibungSync-Request für synchrones SendenSync-Request für synchrones EmpfangenAsync-Request für asynchrones SendenAsync-Request für asynchrones EmpfangenZCL_SYNC_READY Sync-Request. Existiert nur im Zero-Copy-Layer. Signalisiert dieEmpfangsbereitschaft eines entfernten Knotens.ZCL_ASYNC_READYZCL_ERROR_RECVTab. 5.1: Request-TypenReady-Request. Teilt einem entfernten Knoten mit, dass der lokaleKnoten für den Empfang eines synchron verschickten Paketes bereit ist.Signalisiert einen Fehler beim Empfangen <strong>von</strong> Paketen. Bei diesemRequest-Typ haben die Pointer den Wert NULL.5.3.2 Der StatusEine spezielle Struktur enthält den Status eines Requests oder einer Funktion:struct ZCL_STATUS{int nStatus;int nErrorCode;}nStatus kann folgende Werte enthalten:StatusZCL_SUCCESSZCL_ERRORZCL_NW_ERRORZCL_PENDINGTab. 5.2: Request-StatiBeschreibungDer Request wurde erfolgreich verarbeitet.Der Request konnte auf Gr<strong>und</strong> eines Fehlers im Zero-Copy-Layer nichtverarbeitet werden (nErrorCode enthält einen Fehlercode).Der Request konnte auf Gr<strong>und</strong> eines Fehlers im Netzwerk-Layer nichtverarbeitet werden (nErrorCode enthält einen Fehlercode).Der Request ist noch nicht vollständig bearbeitet.5.3.3 Die obere Schnittstelle des Netzwerk-LayersDie obere Schnittstelle des Netzwerk-Layers muss folgende Funktionen zur Verfügung stellen. Sieist in der Datei NWLINTERFACE.H definiert. Es müssen alle Funktionen implementiert werden. Siekönnen je nach Netzwerktechnologie jedoch leer sein.ZCL_STATUS NWL_Init();Initialisiert den Netzwerk-Layer, dessen Datenstrukturen <strong>und</strong> die eventuell darunterliegendenLayer. Zurückgegeben wird der Status der Initialisierung (ZCL_SUCCESS oder ZCL_NW_ERROR)<strong>und</strong> im Fehlerfall ein Fehlercode. Die Funktion wird vom Zero-Copy-Layer als erste Funktionaufgerufen.ZCL_STATUS NWL_Exit();Diese Funktion ist das Gegenstück zu NWL_Init. Sie bringt das Netzwerk in einen Gr<strong>und</strong>zustandzurück <strong>und</strong> baut alle internen Datenstrukturen ab. Die Funktion wird vom Zero-Copy-Layer alsletzte Funktion aufgerufen.Diplomarbeit <strong>von</strong> Roman Roth Seite 41


Institut für ComputersystemeETH Zürichchar* NWL_NetworkName();Diese Funktion gibt einen Pointer auf eine Zeichenkette zurück, die die durch den Layerverwendete Netzwerktechnologie beschreibt, z.B. „Myrinet GM“.BOOL NWL_NeedPolling();Ist der durch diese Funktion zurückgegebene Wert TRUE, dann ist Polling notwendig. Das heisst,die Applikation muss regelmässig via Zero-Copy-Layer prüfen, ob nicht-blockierend zuversendende Pakete verschickt, oder ob neue Pakete empfangen wurden (sieheNWL_CheckRequests <strong>und</strong> ZCL_CheckRequests).ZCL_STATUS NWL_ReadNodeInfo(int nNodeID, char* pcSection);Liest die Datei ZEROCOPY.CFG aus. Diese Datei enthält Sektionen <strong>von</strong> folgendem Format:[Node]HostName = Für jeden Knoten existiert eine Sektion in der Datei. Die Sektion ist mit [Node]gekennzeichnet, wobei eine Nummer darstellt. Der Zero-Copy-Layer liest alle Knotenbeginnend mit 0 <strong>und</strong> aufsteigend, bis eine ID nicht gef<strong>und</strong>en wurde. HostName = wirdebenfalls vom Zero-Copy-Layer gelesen. Für jeden Netzwerk-Layer können weitere Informationenhinzugefügt werden, die der Netzwerk-Layer in der Funktion NWL_ReadNodeInfo zu lesen hat.Gelesen wird mit den folgenden Win32-Funktionen:GetPrivateProfileInt(pcSection, , , ".\\zerocopy.cfg");GetPrivateProfileString(pcSection, , , cBuffer, nBufferSize,".\\zerocopy.cfg"); ist dabei der Schlüssel des zu lesenden Eintrags. ein Standardwert, derverwendet werden soll, wenn der Eintrag nicht gef<strong>und</strong>en wurde.Dies ist ein Beispiel einer gültigen Sektion in ZEROCOPY.CFG. SocketIP <strong>und</strong> SocketPort sindspezielle Einträge für den Socket-Netzwerk-Layer:[Node3]HostName = CS-ZZ13SocketIP = 129.132.13.221SocketPort = 800ZCL_STATUS NWL_Connect(int nNodeID);Falls die verwendete Netzwerktechnologie verbindungsorientiert arbeitet, dann müssen dieVerbindungen in dieser Funktion aufgebaut werden. Der Funktion wird die ID des Knotensübergeben, zu dem eine Verbindung gewünscht wird. Falls für den Verbindungsaufbau weitereInformationen (z.B. IP-Adresse) notwendig sind, dann müssen diese in der Datei ZEROCOPY.CFGenthalten sein (siehe NWL_ReadNodeInfo).ZCL_STATUS NWL_Disconnect(int nNodeID);Aufgebaute Verbindungen müssen in dieser Funktion wieder abgebaut werden.int NWL_SendAsync(PZCL_REQUEST pRequest);Asynchrones Versenden <strong>von</strong> Paketen wird in dieser Funktion implementiert. Wird der Requestsofort blockierend versendet, dann muss die Funktion den Status des Requests auf ZCL_SUCCESSsetzen <strong>und</strong> ZCL_SUCCESS zurückgeben. Im Fehlerfall wird der Status des Request aufZCL_NW_ERROR <strong>und</strong> ein Fehlercode gesetzt <strong>und</strong> ZCL_ERROR zurückgegeben. Wird der Requestnicht-blockierend verschickt, dann gibt die Funktion ZCL_PENDING zurück.int NWL_SendSync(PZCL_REQUEST pRequest);Implementiert das Versenden <strong>von</strong> synchronen Requests. Das Verhalten ist das selbe wie beiNWL_SendAsync.Seite 42Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersystemevoid NWL_CheckRequests();Falls NWL_NeedPolling den Wert TRUE zurückliefert, muss die Applikation regelmässig dieFunktion ZCL_CheckRequests aufrufen, die ihrerseits diese Funktion aufruft. Diese Funktionprüft, ob nicht-blockierende Requests verschickt, <strong>und</strong> ob neue Requests empfangen wurden. Siewird diese Requests über die untere Schnittstelle des Zero-Copy-Layers nach oben weiterreichen(siehe ZCL_HandleSendDone, ZCL_HandleSyncRecv, ZCL_HandleAsyncRecv <strong>und</strong>ZCL_HandleErrorRecv).void* NWL_Alloc(int nSize);Wird vom Zero-Copy-Layer verwendet, um Speicher für Async-Requests zu allozieren. DieseFunktion kann ein normales malloc enthalten. Falls <strong>von</strong> der Netzwerktechnologie jedochspezieller Speicher gebraucht oder zur Verfügung gestellt wird, dann kann diese Funktionspeziellen Code enthalten.void NWL_Free(void* pBuffer);Gibt durch NWL_Alloc allozierter Speicher wieder frei.void NWL_Register<strong>Memory</strong>(void* pBuffer, int nSize);Innerhalb <strong>von</strong> ZCL_RecvSync wird diese Funktion aufgerufen, um dem Netzwerk-Layer dieMöglichkeit zu geben, den Speicherbereich für den Empfang vorzubereiten, z.B. durch Locken desSpeicherbereichs.void NWL_Deregister<strong>Memory</strong>(void* pBuffer, int nSize);Nach dem Empfang, innerhalb der ZCL_HandleSyncRecv, wird diese Funktion aufgerufen, umeinen mit NWL_Register<strong>Memory</strong> behandelten Speicherbereich wieder in den ursprünglichenZustand zurückzusetzen.5.3.4 Die untere Schnittstelle des Zero-Copy-LayersDie untere Schnittstelle des Zero-Copy-Layers ist in der Datei ZCLLOWER.H definiert. Sie kann vomNetzwerk-Layer verwendet werden.void ZCL_HandleAsyncRecv(PZCL_REQUEST pRequest);Wird vom Netzwerk-Layer ein Async-Request empfangen, dann kann dieser Request durch dieseFunktion an den Zero-Copy-Layer weitergereicht werden. Der Status des Requests mussZCL_SUCCESS sein.void ZCL_HandleSyncRecv(void* pBuffer, int nLen);Wird vom Netzwerk-Layer ein Sync-Request empfangen, dann kann mit dieser Funktion dem Zero-Copy-Layer mitgeteilt werden, wohin (pBuffer) der Inhalt des Requests empfangen wurde <strong>und</strong>wie lang (nLen) der Inhalt ist.void ZCL_HandleSendDone(PZCL_REQUEST pRequest);Wenn eine der Funktionen NWL_SendAsync oder NWL_SendSync mit ZCL_PENDING zurückkehrt,dann wird nicht-blockierend versendet. Wenn das Senden beendet ist, muss der Netzwerk-Layerdiese Funktion aufrufen. Der Status des Requests muss zuvor entweder auf ZCL_SUCCESS oderim Fehlerfall auf ZCL_NW_ERROR gesetzt sein.void ZCL_HandleErrorRecv(PZCL_STATUS pStatus);Falls während dem Empfangen im Netzwerk-Layer ein Fehler auftritt, kann der entsprechendeStatus (ZCL_NW_ERROR) <strong>und</strong> Fehlercode dem Zero-Copy-Layer weitergereicht werden.Diplomarbeit <strong>von</strong> Roman Roth Seite 43


Institut für ComputersystemeETH ZürichBOOL ZCL_Protect<strong>Memory</strong>(void* pBuffer, int nLen);Diese Funktion ist wichtig für Netzwerk-Layer, die nicht mit DMA arbeiten. Da Applikationen wieOpenMP die Zugriffsrechte <strong>von</strong> Speicherseiten manipulieren, muss der Netzwerk-Layer diese Seitevor dem Empfangen mit Schreibrechten versehen. Das hätte jedoch zur Folge, dass die Threadsder Applikation auf die Seiten zugreifen könnten. Um das zu verhindern, müssen alle Threadsvorübergehend schlafen gelegt werden (siehe auch ZCL_RegisterThread). Die Funktion gibtTRUE zurück, wenn Threads schlafen gelegt werden mussten, ansonsten FALSE.BOOL ZCL_Unprotect<strong>Memory</strong>();Setzt die Zugriffsrechte der Speicherseiten zurück auf den alten Wert <strong>und</strong> weckt alle schlafengelegten Threads wieder auf. Die Funktion gibt TRUE zurück, wenn Threads aufgeweckt wurden,ansonsten FALSE (siehe auch ZCL_Protect<strong>Memory</strong>).PZCL_REQUEST ZCL_GetAsyncRequest(int nType);Wird vom Netzwerk-Layer ein Async-Request benötigt, dann kann er über diese Funktion einenanfordern. nType muss entweder ZCL_ASYNC_RECV oder ZCL_ASYNC_SEND sein.int ZCL_ReleaseAsyncRequest(PZCL_REQUEST pRequest);Wird vom Netzwerk-Layer ein Async-Request, den er mit ZCL_GetAsyncRequest angeforderthat, nicht mehr benötigt, dann kann er mit dieser Funktion wieder zurückgegeben werden. Umdauerndes Allozieren <strong>und</strong> Freigeben <strong>von</strong> Speicherbereichen zu verhindern, wird eine gewisseAnzahl <strong>von</strong> Async-Requests rezykliert.PZCL_REQUEST ZCL_GetSyncRequest(int nType);Wird vom Netzwerk-Layer ein Sync-Request benötigt, dann kann er über diese Funktion einenanfordern. Der Typ muss entweder ZCL_SYNC_RECV oder ZCL_SYNC_SEND sein.int ZCL_ReleaseSyncRequest(PZCL_REQUEST pRequest);Wird vom Netzwerk-Layer ein Sync-Request, den er mit ZCL_GetSyncRequest angefordert hat,nicht mehr benötigt, dann wird er mit dieser Funktion wieder zurückgegeben. Auch dieSpeicherbereiche <strong>von</strong> Sync-Requests werden rezykliert.int ZCL_GetLocalNodeID();Nachdem NWL_ReadNodeInfo aufgerufen wurde, kann mit dieser Funktion die lokale Knoten-IDgelesen werden.int ZCL_GetNumNodes();Gibt die Anzahl in der Konfigurationsdatei definierten Knoten zurück.char* ZCL_GetHostName(int nNodeID);Gibt den Rechnernamen eines bestimmten Knotens zurück.5.3.5 Die obere Schnittstelle des Zero-Copy-LayersDie obere Schnittstelle des Zero-Copy-Layers wird den Applikationen zur Verfügung gestellt. Sie istin der Datei ZCLUPPER.H definiert.ZCL_STATUS ZCL_Init(BOOL fForwardReadyRequests,PZCL_REQUEST_CALL_BACK pCallBack);Eine Applikation, die den Zero-Copy-Layer verwenden will, muss diese Funktion als erstesAufrufen. Sie initialisiert den Layer. Mit fForwardReadyRequests kann entschieden werden, obdie normale Variante (FALSE) oder die spezielle OpenMP-Variante (TRUE) verwendet werden soll.Seite 44Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeZudem muss dieser Routine ein Funktions-Pointer übergeben werden, der auf die Call-Back-Funktion der Applikation zeigt. Diese Call-Back-Funktion hat folgende Signatur:int CallBackFunc(PZCL_REQUEST pRequest);Der Name der Funktion spielt natürlich keine Rolle. Die Funktion gibt ZCL_SUCCESS zurück, wennsie den Request verarbeitet hat, ZCL_ERROR falls nicht. Im ersten Fall geht der Request in denBesitz der Applikation über. Sie ist daher auch Verantwortlich, dass der Request irgendwanngelöscht wird (siehe ZCL_ReleaseSyncRequest, ZCL_ReleaseAsyncRequest,ZCL_ReleaseErrorRequest).ZCL_STATUS ZCL_Exit();Als letzte Funktion hat die Applikation diese aufzurufen, um alle aufgebauten Datenstrukturensauber abbauen zu können.ZCL_STATUS ZCL_ReadNodeInfo();Wie bereits bei NWL_ReadNodeInfo erwähnt, sind die Informationen zu den Knoten in der DateiZEROCOPY.CFG untergebracht. Diese Funktion öffnet diese Konfigurationsdatei <strong>und</strong> liest daraus fürjeden Knoten den Rechnernamen. Danach wird für jeden Knoten die FunktionNWL_ReadNodeInfo aufgerufen, welche netzwerkabhängige Informationen lesen kann.Auf Gr<strong>und</strong> des lokalen Rechnernamens wird die lokale Knoten-ID ermittelt. Falls in derKonfigurationsdatei mehrere Einträge mit dem lokalen Rechnernamen übereinstimmen, wird derBenutzer nach dem gewünschten Knoten gefragt.int ZCL_GetLocalNodeID();Nachdem ZCL_ReadNodeInfo aufgerufen wurde, kann mit dieser Funktion die lokale Knoten-IDgelesen werden.int ZCL_GetNumNodes();Gibt die Anzahl in der Konfigurationsdatei definierten Knoten zurück.int ZCL_RegisterThread(HANDLE hThread);Falls die Applikation mit Speicherbereichen arbeitet, die nicht mit Schreibzugriffsrechten versehensind <strong>und</strong> in solchen Speicher Daten empfangen will, dann müssen alle Threads ausser demKommunikationsthread beim Zero-Copy-Layer registriert werden (siehe auchZCL_Protect<strong>Memory</strong>).BOOL ZCL_NeedPolling();Über diese Funktion kann die Applikation abfragen, ob Polling über ZCL_CheckRequestsnotwendig ist, oder ob die Call-Back-Funktion „automatisch“ aufgerufen wird. Falls statt PollingAPCs verwendet werden, dann muss der Kommunikationsthread der Applikation regelmässig dieWin32-Funktionen WaitForSingleObjectEx oder SleepEx aufrufen.char* ZCL_NetworkLayer();Gibt eine Informationszeichenkette zurück, die den Netzwerk-Layer beschreibt: z.B. „Myricom GM“.ZCL_STATUS ZCL_Connect(int nNodeID);Egal welchen Netzwerk-Layer verwendet wird, diese Schnittstelle des Zero-Copy-Layers istverbindungsorientiert. Das heisst, vor dem ersten Senden oder Empfangen muss zu allengewünschten Knoten eine Verbindung aufgebaut werden. Die übergebene Knoten-ID musszwischen 0 <strong>und</strong> ZCL_GetNumNodes-1 liegen <strong>und</strong> darf nicht die eigene ID sein.Diplomarbeit <strong>von</strong> Roman Roth Seite 45


Institut für ComputersystemeETH ZürichZCL_STATUS ZCL_Disconnect(int nNodeID);Vor dem Beenden der Applikation müssen alle mit ZCL_Conntect aufgebauten Verbindungenwieder abgebaut werden.int ZCL_SendSync(PZCL_REQUEST pRequest);Sendet den durch einen Sync-Request beschriebenen Speicherbereich an einenEmpfängerknoten. pRequest muss ein gültiger Sync-Request sein, wobei pSourceBuffer aufden zu versenden Speicherbereich zeigt <strong>und</strong> nLen die Länge des Bereichs angibt.Diese Funktion gibt entweder ZCL_SUCCESS zurück, was bedeutet, dass der Request erfolgreichversendet wurde. ZCL_PENDING heisst, dass der Request noch nicht verschickt werden konnte.Der Request geht dann in den Besitz des Zero-Copy-Layers über. Das heisst, die Applikation darfihn so lange nicht mehr verändern, bis sie ihn über die Call-Back-Funktion zurück bekommt.ZCL_ERROR wird im Fehlerfall zurückgegeben.int ZCL_SendAsync(PZCL_REQUEST pRequest);Sendet den Async-Request an einen Empfängerknoten. pRequest muss ein gültiger Async-Request sein. Es werden der Header des Requests plus die ersten nLen-Bytes des Bodiesversendet.Die zurückgegebenen Werte entsprechen denen <strong>von</strong> ZCL_SendSync.int ZCL_RecvSync(PZCL_REQUEST pRequest);Bestätigt die Empfangsbereitschaft. pTargetBuffer des Sync-Requests muss auf einen gültigenSpeicherbereich zeigen, in den Empfangen werden soll. nLen gibt die Grösse desEmpfangspuffers an.Zurückgegeben wird entweder ZCL_PENDING oder im Fehlerfall ZCL_ERROR.int ZCL_CheckRequests();Falls die Funktion ZCL_NeedPolling TRUE retourniert, muss die Applikation regelmässig dieseFunktion aufrufen. Für jeden beendeten Sendevorgang (der ZCL_PENDING zurücklieferte) <strong>und</strong> fürjeden abgeschlossenen Empfangsvorgang wird einmal die Call-Back-Funktion der Applikationaufgerufen.Die Funktion liefert die Anzahl der Aufrufe der Call-Back-Funktion zurück.PZCL_REQUEST ZCL_GetAsyncRequest(int nType);Mittels dieser Funktion kann eine Applikation beim Zero-Copy-Layer einen Async-Requestanfordern. Der Typ kann entweder ZCL_ASYNC_SEND oder ZCL_ASYNC_RECV sein.void ZCL_ReleaseAsyncRequest(PZCL_REQUEST pRequest);Ein Async-Request muss mit dieser Funktion bei Nichtgebrauch wieder zurückgegeben werden.Um ständiges Allozieren <strong>und</strong> Freigeben <strong>von</strong> Speicher zu vermeiden, wird eine gewisse Anzahl <strong>von</strong>Requests rezykliert.PZCL_REQUEST ZCL_GetSyncRequest(int nType);Mittels dieser Funktion kann eine Applikation beim Zero-Copy-Layer einen Sync-Requestanfordern. Der Typ kann entweder ZCL_SYNC_SEND oder ZCL_SYNC_RECV sein.void ZCL_ReleaseSyncRequest(PZCL_REQUEST pRequest);Ein Sync-Request muss mit dieser Funktion bei Nichtgebrauch wieder zurückgegeben werden. Umständiges Allozieren <strong>und</strong> Freigeben <strong>von</strong> Speicher zu vermeiden, wird eine gewisse Anzahl <strong>von</strong>Requests rezykliert.Seite 46Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersystemevoid ZCL_ReleaseErrorRequest(PZCL_REQUEST pRequest);Ein in der Call-Back-Funktion erhaltener Request vom Type ZCL_ERROR_RECV kann mit dieserFunktion dem Zero-Copy-Layer zurückgegeben werden.void ZCL_GetProfileInfo(PZCL_PROFILE_INFO pInfo);Der Zero-Copy-Layer merkt sich die Anzahl versendeter <strong>und</strong> empfangener Requests in einerStruktur mit folgendem, selbsterklärendem Aufbau:struct ZCL_PROFILE_INFO{// number of requests sent or receivedint nSendSync;int nSendAsync;int nSendReady;int nRecvSync;int nRecvAsync;int nRecvReady;}// number of bytes included in the requestsLONGLONG nSendSyncBytes;LONGLONG nSendAsyncBytes;LONGLONG nSendReadyBytes;LONGLONG nRecvSyncBytes;LONGLONG nRecvAsyncBytes;LONGLONG nRecvReadyBytes;Durch den Aufruf dieser Funktion wird der aktuelle Stand dieser Struktur kopiert. pInfo muss alsoauf eine gültige Instanz <strong>von</strong> ZCL_PROFILE_INFO zeigen.void ZCL_ResetProfileInfo();Mittels dieser Funktion werden alle Werte der Profiling-Struktur auf Null zurückgesetzt.5.4 Die Implementation der beiden LayerDie Implementation des Zero-Copy-Layers enthält beide Versionen (gemäss Abbildungen 5.1 <strong>und</strong>5.9). Folgende Dateien enthalten den nötigen Source-Code:DateiZEROCOPYLAYER.CZEROCOPYLAYER.HZCLUPPER.HZCLLOWER.HZCLHELPER.HZCLINTERNAL.HRESOURCE.HInhaltSource-Code des Zero-Copy-LayersHeader-Datei mit allgemeinen DefinitionenDefinition der oberen SchnittstelleDefinition der unteren SchnittstelleDefinitionen <strong>von</strong> allgemeinen Routinen (z.B. Listenverwaltung)Definitionen <strong>von</strong> ausschliesslich intern verwendeten Routinen <strong>und</strong>DatenstrukturenHeader-Datei der RessourcenZEROCOPYLAYER.RC RessourcenTab 5.3: Source-Dateien des Zero-Copy-LayersDiplomarbeit <strong>von</strong> Roman Roth Seite 47


Institut für ComputersystemeETH ZürichDer Zero-Copy-Layer <strong>und</strong> jeweils ein Netzwerk-Layersind in einer Bibliothek namens ZEROCOPY.DLLenthalten. Da es vom Netzwerk-Layer mehrere Versiongibt, gibt es auch mehrere Version der DLL. DieEigenschaften der DLL geben Auskunft über denNetzwerk-Layer (siehe Abbildung 5.11).Sämtlicher Source der beiden Layer ist mit MicrosoftVisual C++ 5.0 erstellt, kompiliert <strong>und</strong> gelinkt worden.Alle nötigen Einstellung für Kompilation <strong>und</strong> Linkensind im Projekt mit dem Namen ZEROCOPY.DSPenthalten.Entschieden, welcher Netzwerk-Layer beim Erstellender DLL verwendet werden soll, wird in der DateiNWLSELECT.H. Für jede Version des Layers enthältdiese Datei ein auskommentiertes Define. Es sollte vordem Kompilieren jeweils das Define aktiviert werden,dessen Layer man integrieren möchte.Die Netzwerk-Layer werden in den folgenden Source-Dateien definiert <strong>und</strong> implementiert:Abb. 5.11: Eigenschaften <strong>von</strong> ZEROCOPY.DLLDateiNWLSELECT.HNWLINTERFACE.HNWLSOCKPIPE.HNWLSOCKPIPE.CNWLGM.HInhaltIn dieser Datei kann ausgewählt werden, welcher Netzwerk-Layer bei dernächsten Kompilation verwendet werden soll.Definiert die obere Schnittstelle des Netzwerk-LayersDefinitionen für den Socket- <strong>und</strong> Pipe-Netzwerk-LayerSource-Code des Socket- <strong>und</strong> Pipe-Netzwerk-LayersDefinitionen für den GM-Netzwerk-LayerNWLGM.CSource-Code des GM-Netzwerk-LayersTab 5.4: Source-Dateien der Netzwerk-LayerWie die obige Tabelle zeigt, gibt es drei verschiedene Netzwerk-Layer:ŒŒŒNamed-Pipes: Dieser Layer benutzt Named-Pipes für die Kommunikation <strong>und</strong> ist damitbesonders für lokale Tests geeignet. Bei jedem NWL_Connect wird eine Pipe zwischen zweiKnoten aufgebaut. Der Named-Pipe-Netzwerk-Layer benutzt APCs, es ist also kein Pollennotwendig.Windows Sockets: Der Socket-Netzwerk-Layer ist vom Code her weitgehend identisch mitdem Named-Pipe-Netzwerk-Layer. Es wird ebenfalls mit APCs gearbeitet. Unterschiedlich isteigentlich nur der Verbindungsaufbau. Wird dieser Layer verwendet, dann ist die Reihenfolgedes Aufbaus der Verbindungen wesentlich:for(i = nNumNodes-1; i > nLocalNodeID; i--)ZCL_Connect(i);for(i = 0; i < nLocalNodeID; i++)ZCL_Connect(i);Myricom GM: Dieser Layer ist vorbereitet, jedoch nicht vollständig lauffähig. Die zeitlich (zu)späte Freigabe des neusten GM-Beta-Release (0.18) durch Myricom, zahlreiche,<strong>und</strong>okumentierte Randbedingungen <strong>und</strong> die unvollständige Freigabe <strong>von</strong> Kernel-Device-Objekten durch die GM-Treiber haben eine erfolgreiche Implementation im Rahmen dieserArbeit verhindert.Die beiden ersten Netzwerk-Layer-Implementationen dürften etwas überraschen, da sowohlNamed-Pipes wie auch Sockets überhaupt nichts mit Zero-Copy zu tun haben. Implementiertwurden diese aus zweierlei Gründen: Erstens boten diese eine stabile <strong>und</strong> einfache Möglichkeit,den Zero-Copy-Layer <strong>und</strong> die beiden Prototypen lokal <strong>und</strong> über Netzwerk zu testen. Zweitenserlauben vor allem die Sockets den Zero-Copy-Layer <strong>und</strong> die Prototypen über ein fast beliebigesNetzwerk (falls ein Treiber vorhanden ist) zu verwenden.Seite 48Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme6 Der OpenMP-PrototypOpenMP [2] ist ein Standard, der bestehende Compiler um ein API erweitert. Dieses API enthältdie notwendigen Funktionen <strong>und</strong> Direktiven, um ein Programm auf einem Mehr-Prozessor-<strong>Shared</strong>-<strong>Memory</strong>-System laufen zu lassen. Das API übernimmt das Management des <strong>Shared</strong>-<strong>Memory</strong>, dieAufteilung des Programms an verschiedene, parallel arbeitende Prozessoren <strong>und</strong> dieSynchronisation des Programmablaufs.Da sich diese Diplomarbeit auf <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong>-Systeme stützt, müssen diesem APIFunktionalitäten hinterlegt werden, die im <strong>Shared</strong>-<strong>Memory</strong>-System weitgehend <strong>von</strong> der Hardwareübernommen würden. Dabei standen vor allem Techniken, wie sie TreadMarks [4] verwendet, imVordergr<strong>und</strong>.Der Prototyp implementiert den wichtigsten Teil des OpenMP-APIs. Das API, wie es in OpenMPFortran Application Program Interface 1.0 [2] definiert wird, ist eine Fortran-Compiler-Erweiterung.Da für die Implementation Microsoft Visual C++ 5.0 zur Verfügung stand, versteht es sich <strong>von</strong>selbst, dass eine Compilererweiterung nicht in Frage kommt. Es wurde deshalb versucht, mittelsPrecompiler-Direktiven (in OPENMP.H) <strong>und</strong> einer Funktionsbibliothek (OMPLIB.DLL) dieausgewählten OpenMP-Direktiven so gut als möglich nachzubilden.Das folgende Unterkapitel geht nun ausführlich auf die Konzepte, die im Prototypen zurAnwendung kommen, ein. Danach wird das implementierte API dokumentiert.6.1 Die Konzepte des OpenMP-PrototypsDen Konzepten, die im OpenMP-Prototyp zur Anwendung gekommen sind, liegen zweiBasiskonzepte zu Gr<strong>und</strong>e:ŒŒWichtigstes Konzept ist die Zero-Copy-Kommunikation. Das heisst im wesentlichen, dass soviel Kommunikation wie möglich ohne zusätzliches Kopieren, sei es im Sender oder imEmpfänger, auskommen sollte. Insbesondere das Verschieben <strong>von</strong> Speicherseiten im<strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong> sollte ohne Kopieren <strong>von</strong> statten gehen.Gr<strong>und</strong>legend sollten auch die Techniken <strong>von</strong> TreadMarks [4], insbesondere Multiple-Writer <strong>und</strong>Lazy-Release-Consistency, sein.Wie die folgenden Ausführungen zeigen, lassen sich diese Konzepte nicht vollständig unter einenHut bringen.6.1.1 KommunikationAnders als OpenMP baut dieser Prototyp auf einem Netzwerk <strong>von</strong> Workstations auf. Eine derwichtigsten Komponenten eines solchen verteilten Systems ist die Kommunikation. Die vorgehendausführlich dokumentierten Studien zur Zero-Copy-Kommunikation <strong>und</strong> der Zero-Copy-Layer alsResultat daraus, kommen in diesem Prototypen zu ihrem ersten praktischen Einsatz. Der Prototypwird also auf die obere Schnittstelle (ZCLUPPER.H) des Zero-Copy-Layers aufgebaut. Damit lässtsich der Prototyp über alle Netzwerke betreiben, die durch den Zero-Copy-Layer bzw. durch einerseiner Netzwerk-Layer unterstützt werden.6.1.2 ThreadingOpenMP versucht die zur Verfügung stehende Rechenleistung zu nutzen, indem parallelisierbareProgrammteile <strong>von</strong> mehreren Prozessoren abgearbeitet werden. Dazu werden dynamisch Threadsgestartet <strong>und</strong> beendet.Eine solche Dynamik hätte den Rahmen eines Prototypen gesprengt, zumal die Threads nicht aufeinem System, sondern auf vielen, verteilten <strong>und</strong> vernetzten Systemen hätten zur Ausführunggebracht werden müssen. Ein NT-Service, der auf jedem System im Hintergr<strong>und</strong> läuft <strong>und</strong> beiBedarf Prozesse <strong>und</strong> Threads startet, wäre die aufwendige Konsequenz gewesen.Der Benutzer hat daher auf jeder Maschine (im folgenden Knoten genannt) das selbe Programmzu starten. Da diese Maschinen über mehrere Prozessoren verfügen können, ist es sinnvoll, dassDiplomarbeit <strong>von</strong> Roman Roth Seite 49


Institut für ComputersystemeETH Zürichmehrere Threads pro Knoten parallel laufen können. Lokal implementiert der Prototyp also ein<strong>Shared</strong>-<strong>Memory</strong>-System, über die Grenzen eines Knotens hinweg jedoch ein <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong>-System.Nach dem Starten eines OpenMP-Prozesses wird der Hauptthread das System initialisieren, dieeigentlichen OpenMP-Threads starten <strong>und</strong> selber als Kommunikations-Thread agieren.Knoten 0Knoten 1OpenMPThread 0OpenMPThread 1OpenMPThread 2OpenMPThread 3OpenMPThread 4OpenMPThread 5Kommunikations-ThreadKommunikations-ThreadZero-Copy-LayerZero-Copy-LayerAbb. 6.1: Thread-Modell des OpenMP-PrototypenWährend der Kommunikations-Thread aufbauend auf dem Zero-Copy-Layer für sämtlicheKommunikation zwischen den Knoten (nicht zwischen den Threads) verantwortlich ist, führen dieOpenMP-Threads das OpenMP-Programm aus, <strong>und</strong> zwar alle das selbe. Gesteuert <strong>von</strong> Direktivenim Programm werden einzelne Programmteile jedoch nicht <strong>von</strong> allen Threads ausgeführt, sonderngezielt auf die einzelnen Threads verteilt. Welcher Thread welche Teile des Programms ausführt,ist im Programm durch die OpenMP-Direktiven fixiert. Dynamische Verteilung, die zusätzlicheKommunikation zur Folge hätte, wurde bewusst nicht in den Prototypen aufgenommen.Die OpenMP-Threads werden durch zwei Numerierungssysteme identifiziert. Innerhalb einesKnotens hat jeder Thread eine lokale Nummer, die <strong>von</strong> Null aufwärts gezählt wird. Der Thread mitder lokalen Nummer 0 wird als lokaler Master bezeichnet.Global werden die Nummern zwischen 0 <strong>und</strong> N-1 verteilt, wobei N die Summe der Threads aufallen Knoten ist. Beim Starten der OpenMP-Applikation auf einem Knoten werden dieser so vielefortlaufende Nummern fest zugeteilt, wie diese OpenMP-Threads haben soll. Analog zum lokalenMaster trägt der Thread mit der globalen Nummer 0 die Bezeichnung globaler Master.Auf Gr<strong>und</strong> der lokalen <strong>und</strong> globalen Nummer bestimmen die Threads, welche Teile desProgramms sie abarbeiten müssen.6.1.3 <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong>Noch wichtiger <strong>und</strong> wesentlich komplexer als das Thread-Modell ist die Speicherverwaltung desOpenMP-Prototypen. Diese basiert auf dem Konzept des <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong>. AlleOpenMP-Threads sollen einen einzigen, grossen, globalen Speicher zur Verfügung haben, auf densie quasi parallel zugreifen können.Physisch gibt es natürlich keinen gemeinsamen Speicher. Jeder Knoten hat zu jedem Zeitpunkt nureinen Teil des konzeptuell gemeinsamen Speichers in seinem lokalen, physischen Speicher – einWorking-Set sozusagen. Wird eine Speicherseite ausserhalb dieses Sets adressiert, dannentspricht das einem Page-Fault. Dieser kann aufgelöst werden, indem ermittelt wird, welcherKnoten eine gültige Kopie der Seite hat <strong>und</strong> diese kommuniziert wird.Seite 50Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeWelche Probleme hinter einem solchen <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong>-System stecken, wird schnellklar: Was passiert, wenn zwei Knoten eine gültige Kopie einer Seite haben <strong>und</strong> diese gleichzeitigbeschreiben? Wie wird ermittelt, wer eine gültige Kopie besitzt? Wann wird mitgeteilt, dass sich derInhalt einer Seite verändert hat <strong>und</strong> die eigene Kopie der Seite somit ungültig ist? HochgradigeParallelität, relativ langsame Kommunikation <strong>und</strong> grobgranulare Unterteilung des Speichers inSeiten (<strong>von</strong> 4 kB auf i386-Systemen) lassen die Implementation zu einer wahren Herausforderungwerden. Folgende Unterkapitel sollen die Mechanismen <strong>und</strong> Strukturen, die im Prototypeneingebaut wurden, aufzeigen:6.1.3.1 Initialisieren des <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong>Der <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong> (im folgenden nur noch SHARED-Speicher genannt) ist inWirklichkeit ein grosser, virtueller Speicherbereich, der in jedem Knoten beim Initialisieren desSystems reserviert wird. Alle Threads eines Knotens teilen sich einen SHARED-Speicher. Nur einkleiner Teil dieses reservierten, virtuellen Speicherbereichs wird zu Beginn tatsächlich mitphysischem RAM oder Diskspeicher hinterlegt (in der Windows NT-Terminologie heisst dascommited), der Rest bleibt lediglich reserviert.In einer Liste wird für jede Speicherseite im SHARED-Speicher deren Zustand festgehalten.Folgende Zustände sind möglich:Status Beschreibung ZugriffsrechteOMP_PAGE_RESERVED Die Seite ist reserviert, es ist aber kein no accessphysischer Speicher damit verb<strong>und</strong>en.OMP_PAGE_VALIDOMP_PAGE_INVALIDDie Seite ist gültig. Sie enthält gültigeInformation, die gelesen (<strong>und</strong> nach demÜbergang in den Modified-Status auchbeschrieben) werden kann.Die Seite ist ungültig. Bevor gelesen odergeschrieben werden kann, muss eine gültigeKopie <strong>von</strong> einem anderen Knoten angefordertwerden.read onlyno accessOMP_PAGE_MODIFIED Die Seite ist gültig <strong>und</strong> wurde modifiziert. read / writeOMP_PAGE_SENDOMP_PAGE_RECVDie Seite ist gültig, wird aber gerade an einenanderen Knoten gesendet <strong>und</strong> kann dahertemporär nur lesend zugegriffen werden.Die Seite ist ungültig, wird aber gerade <strong>von</strong>einem anderen Knoten angefordert.Tab. 6.1: Zustände der Speicherseiten im SHARED-SpeicherBetrachtet man das Diagramm derZustandsübergänge in Abbildung 6.2, danndürfte eventuell der direkte Übergang <strong>von</strong>Reserved in Valid erstaunen. Dies hat miteiner Eigenschaft <strong>von</strong> Windows NT zu tun:Wann immer im User-Mode eineSpeicherseite angefordert wird, dann istdiese aus Sicherheitsgründen mit Nulleninitialisiert. Das heisst, die Speicherseitenin allen Knoten haben nach dem Commitden selben – also auch gültigen – Inhalt.Invalidread onlyno accessModifiedWinNT: Sowohl für das Reservieren wieauch für das Hinterlegen mitSpeicher ist unter Windows NT Abb. 6.2: Zustandsübergänge einer SpeicherseitendieWin32-FunktionVirtualAlloc zuständig. Gegenstück dazu ist die Funktion VirtualFree. Eineweitere wichtige Funktion ist VirtualProtect, welche die Zugriffsrechte einer odermehrerer Seiten setzt. Diese drei Funktionen <strong>und</strong> ein Exception-Handler (__try {...}RecvRecv inprogressDecommitBarrierReservedValidSendCommitWriteSend inprogressBarrierDiplomarbeit <strong>von</strong> Roman Roth Seite 51


Institut für ComputersystemeETH Zürich__except(GetExceptionInformation()) {...}) genügen im wesentlichen, umein <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong> System unter NT auf die Beine zu stellen.6.1.3.2 Allozieren <strong>von</strong> Speicherbereichen im SHARED-SpeicherTeile des initialisierten SHARED-Speichersmüssen nun den Threads zur Verfügung gestelltwerden. Dabei muss sichergestellt werden, dassalle Threads die selbe Sicht des Speichers haben.Das heisst im wesentlichen, dass auf allen Knotenalle Allokationen in der selben Reihenfolgedurchgeführt werden müssen (dafür ist derOpenMP-Programmierer zuständig), <strong>und</strong> dasstrotz mehrerer lokaler Threads, die selbeAllokation nur einmal ausgeführt wird.Mit OMP_ALLOC kann ein Programm Speicher(fast) beliebiger Grösse allozieren. Gr<strong>und</strong>sätzlichwird Speicher fortlaufend in eine Richtung alloziert<strong>und</strong>, falls nötig, weiterer physischer Speicher demreservierten Bereich hinterlegt (commited).OMP_ALLOC_NEW_PAGE alloziert nicht nurSpeicher, sondern garantiert auch, dass er amAnfang einer Speicherseite beginnt.Da pro Knoten mehrere Threads parallel arbeitenkönnen, muss dafür gesorgt werden, dass pro OMP_ALLOC nur ein Thread realer Speicher alloziert.Die anderen erhalten lediglich den Pointer auf den entsprechenden Speicherbereich zurück.Der aktuelle Allokationsstatus wirddurch eine zyklische, einfachverkettete Liste repräsentiert, derenElemente neben dem Pointer aufden allozierten Speicherbereichauch die Anzahl der lokalenThreads, die diese Allokation nochnicht ausgeführt haben, enthalten.Jeder Thread kennt das Element,das er für die nächste Allokationverwenden soll. Ist dieThreadanzahl gleich Null, dannhandelt es sich um eine neueAllokation, ansonsten wird nur dieSpeicheradresse zurückgegeben.Initial enthält diese Kette nur einElement mit der Threadanzahl Null.Pointer: 0x00000000Threads: 0ReservedCommitedOMP_ALLOC_NEW_PAGEunusedOMP_ALLOCOMP_ALLOCOMP_ALLOCAbb. 6.3: Der SHARED-SpeicherPointer: 0x3b800600Threads: 4Pointer: 0x00000000Threads: 0Pointer: 0x3b800400Threads: 3Pointer: 0x3b800000Threads: 1Thread 0 Thread 1 Thread 2 Thread 3 Thread 4Alloc(256)Alloc(512)Alloc(256)Abb. 6.4: Kette der pendenten AllokationenAlloc(512)Alloc(256)Alloc(1024)Alloc(512)Alloc(256)Mit jeder neuen Allokation kommt ein Element dazu, es sei denn, es existiert bereits ein Element,das inzwischen wieder die Threadanzahl Null erreicht hat.6.1.3.3 Allozierter Speicher existiert in einem ScopeWer mit der Allokation <strong>von</strong> Speicher zu tun hat, muss sich automatisch auch mit der Deallokationbefassen, schliesslich ist der Speicher eine begrenzte Ressource. In erster Linie stellt sich dieFrage, wer die Deallokation übernimmt: Gibt es eine explizite Deallokation durch den OpenMP-Programmierer? Oder übernimmt das System die Deallokation implizit?Die explizite Version hätte zur Folge, dass der SHARED-Speicher als Heap funktionieren müsste.Die Konsequenzen wären eine aufwendige Buchführung der benutzten bzw. freienSpeicherbereiche <strong>und</strong> eine zunehmende Fragmentierung des SHARED-Speichers.WinNT: Win32 bietet eine Reihe <strong>von</strong> Funktionen, die für den Programmierer einen oder mehrereHeaps implementieren <strong>und</strong> unterhalten. Mit HeapCreate bzw. HeapDestroy lässt sichein Heap erzeugen bzw. auflösen, mit HeapAlloc bzw. HeapFree kann Speicheralloziert bzw. dealloziert werden. Es gibt noch weitere Heap-Funktionen. Leider ist nichtSeite 52Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersystemedokumentiert, wo diese Heap-Funktionen die nötigen Verwaltungsstrukturen ablegen. Dasich für OpenMP diese Strukturen unter keinen Umständen im eigentlichen SHARED-Speicher befinden dürfen, wurde <strong>von</strong> der Verwendung dieser Funktionen abgesehen.Der OpenMP-Prototyp verwendet dieimplizite Variante: Der SHARED-Speicherfunktioniert dabei nicht als Heap, sondernals Stack. Jedes OMP_ALLOC reserviertSpeicher on top of stack <strong>und</strong> lässt denStack wachsen.OMP_DOOMP_END_DOOMP_INIT/OMP_EXIT,OMP_PARALLEL/OMP_END_PARALLEL,OMP_DO/OMP_END_DO<strong>und</strong>OMP_PARALLELOMP_END_PARALLELOMP_SECTIONS/OMP_END_SECTIONSdefinieren sogenannte (geschachtelte)Scopes (Abbildung 6.5). Am Beginn jedesOMP_INITOMP_EXITScopes wird eine neuer Stack-Frame Abb. 6.5: Stack-Frames verschiedener Scopesangelegt. Innerhalb des Scopes kann nunbeliebig Speicher alloziert werden. Am Ende des Scopes wird der aktuelle Stack Frame, dassheisst, aller innerhalb des Scopes allozierter Speicher, wieder freigegeben (decommited).6.1.3.4 Synchronisation des SHARED-SpeicherBevor die in Abbildung 6.2 dargestellten Zustandsübergänge beschrieben werden können, müssenan dieser Stelle einige Worte über Synchronisation verloren werden.Auf Gr<strong>und</strong> der geographischen Verteilung des Systems <strong>und</strong> den damit verb<strong>und</strong>enen (grossen)Latenzzeiten bei der Kommunikation zwischen den verschiedenen Knoten wäre es nicht effizient,jede einzelne Veränderung im SHARED-Speicher sofort an die anderen Knoten weiterzuleiten. Eswerden also mehrere Modifikationen zu einem Paket zusammengefasst <strong>und</strong> dann als ganzesverschickt. Genauer gesagt, werden im OpenMP-Programm implizit oder explizitSynchronisationspunkte, sogenannte Barriers, definiert. Sobald ein Thread an eine Barrier gelangt,wird dieser Schlafen gelegt. Sind alle Threads eines Knotens an einer Barrier angekommen, wirdder Kommunikations-Thread allen anderen Knoten mitteilen, welche Speicherseiten seine Threadsverändert haben. Wenn der Knoten <strong>von</strong> allen anderen die Veränderungen empfangen hat, kann ersich lokal errechnen, welcher Knoten welche gültigen Seiten besitzt. Erst dann werden die Threadsihre Arbeit wieder aufnehmen.Wie bei TreadMarks kommt also auch bei diesem Prototypen das sogenannte Lazy-Release-Consistency-Verfahren zum Zuge: Das heisst, bei der Synchronisation wird nicht der Inhalt derveränderten Speicherseiten kommuniziert, sondern nur eine Liste der Nummern aller modifiziertenSpeicherseiten. Erst wenn ein Thread auf eine Speicherseite zugreift, die ein anderer Thread vorder letzten Synchronisation verändert hat, wird diese angefordert.Dieses Verfahren ist jedoch mit äusserster Vorsicht zu geniessen: Auf Gr<strong>und</strong> derNetzwerktopologie kann ein Knoten die Barrier bereits verlassen <strong>und</strong> neue Page-Faults generierthaben, bevor die anderen Knoten alle Modifikationsmitteilungen erhalten haben. Treffen zu diesemZeitpunkt Page-Requests solcher „schneller“ Knoten ein, so müssen diese unbedingtzurückgehalten werden, bis der eigene Knoten die Barrier verlassen kann!Die bisherigen Ausführungen zur Synchronisation implizieren quasi, dass zwischen zwei Barriersnur immer ein Knoten auf eine Speicherseite schreibend zugreifen darf. Auf Gr<strong>und</strong> der grobenUnterteilung des Speichers in Speicherseiten (4kB!) kann das für den OpenMP-Programmierersehr problematisch werden, da er sicherstellen muss, dass die einzelnen Threads (aufverschiedenen Knoten) nur Variablen schreibend benutzen, die auch in verschiedenenSpeicherseiten liegen.In TreadMarks wurde ein Multiple-Writer-Verfahren verwirklicht, das mehreren Threads erlaubt, aufverschiedene Stellen der selben Seite zuzugreifen. Konkret wurde also die Granularität verkleinert.Für einen Thread, der auf eine ungültige Seite zugreift heisst das, dass er unter Umständen beimehreren Knoten nachfragen muss, welche Änderungen vorgenommen wurden, um aus all diesenInformationen wieder eine gültige Seite zusammenzustellen. Dieses Verfahren kann zwar diekommunizierte Datenmenge reduzieren, lässt sich jedoch auf keine Art <strong>und</strong> Weise mit der imDiplomarbeit <strong>von</strong> Roman Roth Seite 53


Institut für ComputersystemeETH ZürichPrototypen geforderten Zero-Copy-Kommunikation vereinbaren, da die Speicherseite bei denSendern auseinandergenommen <strong>und</strong> beim Empfänger wieder zusammengesetzt werden muss.Deshalb wurde bewusst auf diese Multiple-Writer-Funktion verzichtet. Dem Programmierer wurdejedoch mit OMP_ALLOC_NEW_PAGE eine Funktion in die Hand gegeben, mit der er die Plazierungder Variablen im SHARED-Speicher besser kontrollieren kann. Zusätzlich kontrolliert jeder Knotennach einer Barrier, ob ihm mehrere Knoten die Modifikation der selben Seite gemeldet haben <strong>und</strong>gibt in diesem Fall eine Fehlermeldung aus.Für die Synchronisierung des SHARED-Speichers sind zwei Listen notwendig, die hier kurzvorgestellt werden sollen:ŒŒModified-List: Diese Liste enthält die Nummern der Speicherseiten, die seit der letztenSynchronisation lokal verändert wurden. Diese Liste liesse sich zwar auch aus derZustandstabelle der Speicherseiten ermitteln, wird im Prototypen aus Performancegründenjedoch zur Laufzeit aufgebaut <strong>und</strong> kann beim Erreichen der Barrier sofort verschickt werden.Owner-List: Diese Liste enthält für jede Speicherseite die Nummer des Knotens, der zuletzt(vor der letzten Synchronisation) diese Seite modifiziert hat <strong>und</strong> damit eine gültige Kopie derSeite besitzt.Welche Vorgänge werden im Prototypen nun konkret ausgelöst, wenn die Threads eines Knotensan eine Barrier gelangen:ŒŒŒDie Ausführung der lokalen OpenMP-Threads wird an der Barrier unterbrochen. Wenn allelokalen Threads an der Barrier angelangt sind, wird...ŒŒŒŒŒŒ... die Modified-List an alle Knoten (inklusive an sich selbst) geschickt... <strong>von</strong> allen Knoten eine Modified-List empfangen... mittels dieser Modified-Listen die Owner-List aktualisiert... jeder Speicherseite im Zustand Modified den neuen Zustand Valid zugeteilt... jede Speicherseite, die durch einen entfernten Thread modifiziert wurde, in den ZustandInvalid versetzt... allfällige Kopien <strong>von</strong> Speicherseiten (siehe unten) wieder freigegebenDie Ausführung der lokalen OpenMP-Threads wird weitergeführt.Page-Requests, die nach dem Empfang der Modified-List eines Knotens empfangen wurden,werden bis zu diesem Zeitpunkt verzögert.Auf Gr<strong>und</strong> <strong>von</strong> Zero-Copy hat diese Art <strong>von</strong> Barrier im Prototypen noch eine kleine Modifikationerfahren: Der Zero-Copy-Kommunikation liegen asynchrone, kleine Pakete <strong>und</strong> synchrone, grossePakete zu Gr<strong>und</strong>e. Die Modified-List wird in einem kleinen Paket verschickt. Wegen derbegrenzten Grösse kann es theoretisch vorkommen, dass mehr Seiten verändert werden, als ineinem Paket Platz finden. Hat die Modified-List die Grösse eines Paketes erreicht, dann wird esunmittelbar verschickt (also nicht auf eine Barrier gewartet) <strong>und</strong> eine neue, leere Liste wirdangelegt. Im Header des Paketes, das eine Modified-List enthält, teilt ein Flag mit, ob der Knotenan einer Barrier angelangt ist, oder ob einfach die Liste voll war. Dieses Verfahren bedingt jedoch,dass jeder Knoten zwei Owner-Lists unterhält: Eine aktuelle, <strong>und</strong> eine, die fortlaufend aus denempfangenen Modified-Lists aufgebaut <strong>und</strong> bei der nächsten Barrier mit der aktuellenverschmolzen wird.Auf Gr<strong>und</strong> des Single-Writer-Verfahrens ist die Grösse des verwendeten Speichers oft abhängig<strong>von</strong> der Anzahl der Knoten, da Speicherseiten gleichzeitig nur durch einen Knoten beschriebenwerden dürfen. Ein Knoten wird jedoch sehr oft nur auf einem kleinen Teil des SHARED-Speichersarbeiten. Um das Working-Set gezielt klein zu halten, könnte den Seiten, die den Zustand Invalidhaben, der physische Speicher entzogen (decommit) werden. Erst bevor eine gültige Seiteangefordert wird, würde dieser Seite wieder Speicher zugeordnet (commit). Dieses Verfahren ist imPrototypen implementiert, wenn die Precompiler-Variable OMP_DECOMMIT_INVALID beimKompilieren der OpenMP-Bibliothek gesetzt ist (siehe OMPLIB.H).Zu erwähnen ist hier noch, dass die einzelnen Threads eines Knotens untereinander nichtsynchronisiert werden müssen, da sie auf den selben lokalen SHARED-Speicher zugreifen. Dasheisst im wesentlichen, dass für alle lokalen Threads eine lokale Speichermodifikation sofortSeite 54Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersystemesichtbar wird. Es ist deshalb auch erlaubt, dass mehrere lokale Threads die selbe Speicherseitemodifizieren. Es muss dem Programmierer jedoch bewusst sein, dass danach ein lokaler Threadeine andere Sicht der entsprechenden Seite hat, als ein entfernter, da dieser erst nach dernächsten Barrier <strong>von</strong> der Modifikation erfährt!WinNT: Trotzdem ist natürlich auch lokal viel Synchronisation nötig, um den SHARED-Speicher<strong>und</strong> die erwähnten Listen in einem konsistenten Zustand zu halten. Windows NT bietetdafür eine Vielzahl <strong>von</strong> Synchronisationsobjekten an: Events stellen ein Signal dar, aufwelches ein Thread warten kann. Mutex erlauben exklusiven Zugriff auf Codestücke <strong>und</strong>die darin modifizierten Daten. Semaphoren erlauben einer beschränkten Anzahl Threadsden Zugriff auf Code bzw. Daten. Folgende Win32-Funktionen stehen im Zusammenhangmit diesen Synchronisationsobjekten: CreateEvent, SetEvent, ResetEvent,CreateMutex, ReleaseMutex, CreateSemaphore, ReleaseSemaphore,WaitForSingleObject, WaitForSingleObjectEx, WaitForMultipleObjects,CloseHandle.6.1.3.5 Eine gültige Speicherseite wird beschriebenWas passiert nun, wenn ein Thread eine gültige Speicherseite (Zustand Valid) beschreibt? LautDiagramm wird diese Seite in den Zustand Modified übergehen. Genauer gesagt passiertfolgendes:ŒŒŒŒEine Seite im Zustand Valid verfügt nur über Lesezugriff. Auf Gr<strong>und</strong> des Schreibzugriffs wirdder Exception-Handler aufgerufen.Dieser ändert den Zustand der Seite in Modified <strong>und</strong> fügt die Seitennummer in die Modified-Listein.Der Speicherseite wird das Recht für Schreib-/Lese-Zugriff zugeteilt.Wenn dieser Knoten laut Owner-List der Knoten mit der gültigen Seite ist, dann muss <strong>von</strong>dieser Seite eine Kopie angefertigt werden, um anderen Knoten, die eine gültige Seiteanfordern, den Zustand bei der letzten Barrier liefern zu können. Dies ist der einzige Fall imPrototypen, wo Zero-Copy nicht erfüllt wird!Tests mit konkreten OpenMP-Anwendungen (siehe Kapitel 9) haben gezeigt, dass dieseKopiervorhänge nicht nur zeitraubend <strong>und</strong> ineffizient, sondern dass sie bei Algorithmen mit hohemParallelisierungsgrad (d.h. Datenbereiche werden über einen längeren Zeitraum ausschliesslich<strong>von</strong> ein <strong>und</strong> demselben Thread gelesen <strong>und</strong> beschrieben) auch unnötig sind. Aus diesem Gr<strong>und</strong>wurde ein sogenannter Lock-Mechanismus eingebaut. Mit OMP_LOCK können durch einen Threadeiner oder mehreren Speicherseiten dauerhaft Read/Write-Zugriffsrechte zugeteilt werden, ohnedass eine Kopie der Daten angelegt würde. Dieser Lock-Zustand bleibt auch über Barriers hinausbestehen. Threads auf entfernten Knoten haben nach einem Lock keinerlei Zugriffsrechte aufgesperrte Seiten. Mit OMP_UNLOCK können gesperrte Seiten wieder freigegeben werden.Der implementierte Lock-Mechanismus ist sehr einfacher Natur. Insbesondere wird den entferntenKnoten nicht mitgeteilt, dass Seiten gesperrt wurden. Der OpenMP-Programmierer ist selberverantwortlich, dass nur der Thread, der die Seiten gesperrt hat, auf diese Seiten zugreift.Fehlerhaft agierende OpenMP-Programme können Fehlermeldungen auslösen, müssen jedochnicht.6.1.3.6 Auf eine ungültige Speicherseite wird zugegriffenEine Speicherseite, die ausserhalb des Knotens modifiziert wurde, ist im Zustand Invalid. Bei derletzten Synchronisation wurde zwar mitgeteilt, welcher Knoten die gültige Kopie der Seite besitzt,der Inhalt wurde aber bewusst noch nicht übermittelt. Sobald der erste lokale Thread nun auf dieseSeite zugreift, wird der Exception-Handler aufgerufen, da die Seite über keine Zugriffsrechteverfügt. Dieser hat folgendes zu tun:ŒŒDie Seite wird in den Zustand Recv versetzt, um anderen lokalen Threads mitzuteilen, dass dieSeite zwar immer noch ungültig ist, jedoch bereits <strong>von</strong> einem Thread angefordert wird.Der Kommunikations-Thread wird gebeten, eine gültige Kopie der entsprechenden Seite zubesorgen.Diplomarbeit <strong>von</strong> Roman Roth Seite 55


Institut für ComputersystemeETH ZürichŒŒŒDer Thread wird schlafen gelegt, bis der Kommunikations-Thread die gültige Kopie empfangenhat. Falls inzwischen weitere Threads auf diese Seite zugreifen, werden diese ebenfallsschlafen gelegt.Wurde die Seite aktualisiert, dann wird der erste Thread aufgeweckt. Falls auf die Seite lesendzugegriffen wurde, wird der Zustand der Seite auf Valid gesetzt <strong>und</strong> mit den Rechten fürLesezugriff versehen. Falls geschrieben wurde, dann wird obiges Szenario ausgeführt.Alle Threads, die auf Gr<strong>und</strong> des Recv-Zustandes schlafen gelegt wurden, werden aufgeweckt.6.1.3.7 Ein entfernter Thread fordert eine Speicherseite anEine Anfrage eines entfernten Threads um eine gültige Kopie einer Speicherseite wird vollständigvom Kommunikations-Thread bearbeitet, kann jedoch nicht in jedem Fall ohne Nebenwirkungen fürdie OpenMP-Threads über die Bühne gehen. Folgendes passiert, wenn ein Page-Requestempfangen wird:ŒFalls die Seite den Zustand Valid hat, wird sie vorübergehend in den Zustand Send versetzt,um anderen lokalen Threads mitzuteilen, dass die Seite zwar immer noch gültig ist, zur Zeitjedoch nicht schreibend darauf zugegriffen werden darf. Schreibende Threads werden indiesem Fall schlafen gelegt.Œ Der Kommunikations-Thread versendet die Seite. Falls die Seite den Zustand Modifiedaufweist, wird die Kopie der Seite verschickt.ŒŒFalls die Seite im Zustand Send ist, wird sie auf Valid zurückgesetzt.Alle Threads, die auf Gr<strong>und</strong> des Send-Zustandes schlafen gelegt wurden, werden wiedergeweckt.6.1.4 Private <strong>Memory</strong>Die Realisation des PRIVATE-Speichers ist keine besonders komplexe Angelegenheit, doch diefehlende Compilerunterstützung fordert doch einige Kniffe, um eine vernünftige Implementation zuerreichen.Um was geht es überhaupt? Neben dem global zugreifbaren SHARED-Speicher hat jeder Threadnoch einen privaten Speicherbereich, auf den nur er zugreifen kann. Gr<strong>und</strong>sätzlich lässt sichSpeicher nur auf dem SHARED-Speicher allozieren. Doch Speicherbereiche im SHARED-Speicherkönnen „privatisiert“ werden, indem mit OMP_PRIVATE ein Speicherbereich der selben Grösse imPRIVATE-Speicher alloziert, <strong>und</strong> der Pointer der Variable auf diesen neuen Bereich umgebogenwird. An dieser Stelle ist zu erwähnen, dass OpenMP-Variablen (in diesem Prototypen) immerPointer sind, die entweder auf den SHARED- oder den PRIVATE-Speicher zeigen.Auch private Variablen existieren nur in dem Scope, in demOMP_PRIVATE aufgerufen wurde. Wird der Scopeverlassen, dann werden auch die privaten Variablengelöscht <strong>und</strong> der Variablenpointer wieder zurück auf denentsprechenden SHARED-Speicher gebogen.Auch der PRIVATE-Speicher ist als Stack implementiert.Jedes mal, wenn ein neuer Scope eröffnet wird, wird einFrame-Mark auf den Stack geschrieben. Der Aufruf <strong>von</strong>OMP_PRIVATE reserviert nicht nur den für die Variablenötigen Speicherbereich, sondern speichert auch alleInformationen, um beim Verlassen des Scope den aktuellenStack-Frame wieder abzubauen. Zu diesen Informationengehören die Grösse des allozierten Speicherbereichs, dieAdresse des entsprechenden Bereichs im SHARED-Speicher, die Adresse der Pointervariable, sowie ein Flagdas anzeigt, ob der Inhalt des PRIVATE-Bereichs beimVerlassen des Scopes in den SHARED-Bereich kopiertwerden soll (OMP_LASTPRIVATE) oder nicht.Pointer to pointerPointer to sharedLastprivate ?Size of variableContent ofthe variableFrame MarkAbb. 6.6: PRIVATE-SpeicherSeite 56Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeBeim Verlassen eines Scopes wird Variable für Variable inklusive den Zusatzinformationenausgelesen, eventuell der Inhalt der PRIVATE-Variable in den SHARED-Speicherzurückgeschrieben <strong>und</strong> der Variablenpointer auf den SHARED-Bereich zurückgebogen. Das wirdsolange getan, bis ein Frame-Mark erkannt wird.6.2 Das API des OpenMP-PrototypenDieses Unterkapitel beschreibt das API des Prototypen, wie es angewendet wird, <strong>und</strong> welcheEinschränkungen gegenüber dem originalen OpenMP Fortran API [2] bestehen.Alle OpenMP-Direktiven <strong>und</strong> Run-time-Library-Routinen sind als #define in der C-HeaderdateiOPENMP.H implementiert <strong>und</strong> greifen auf die Funktionsbibliothek OMPLIB.DLL zu. Ein um OpenMP-Funktionalität erweitertes C-Programm muss OMPLIB.LIB mitlinken, um Zugriff auf die Funktionen inder DLL zu bekommen.Folgendes kleines Beispiel zeigt ein Programm, das die Summe aller Zahlen zwischen 1 <strong>und</strong>10‘000 verteilt auf mehrere Threads berechnet:OMP_INITOMP_INIT_DECLAREint* x = OMP_ALLOC(sizeof(int));int* z = OMP_ALLOC(sizeof(int));OMP_INIT_END_DECLAREOMP_PARALLEL(TRUE)OMP_PARALLEL_DECLAREint i;int *y[OMP_GET_MAX_THREADS];OMP_PARALLEL_END_DECLAREfor(i = 0; i < OMP_GET_NUM_THREADS; i++)y[i] = OMP_ALLOC_NEW_PAGE(sizeof(int));*(y[OMP_GET_THREAD_NUM]) = 0;OMP_DOOMP_DO_DECLAREint* x = OMP_ALLOC(sizeof(int));OMP_PRIVATE(x, sizeof(int))OMP_DO_END_DECLAREDO(x, 1, 10000, 1)*(y[OMP_GET_THREAD_NUM]) += *x;OMP_END_DOOMP_MASTER*z = 0;for(*x = 0; *x < OMP_GET_NUM_THREADS; (*x)++)*z += *(y[*x]);printf("Summe = %i\n", *z);OMP_END_MASTEROMP_END_PARALLELOMP_EXITDie Tatsache, dass der Prototyp ohne Complierunterstützung auskommen muss, lässt dieProgrammierung <strong>von</strong> OpenMP-Anwendungen etwas kompliziert erscheinen. WelcheEinschränkungen gelten <strong>und</strong> was sonst noch beachtet werden muss, wird bei der Beschreibungder einzelnen Direktiven genauer erläutert.6.2.1 OMP_INIT / OMP_EXITOriginal: -Prototyp: OMP_INITOMP_INIT_DECLARE{declaration}OMP_INIT_END_DECLAREblockOMP_EXITDiplomarbeit <strong>von</strong> Roman Roth Seite 57


Institut für ComputersystemeETH ZürichJedes OpenMP-Programm, das den Prototypen verwendet, muss mit der OMP_INIT-Direktivebeginnen. Jeder OpenMP-Thread bearbeitet das Programm zwischen OMP_INIT <strong>und</strong> OMP_EXIT,gesteuert <strong>von</strong> den Direktiven dazwischen. OMP_EXIT ist die letzte Direktive im Programm.Innerhalb der beschriebenen Direktiven sind keine Subroutinen erlaubt. Sollten Subroutinen nötigsein, dann sind diese entweder vor OMP_INIT zu plazieren oder nach OMP_EXIT, wobei diesedann in einer Headerdatei deklariert sein müssen. Es ist verboten, innerhalb <strong>von</strong> SubroutinenOpenMP-Direktiven zu verwenden, die einen neuen Scope eröffnen! Gr<strong>und</strong>sätzlich wird auf Gr<strong>und</strong>der fehlenden Compilerunterstützung empfohlen, auf Subroutinen zu verzichten.Da die Sprache C die Variablendeklaration nur am Anfang eines Blockes erlaubt, müssen diese ineinem OpenMP-Programm ganz zu Beginn eines neuen Scopes vorgenommen werden. Deshalbwurden die nicht zum Standard gehörenden Direktiven OMP_INIT_DECLARE <strong>und</strong>OMP_INIT_END_DECLARE eingeführt. Zwischen den beiden Direktiven werden alle Variablendeklariert, die im SHARED-Speicher <strong>von</strong> OpenMP plaziert werden <strong>und</strong> über die gesamte Laufzeit(auch über die Grenzen <strong>von</strong> PARALLEL-Blöcken hinweg) verfügbar sein sollen. Declaration weisstfolgendes Format auf:pointer_type var [= OMP_ALLOC[_NEW_PAGE](size)];Es können gr<strong>und</strong>sätzlich nur Pointer verwendet werden, so dass das OpenMP-System dieMöglichkeit hat, den Pointer umzubiegen (z.B. beim Aufruf <strong>von</strong> OMP_PRIVATE, um den Pointervom SHARED-Speicher in den PRIVATE-Speicher umzuplazieren). Der Programmierer solltediese Variablen aber nicht als Pointer betrachten, sondern als normale Instanzen eines bestimmtenTyps. Zugriffe auf die Variablen sind dann auch immer in dereferenzierter Form vorzunehmen, wiein Beispiel 1 gezeigt.Beispiel 1:OMP_INITOMP_INIT_DECLAREint* x = OMP_ALLOC(sizeof(int));int* y = OMP_ALLOC_NEW_PAGE(2 * sizeof(int));OMP_INIT_END_DECLAREOMP_PARALLEL(TRUE)OMP_PARALLEL_DECLAREOMP_PARALLEL_END_DECLARE*x = 0;y[0] = 15;y[1] = *x;OMP_END_PARALLELOMP_EXITDer Programmbereich zwischen OMP_INIT_END_DECLARE <strong>und</strong> dem ersten OMP_PARALLEL wirdnur vom globalen Master-Thread ausgeführt, alle anderen Threads überspringen diesen Bereich.Die Angabe <strong>von</strong> OMP_INIT_DECLARE <strong>und</strong> OMP_INIT_END_DECLARE ist zwingend, auch wennkeine Deklarationen vorgenommen werden!Differenzen zum Standard:All diese Direktiven existieren im Standard nicht. Da Standard-OpenMP eine Compilererweiterungist, würden alle nötigen Initialisierungs- <strong>und</strong> Aufräumarbeiten vom Compiler automatisch eingefügt.Implementation:#define OMP_INIT int main( int argc, char *argv[ ], char *envp[ ] )\{\OMP_Init(argc, argv, _OpenMPThreadFunc);\OMP_Run();\OMP_Exit();\return 0;\}\DWORD WINAPI _OpenMPThreadFunc(LPVOID _pVoid)\{\int _nThreadNum = (int)_pVoid;\__try\{Seite 58Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeOMP_Init() initialisiert die internen Datenstrukturen <strong>und</strong> baut die Kommunikation auf.OMP_Run() startet dann die lokalen OpenMP-Threads <strong>und</strong> sichert die Kommunikation solangediese laufen. OMP_Exit() beendet die Kommunikation <strong>und</strong> baut die internen Datenstrukturenwieder ab. _nThreadNum enthält die lokale Thread-Nummer.#define OMP_EXIT }\}\__except(OMP_ExceptionFilter(_nThreadNum,\GetExceptionInformation()))\{\OMP_Abort(“Unhandled exception”);\}\OMP_PostThreadTermination(_nThreadNum);\return 0;\}Für den gesamten Programmbereich übernimmt OMP_ExceptionFilter() das Exception-Handling. Fehler, die korrekt aufgelöst werden können (Page-Faults), werden ohne sichtbareAnzeichen für den Betrachter behandelt, alle anderen Fehler führen zum sofortigen Abbruch desProzesses durch OMP_Abort(). Die bevorstehende Termination des Threads wird OMP_Run()durch OMP_PostThreadTermination() mitgeteilt.#define OMP_INIT_DECLARE#define OMP_INIT_END_DECLARE if (OMP_GetThreadNum(_nThreadNum) == 0)\{6.2.2 OMP_ALLOC / OMP_ALLOC_NEW_PAGEOriginal: -Prototyp: pointer = OMP_ALLOC( size );pointer = OMP_ALLOC_NEW_PAGE( size );Um den deklarierten Variablen einen Speicherbereich aus dem SHARED-Speicher zuzuweisen,können die beiden Funktionen OMP_ALLOC / OMP_ALLOC_NEW_PAGE verwendet werden. BeideFunktionen nehmen als Parameter die Grösse des zu allozierenden Speicherblocks in Bytesentgegen <strong>und</strong> geben einen Pointer zum entsprechenden Speicherbereich zurück (siehe Beispiel 1).OMP_ALLOC alloziert den Speicher unmittelbar angrenzend an den letzten allozierten Bereich. ImGegensatz dazu alloziert OMP_ALLOC_NEW_PAGE den gewünschten Speicherbereich garantiert inder nächsten Speicherseite. Da dieser Prototyp Multiple-Writer-Funktionalität nicht unterstützt, istdies nützlich, wenn auf zwei Variablen gleichzeitig aus zwei (auf verschiedenen Maschinenlaufenden) Threads zugegriffen werden soll.Der allozierte Speicherbereich steht nur im aktuellen Scope zur Verfügung. Im BereichOMP_INIT_DECLARE / OMP_END_INIT_DECLARE allozierter Speicher hat maximalen Scope, washeissen will, dass der Speicher während der ganzen Laufzeit verfügbar ist, auch über die grenzen<strong>von</strong> PARALLEL-Blöcken hinweg.Differenzen zum Standard:Diese beiden Funktionen sind nicht Bestandteil des OpenMP-Standards. Die Speicherallokationwürde standardgemäss durch den Compiler geregelt.Implementation:#define OMP_ALLOC(size)#define OMP_ALLOC_NEW_PAGE(size)OMP_Alloc(_nThreadNum, size, FALSE)OMP_Alloc(_nThreadNum, size, TRUE)OMP_Alloc() alloziert einen Speicherblock der gewünschten Grösse entweder fortlaufend oderpage-aligned. Realer Speicher wird jedoch nur durch den ersten lokalen Thread, der dieseFunktion aufruft, alloziert, alle anderen Threads erhalten lediglich den Pointer auf den bereitsallozieren Bereich zurück.Diplomarbeit <strong>von</strong> Roman Roth Seite 59


Institut für ComputersystemeETH Zürich6.2.3 OMP_PARALLEL / OMP_END_PARALLELOriginal:Prototyp:!$OMP PARALLEL [clause {[,] clause}]block!$OMP END PARALLELOMP_PARALLEL(logical_expression)OMP_PARALLEL_DECLARE{declaration}{clause}OMP_PARALLEL_END_DECLAREblockOMP_END_PARALLELProgrammcode zwischen OMP_PARALLEL <strong>und</strong> OMP_END_PARALLEL wird <strong>von</strong> allen OpenMP-Threads ausgeführt, wenn das nicht durch andere Direktiven innerhalb des PARALLEL-Blockseingegrenzt wird.OMP_PARALLEL öffnet einen neuen Scope. Alle Variablen, die im globalen Scope deklariertwurden, sind auch im neuen Scope sichtbar. Variablen, die zwischen OMP_PARALLEL_DECLARE<strong>und</strong> OMP_PARALLEL_END_DECLARE deklariert, <strong>und</strong> Speicher, der dazwischen alloziert wird, ist nurinnerhalb dieses Scopes gültig. Declaration entspricht der obigen Definition.Folgende clause sind vom Standard übernommen <strong>und</strong> im Prototypen implementiert worden:Œ DEFAULT(SHARED): Alle Variablen, die im äusseren Scope existieren, existierenstandardmässig im neuen Scope als SHARED weiter.ŒŒŒPRIVATE(list)ist implementiert in OMP_PRIVATE.FIRSTPRIVATE(list)ist implementiert in OMP_FISTPRIVATE.IF(scalar_logical_expression) ist in logical_expression <strong>von</strong> OMP_PARALLEL enthalten.Logical_expression ist ein boolscher Ausdruck. Der parallele Bereich wird nur dann ausgeführt,wenn der Ausdruck wahr ist.Der Bereich zwischen OMP_END_PARALLEL <strong>und</strong> dem nächsten OMP_PARALLEL bzw. OMP_EXITwird ausschliesslich durch den globalen Master-Thread ausgeführt.Differenzen zum Standard:Weder OMP_PARALLEL_DECLARE noch OMP_PARALLEL_END_DECLARE gehören zum Standard.Da die OpenMP-Threads nicht dynamisch gestartet werden, sind verschachtelte PARALLEL-Blöcke, wie es der OpenMP-Standard erlaubt, nicht möglich.Folgende clause sind nicht oder nur unvollständig implementiert:ŒŒŒŒSHARED(list) ist default, das heisst, alle Variablen existieren im SHARED-Speicher, wenn sienicht explizit als PRIVATE deklariert werden.DEFAULT(PRIVATE | NONE): DEFAULT(PRIVATE) dürfte ohne Compilerunterstützung nurmit erheblichem Aufwand zu implementieren sein, da dem System immer alle Variablenbekannt sein müssten.REDUCTION({operator|intrinsic}:list): Auch dieser Ausdruck ist ohne Compilerunterstützungnur mit grossem Aufwand zu implementieren, da Typenerkennung nötig wird.COPYIN(list) bezieht sich auf Fortran-Common-Blocks, die C nicht unterstützt.Implementation:#define OMP_PARALLEL(ifexp) }\if (ifexp)\{\OMP_EnterParallel(_nThreadNum);\{Seite 60Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeAusgeführt wird der PARALLEL-Bereich nur, wenn der boolsche Ausdruck ifexp einen wahrenWert ergibt. OMP_EnterParallel() synchronisiert alle OpenMP-Threads mit einer globalenBarrier <strong>und</strong> eröffnet einen neuen Scope sowohl im SHARED- wie auch im PRIVATE-Speicher.#define OMP_END_PARALLEL }\OMP_LeaveParallel(_nThreadNum);\}\if (OMP_GetThreadNum(_nThreadNum) == 0)\{OMP_LeaveParallel() synchronisiert zuerst die Threads mit einer Barrier, danach wird aller imaktuellen Scope allozierter Speicher freigegeben <strong>und</strong> der Scope gelöscht.#define OMP_PARALLEL_DECLARE#define OMP_PARALLEL_END_DECLARE6.2.4 OMP_DO / OMP_END_DOOriginal:Prototyp:!$OMP DO [clause {[,] clause}]do_loop[!$OMP END DO [NOWAIT]]OMP_DOOMP_DO_DECLARE{declaration}{clause}OMP_DO_END_DECLAREDO(pointer, from, to, step)blockOMP_END_DO({ WAIT | NOWAIT })Ein paralleler Loop, wie er durch OMP_DO / OMP_END_DO implementiert wird, kann nur innerhalb<strong>von</strong> OMP_PARALLEL / OMP_END_PARALLEL existieren.OMP_DO eröffnet einen neuen Scope für Variablen. Alle Variablen, die in einem äusseren Scopedeklariert wurden, sind auch im neuen Scope sichtbar. Zwischen OMP_DO_DECLARE <strong>und</strong>OMP_DO_END_DECLARE können Variablen deklariert <strong>und</strong> Speicher alloziert werden, dieausschliesslich innerhalb dieses Scopes existieren.Folgende clause werden vom Standard übernommen <strong>und</strong> implementiert:ŒŒŒŒPRIVATE(list)ist implementiert in OMP_PRIVATE.FIRSTPRIVATE(list)ist implementiert in OMP_FISTPRIVATE.LASTPRIVATE(list)ist implementiert in OMP_LASTPRIVATE.SCHEDULE(STATIC,1): Die einzelnen Iterationen werden im Ro<strong>und</strong>-Robin-Verfahren auf dieeinzelnen Threads aufgeteilt.OMP_FIRSTPRIVATE <strong>und</strong> OMP_LASTPRIVATE lassen sich nur auf Variablen aus dem äusserenScope sinnvoll anwenden. Die LASTPRIVATE-Variablen werden <strong>von</strong> dem Thread zurückkopiert,der die letzte Iteration durchgeführt hat.Die eigentliche Schleife wird durch DO implementiert. Der Parameter pointer ist ein Pointer auf eineVariable im PRIVATE-Bereich (also nicht dereferenzieren!). Der Pointer zeigt auf die zuverwendende Iterationsvariable, die <strong>von</strong> einem gültigen Integer-Typ sein muss. DO würde in C etwafolgendermassen aussehen, wobei die einzelnen Iterationen auf die Threads verteilt werden:for(*pointer = from; *pointer


Institut für ComputersystemeETH ZürichMit WAIT bzw. NOWAIT kann entschieden werden, wie die Threads sich nach Beendigung derSchleife verhalten sollen. WAIT impliziert eine globale Barrier, das heisst, alle Threads <strong>und</strong> derStatus des SHARED-Speichers werden synchronisiert. Mit NOWAIT werden nur die lokalenThreads synchronisiert. Dies ist nötig, damit alle lokalen Threads den gleichen Scope aufweisen.Differenzen zum Standard:Weder OMP_DO_DECLARE noch OMP_DO_END_DECLARE gehören zum Standard.Folgende clause sind in diesem Prototypen nicht implementiert worden:ŒŒŒREDUCTION ({operator|intrinsic}:list): siehe OMP_PARALLEL.SCHEDULE(type[,chunk]): Die Implementation <strong>von</strong> anderen Typen als STATIC <strong>und</strong> anderenChunk-Grössen als 1 liesse sich zum Teil durch einfaches verändern der DO-Implementationrealisieren. Typen wie DYNAMIC jedoch bedürfen zusätzlicher Kommunikation <strong>und</strong>Synchronisation.ORDERED: Könnte nur durch entsprechende Kommunikation <strong>und</strong> Synchronisation implementiertwerden. Damit zusammenhängend sind auch die Direktiven !$OMP ORDERED <strong>und</strong> !$OMP ENDORDERED nicht implementiert.Implementation:#define OMP_DOOMP_EnterBlock();\OMP_PrivatePush(_nThreadNum, NULL, 0, FALSE, FALSE);\{OMP_EnterBlock() eröffnet einen neuen Scope im SHARED-Speicher. OMP_PrivatePush()tut das selbe im PRIVATE-Speicher.#define DO(px,from,to,step) {\int _nLast = ( ((to-from+1)/step) -\(((to-from+1)%step)?0:1) ) %\OMP_GetNumThreads();\for(*px = (from +\(OMP_GetThreadNum(_nThreadNum) * step));\*px


ETH ZürichInstitut für Computersysteme6.2.5 OMP_SECTIONS / OMP_END_SECTIONSOriginal:Prototyp:!$OMP SECTIONS [clause {[,] clause}][!$OMP SECTION]block[!$OMP SECTIONblock]...!$OMP END SECTIONS [NOWAIT]OMP_SECTIONSOMP_SECTIONS_DECLARE{declaration}{clause}OMP_SECTIONS_END_DECLAREblock{OMP_SECTION([ FIRST | NEXT | thread-number ])block }OMP_END_SECTIONS([ WAIT | NOWAIT ])Die SECTIONS-Direktiven teilen mehrere Codestücke verschiedenen Threads zur Bearbeitung zu.OMP_SECTIONS markiert den Beginn eines solchen Bereichs. Wie bereits bei OMP_PARALLEL <strong>und</strong>OMP_DO beschrieben, öffnet auch OMP_SECTIONS einen neuen Scope für Variablen. So könnenzwischen OMP_SECTIONS_DECLARE <strong>und</strong> OMP_SECTIONS_END_DECLARE Variablen deklariert <strong>und</strong>Speicher alloziert werden, die nur bis OMP_END_SECTIONS Gültigkeit haben.Folgende clause werden vom Prototypen implementiert:ŒŒŒPRIVATE(list)ist implementiert in OMP_PRIVATE.FIRSTPRIVATE(list)ist implementiert in OMP_FISTPRIVATE.LASTPRIVATE(list)ist implementiert in OMP_LASTPRIVATE.OMP_FIRSTPRIVATE <strong>und</strong> OMP_LASTPRIVATE lassen sich nur auf Variablen aus dem äusserenScope sinnvoll anwenden. Die LASTPRIVATE-Variablen werden <strong>von</strong> dem Thread zurückkopiert,der die lexikographisch letzte SECTION ausgeführt hat.Der erste Block wird vom globalen Master-Thread ausgeführt. Die weiteren Blöcke sind durchOMP_SECTION <strong>von</strong>einander getrennt. FIRST als Parameter bewirkt die Ausführung des nächstenBlocks durch den globalen Master-Thread. NEXT lässt den folgenden Block durch den Threadausführen, der die nächst höhere globale Threadnummer hat als der Thread des vorgängigenBlocks. Es kann auch explizit eine Threadnummer angegeben werden.OMP_END_SECTIONS schliesst die SECTION ab. Mit WAIT wird eine globale Barrier eingerichtet,die alle Threads <strong>und</strong> den SHARED-Speicher synchronisiert. NOWAIT synchronisiert nur die lokalenThreads, um keine Probleme mit den Scopes zu bekommen.Differenzen zum Standard:Weder OMP_SECTIONS_DECLARE noch OMP_SECTIONS_END_DECLARE gehören zum Standard.Folgende clause sind in diesem Prototypen nicht implementiert worden:ŒREDUCTION ({operator|intrinsic}:list): siehe OMP_PARALLEL.Die Möglichkeit, OMP_SECTION eine Threadnummer zu übergeben, ist im Standard nicht enthalten,sie ermöglicht es dem Programmierer jedoch, die Ressourcen gezielter einzusetzen.Diplomarbeit <strong>von</strong> Roman Roth Seite 63


Institut für ComputersystemeETH ZürichImplementation:#define OMP_SECTIONS {\int _nSection = 0;\OMP_EnterBlock();\OMP_PrivatePush(_nThreadNum, NULL, 0, FALSE, FALSE);\{_nSection numeriert die Sektionen. OMP_EnterBlock() <strong>und</strong> OMP_PrivatePush() eröffnenden neuen Scope.#define OMP_SECTIONS_DECLARE#define OMP_SECTIONS_END_DECLARE if (OMP_GetThreadNum(_nThreadNum) == _nSection)\#define OMP_SECTION(x) }\if ( OMP_GetThreadNum(_nThreadNum) ==\((_nSection = ((x == NEXT) ? _nSection+1 : x)) %\OMP_GetNumThreads()) )\{Auf Gr<strong>und</strong> <strong>von</strong> _nSection wird entschieden, ob ein Thread eine Sektion auszuführen hat odernicht.#define OMP_END_SECTIONS(wait) }\OMP_ReleasePrivate(_nThreadNum, (_nSection %\OMP_GetNumThreads()) ==\OMP_GetThreadNum(_nThreadNum));\OMP_Barrier(_nThreadNum, (wait == NOWAIT), TRUE);\}\}OMP_ReleasePrivte() kopiert die LASTPRIVATE-Variablen zurück <strong>und</strong> schliesst den aktuellenScope im PRIVATE-Speicher. OMP_Barrier() synchronisiert nur die lokalen (NOWAIT) bzw. alleThreads (WAIT) <strong>und</strong> gibt den aktuellen Scope im SHARED-Speicher frei.6.2.6 OMP_MASTER / OMP_END_MASTEROriginal:Prototyp:!$OMP MASTERblock!$OMP END MASTEROMP_MASTERblockOMP_END_MASTEROMP_LOCAL_MASTERblockOMP_END_LOCAL_MASTERInnerhalb eines PARALLEL-Blocks kann erzwungen werden, dass ein Codestück nur durch denglobalen Master-Thread ausgeführt wird. Alle anderen Threads überspringen diesen Code.Nicht im Standard enthalten, auf Gr<strong>und</strong> der Differenzierung <strong>von</strong> lokalen <strong>und</strong> globalen Threadsjedoch nützlich, sind die Direktiven OMP_LOCAL_MASTER / OMP_END_LOCAL_MASTER. Der darinenthaltene Code wird nur <strong>von</strong> den lokalen Master-Threads ausgeführt.Es ist zu beachten, dass OMP_MASTER wie auch OMP_LOCAL_MASTER keine neuen Scopeseröffnen.Implementation:#define OMP_MASTER if (OMP_GetThreadNum(_nThreadNum) == 0)\{#define OMP_END_MASTER }#define OMP_LOCAL_MASTER if (_nThreadNum == 0)\{#define OMP_END_LOCAL_MASTER }Seite 64Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme6.2.7 OMP_BARRIEROriginal:Prototyp:!$OMP BARRIEROMP_BARRIEROMP_BARRIER stellt eine globale Barrier dar. Jeder Thread wird solange angehalten, bis alleThreads bei der Barrier angelangt sind. Bei dieser Gelegenheit wird auch der SHARED-Speichersynchronisiert, das heisst, es werden Informationen ausgetauscht, wer welche Speicherseiteverändert hat.Implementation:#define OMP_BARRIEROMP_Barrier(_nThreadNum, FALSE, FALSE);6.2.8 OMP_PRIVATE / OMP_FIRSTPRIVATE / OMP_LASTPRIVATEOriginal:Prototyp:!$OMP PRIVATE(list)!$OMP FIRSTPRIVATE(list)!$OMP LASTPRIVATE(list)OMP_PRIVATE(pointer, size)OMP_FIRSTPRIVATE(pointer, size)OMP_LASTPRIVATE(pointer, size)Allozierter Speicher existiert gr<strong>und</strong>sätzlich im SHARED-Speicher <strong>und</strong> ist für jeden OpenMP-Threadles- <strong>und</strong> (unter Berücksichtigung des Single-Writer-Prinzips) schreibbar.Alle OMP_xxxPRIVATE-Funktionen allozieren einen Speicherbereich im PRIVATE-Speicher. DerFunktion wird dabei ein Pointer auf den SHARED-Speicherbereich <strong>und</strong> dessen Grösse übergeben.Wenn die Funktion zurückkehrt, ist der gewünschte Speicherbereich im PRIVATE-Speicheralloziert <strong>und</strong> der Pointer auf den entsprechenden Bereich umgebogen. Der allozierteSpeicherbereich ist nicht initialisiert, enthält also einen <strong>und</strong>efinierten Inhalt.Der Speicherbereich im PRIVATE-Speicher ist nur im aktuellen Scope gültig. Wird der Scopeverlassen, dann wird automatisch der Speicher freigegeben <strong>und</strong> der Pointer wieder auf denursprünglichen SHARED-Bereich zurückgebogen.Es können nur Variablen in den PRIVATE-Speicher verlegt werden, die sich bisher im SHARED-Speicher bef<strong>und</strong>en haben, das heisst insbesondere, dass eine Variable nicht zweimal mit einerOMP_PRIVATE Funktion behandelt werden darf.Zusätzlich zur beschriebenen Funktionalität bietet OMP_FIRSTPRIVATE die Möglichkeit, denSpeicherinhalt des entsprechenden SHARED-Bereiches in den PRIVATE-Bereich zu kopieren. Dieumgekehrte Funktionalität bietet die Funktion OMP_LASTPRIVATE, die beim Verlassen des Scopesvom PRIVATE- in den SHARED-Bereich kopiert. Dabei kommt jedoch nur ein OpenMP-Threadzum Zuge. Welcher das ist, hängt vom aktuellen Scope ab.Differenzen zum Standard:Im Gegensatz zum OpenMP-Standard können die OMP_PRIVATE-Funktionen nur jeweils eineVariable als PRIVATE deklarieren. Sollen mehrere Variablen deklariert werden, dann muss dieFunktion mehrmals aufgerufen werden.Implementierung:#define OMP_PRIVATE(px, size)#define OMP_FIRSTPRIVATE(px, size)#define OMP_LASTPRIVATE(px, size)OMP_DeclarePrivate(_nThreadNum, &px, size,FALSE, FALSE);OMP_DeclarePrivate(_nThreadNum, &px, size,TRUE, FALSE);OMP_DeclarePrivate(_nThreadNum, &px, size,FALSE, TRUE);OMP_DeclarePrivate() alloziert einen Bereich im PRIVATE-Speicher für den Inhalt derVariable <strong>und</strong> für alle Informationen, die notwendig sind, um am Ende des aktuellen Scope, denDiplomarbeit <strong>von</strong> Roman Roth Seite 65


Institut für ComputersystemeETH ZürichSpeicherbereich automatisch wieder freizugeben <strong>und</strong> den Pointer wieder zurück auf den altenBereich zu biegen.6.2.9 OMP_GET_NUM_THREADSOriginal:Prototyp:SUBROUTINE OMP_GET_NUM_THREADS()int OMP_GET_NUM_THREADSint OMP_GET_NUM_LOCAL_THREADSOMP_GET_NUM_THREADS gibt die Anzahl der Threads zurück, die den aktuellen PARALLEL-Blockbearbeiten. Es ist ein Wert zwischen 1 <strong>und</strong> OMP_GET_MAX_THREADS.Differenzen zum Standard:Ausserhalb des Standards bietet der Prototyp zusätzlich die FunktionOMP_GET_NUM_LOCAL_THREADS, die angibt, wieviele Threads lokal arbeiten.Implementation:#define OMP_GET_NUM_THREADS#define OMP_GET_NUM_LOCAL_THREADSOMP_GetNumThreads()OMP_GetNumLocalThreads()6.2.10 OMP_GET_MAX_THREADSOriginal:Prototyp:SUBROUTINE OMP_GET_MAX_THREADS()const int OMP_GET_MAX_THREADSOMP_GET_MAX_THREADS gibt die maximale Anzahl Threads zurück, die einen PARALLEL-Blockbearbeiten können. Dies ist eine Konstante.Implementation:#define OMP_GET_MAX_THREADS(OMP_MAX_LOCAL_THREADS * OMP_MAX_GLOBAL_NODES)6.2.11 OMP_GET_THREAD_NUMOriginal:Prototyp:SUBROUTINE OMP_GET_THREAD_NUM()int OMP_GET_THREAD_NUMint OMP_GET_LOCAL_THREAD_NUMOMP_GET_THREAD_NUM gibt die globale Nummer des aufrufenden Threads zurück. Der Wert liegtzwischen 0 <strong>und</strong> OMP_GET_NUM_THREADS-1. 0 kennzeichnet den globalen Master-Thread.Differenzen zum Standard:Ausserhalb des Standards bietet der Prototyp zusätzlich die FunktionOMP_GET_LOCAL_THREAD_NUM, die die lokale Nummer des aufrufenden Threads zurückgibt.Dieser Wert liegt zwischen 0 (lokaler Master) <strong>und</strong> OMP_GET_NUM_LOCAL_THREADS-1.Implementation:#define OMP_GET_THREAD_NUM#define OMP_GET_LOCAL_THREAD_NUMOMP_GetThreadNum(_nThreadNum)_nThreadNumSeite 66Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme6.2.12 OMP_GET_xxx_NODES / OMP_GET_NODE_NUMOriginal: -Prototyp: int OMP_GET_MAX_NODESint OMP_GET_NUM_NODESint OMP_GET_NODE_NUMDiese drei Funktionen sind nicht im OpenMP-Standard enthalten, sie können jedoch sehr nützlichsein. Sie beschreiben, wieviele Knoten maximal auftreten können (OMP_GET_MAX_NODES),wieviele Knoten aktuell vorhanden sind (OMP_GET_NUM_NODES), <strong>und</strong> welche Nummer der aktuelleKnoten hat (OMP_GET_NODE_NUM). Die Numerierung der Knoten folgt der globalenThreadnumerierung: Der Knoten, der Thread 0 enthält, ist Knoten 0. Der Knoten, der den Threadenthält, der die nächst grössere Nummer hat als der letzte Thread des vorhergehenden Knotens,erhält die nächst grössere Knotennummer.Beispiel:Zero-Copy-LayerNode IDGlobaleThreadnummernLogischeKnotennummer0 3, 4, 5, 6 11 0, 1, 2 02 7, 8, 9 2Tab. 6.2: Die logischen KnotennummernImplementation:#define OMP_GET_MAX_NODES#define OMP_GET_NUM_NODES#define OMP_GET_NODE_NUM(OMP_MAX_GLOBAL_NODES)OMP_GetNumNodes()OMP_GetNodeNum()6.2.13 OMP_LOCK / OMP_UNLOCKOriginal: -Prototyp: OMP_LOCK(pointer, size)OMP_UNLOCK(pointer, size)Mit OMP_LOCK können Speicherseiten für den exklusiven Zugriff durch den aufrufenden Threadreserviert werden. Alle Seiten, die durch den Speicherbereich (pointer, size) belegt werden, sindbetroffen. Die Speicherseiten erhalten Read/Write-Zugriffsrechte. Von Threads auf entferntenKnoten dürfen diese Seiten nicht mehr gelesen oder beschrieben werden. OMP_UNLOCK gibt diegesperrten Seiten wieder frei.Die Implementation des Lock-Mechanismus wurde so simpel wie möglich gelöst. Daher kann einkorrekter Programmablauf nur gewährleistet werden, wenn OMP_LOCK unmittelbar nach,OMP_UNLOCK unmittelbar vor einer Barrier plaziert sind.Beispiel:OMP_BARRIEROMP_LOCK(a, 4096);...OMP_UNLOCK(a, 4096);OMP_BARRIERDifferenzen zum Standard:Beide Funktionen sind in dieser Form nicht im OpenMP-Standard enthalten.Diplomarbeit <strong>von</strong> Roman Roth Seite 67


Institut für ComputersystemeETH ZürichImplementation:#define OMP_LOCK(px, l) OMP_Lock((char*)px, l)#define OMP_UNLOCK(px, l) OMP_Unlock((char*)px, l)6.3 Im Prototypen nicht enthaltene FunktionalitätDer OpenMP-Prototyp beinhaltet längst nicht alle Funktionalität, die der Standard anbieten würde.Einzelne Direktiven wurden, wie bereits erwähnt, nur unvollständig implementiert, anderevollständig weggelassen. Die folgende Liste beschreibt, welche Direktiven gänzlich nichtimplementiert wurden:ŒŒŒŒŒŒSINGLE: Diese Direktive beschränkt die Ausführung <strong>von</strong> Code auf den Thread, der als erstesbei der Direktive angelangt ist. Die Implementation würde zusätzliche Kommunikation <strong>und</strong>Synchronisation erfordern.CRITICAL implementiert eine Critical-Section. Auch hier wäre zusätzliche Kommunikation <strong>und</strong>Synchronisation nötig.ATOMIC: Erlaubt atomares Lesen <strong>und</strong> Schreiben einer Variable, entspricht also imwesentlichen einer minimalen Critical-Section.FLUSH: Flush wurde nicht implementiert, da es sich um eine sehr fortranspezifische Direktivehandelt.ORDERED: Garantiert die sequentielle Ausführung <strong>von</strong> Code in einer DO-Schleife. Entspricht imwesentlichen einer Critical-Section mit zusätzlicher Spezifikation, was die Reihenfolge dereintretenden Threads betrifft.THREADPRIVATE: Fortranspezifische Version <strong>von</strong> PRIVATE.Neben diesen Direktiven wurden folgende Funktionen weggelassen:ŒŒŒOMP_SET_NUM_THREADS: Die Anzahl Threads ist im Prototypen statisch.OMP_GET_NUM_PROCS: Für den Prototypen unwichtig.OMP_GET_DYNAMIC / OMP_SET_DYNAMIC: Die Anzahl der Threads ist statisch.ŒOMP_GET_NESTED / OMP_SET_NESTED: Die Anzahl der Threads ist statisch. VerschachteltePARALLEL-Bereiche sind nicht erlaubt.Zudem definiert der OpenMP-Standard eine Reihe <strong>von</strong> Lock-Routinen, die einen exklusiven Zugriffimplementieren. Diese Funktionen wurden im Prototypen nicht aufgenommen. Lokal können solcheLocks mit Win32 Mutex implementiert werden. Für global exklusiven Zugriff ist zusätzlicheKommunikation nötig.6.4 Mögliche Erweiterungen <strong>und</strong> VerbesserungenGr<strong>und</strong>sätzlich könnten die in den beiden vorherigen Unterkapiteln erwähnten, noch nichtimplementierten OpenMP-Direktiven <strong>und</strong> -Funktionen hinzugefügt werden. Zusätzlich beschreibtdie folgende Liste kurz mögliche Veränderungen am bestehenden Prototypen, die zuVerbesserungen oder Erweiterungen führen könnten. Bei diesen Vorschlägen handelt es sich zumTeil nur um Ideen, die jedoch weiterer Abklärungen bedürfen, um konkrete Aussagen über denNutzen machen zu können.ŒZusätzliche Synchronisation: Jeder Thread (nicht Knoten, wie bei der globalen Barrier) schickteine Meldung dem Master-Knoten, wenn er an einen Synchronisationspunkt angelangt ist. DerKommunikations-Thread des Masters wäre dafür verantwortlich, diese Meldungenentgegenzunehmen <strong>und</strong> den einzelnen Threads mit einer weiteren Meldung mitzuteilen, wannsie den Synchronisationspunkt verlassen dürfen. Durch eine Variation der Reihenfolge, inwelcher die Threads weiterarbeiten dürfen, könnten Direktiven wie SINGLE, CRITICAL <strong>und</strong>ORDERED implementiert werden.Seite 68Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeŒŒŒŒŒEs wäre abzuklären, in wie fern sich ein Multi-Writer-Verfahren, wie es in TreadMarksverwirklicht ist, auf Kosten <strong>von</strong> Zero-Copy doch lohnen könnte. Die einzelnen Knoten sindgr<strong>und</strong>sätzlich fähig, bei einer Barrier zu erkennen, wie viele Knoten eine Seite veränderthaben. So könnte dynamisch Zero-Copy eingesetzt werden, wenn nur ein Knoten die Seiteverändert hat <strong>und</strong> ein Multi-Writer-Verfahren anderenfalls.Das Win32-API bietet mit der Funktion VirtualLock die Möglichkeit, eine beschränkteAnzahl <strong>von</strong> Speicherseiten zu locken, also vor dem Auslagern ins Page-File zu schützen. Eingezielter Einsatz dieser Funktion (zum Beispiel auf Seiten im aktuellen Scope, oder aufmodifizierte Seiten) könnte unter Umständen die Performance erhöhen. Eventuell muss mitSetProcessWorkingSetSize die Grösse des Working-Sets verändert werden.Im Prototypen wird bei einem Page-Fault (auf Gr<strong>und</strong> des Invalid-Zustandes) nur immer dieentsprechende Seite <strong>von</strong> einem entfernten Rechner geholt. Falls die Seiten unmittelbar vorbzw. nach der kommunizierten Seite ebenfalls den Zustand Invalid haben <strong>und</strong> dieentsprechenden, gültigen Seiten beim gleichen, entfernten Rechner liegen, dann könntendiese Seiten miteinander übertragen werden (Pre-Fetching). So könnten die Paketevergrössert <strong>und</strong> die Anzahl der Übertragungen verkleinert werden, mit dem kleinen Nachteil,dass eventuell mehr Seiten kommuniziert werden als wirklich benötigt.Das Versenden der Modified-List ist nicht optimal gelöst: Da der Zero-Copy-Layer nichtblockierendesSenden unterstützt, wird pro Empfängerknoten ein Async-Request alloziert <strong>und</strong>die Modified-List hineinkopiert. Wenn die Modified-List <strong>von</strong> Anfang an im Body eines Async-Request gehalten würde <strong>und</strong> dem Request eine Liste <strong>von</strong> Empfängern mitgegeben werdenkönnte, könnte diese „Kopiererei“ vermieden werden. Erst wenn das Senden an alleEmpfänger abgeschlossen ist, würde der Request mit der Modified-List wieder freigegeben.Im aktuellen Prototypen tauschen die Threads nur die Nummern der Seiten aus, die sie seit derletzten Barrier modifiziert haben. Es könnte unter Umständen interessant sein, auch dieNummern der Seiten zu kommunizieren, die ein Knoten seit der letzten Barrier <strong>von</strong> einemanderen angefordert hat. Wenn mehrere Knoten eine gültige Version einer Seite haben, dannmacht es durchaus Sinn, dass andere Knoten die Last verteilen, indem sie eine gültige Seitenicht zwingend beim ursprünglichen Knoten abholen, sondern eventuell bei einem anderen.6.5 Die Anwendung des OpenMP-PrototypenDer OpenMP-Prototyp ist in den Source-Dateien OPENMP.H, OMPLIB.H <strong>und</strong> OMPLIB.Cimplementiert. Das Microsoft Visual C++ 5.0 Projekt OMPLIB.DSP enthält alle notwendigenEinstellungen für Kompilation <strong>und</strong> Linken.Ein OpenMP-Programm muss ein #Include enthalten <strong>und</strong> mit OMPLIB.LIB gelinktwerden. Um lauffähig zu sein, müssen sowohl die Funktionsbibliothek des Zero-Copy-Layers(ZEROCOPY.DLL) wie des OpenMP-Prototypen (OMPLIB.DLL) auf allen Systemen verfügbar sein.Zudem muss die oben bereits erklärte ZEROCOPY.CFG vorhanden sein.Für die Konfiguration der OpenMP-Prototypen muss der OpenMP-Applikation der Name einerweiteren Konfigurationsdatei mitgegeben werden:z.B.: TestOmp.Exe TestOmp.CfgDiese Konfigurationsdatei enthält für jeden zu verwendenden Knoten eine Sektion <strong>von</strong> folgendemFormat:[Node]FirstThread = LastThread = Im Knoten mit der ID werden so viele OpenMP-Threads gestartet, wie Nummern zwischender <strong>und</strong> (Randnummern inklusive) liegen. Alle Sektionen zusammen müssenlückenlos alle Nummern zwischen 0 <strong>und</strong> dem grössten abdecken, ohne dass eineNummer doppelt vergeben wird.Diplomarbeit <strong>von</strong> Roman Roth Seite 69


Institut für ComputersystemeETH ZürichBeispiel:[Node2]FirstThread = 0LastThread = 3[Node0]FirstThread = 4LastThread = 6[Node3]FirstThread = 7LastThread = 8Seite 70Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme7 Der MPI-PrototypVom Konzept her bereits wesentlich einfacher als OpenMP ist das <strong>Message</strong>-<strong>Passing</strong>-Interface(MPI). Dementsprechend ist auch die Implementation eines Prototypen wesentlich einfacher.Zudem wurde die Anzahl der implementierten Funktionen auf die wichtigsten beschränkt. Konkretwurden eigentlich nur Senden <strong>und</strong> Empfangen plus eine Barrier implementiert. Basierend aufdiesen Funktionen lässt sich ein grosser Teil der <strong>von</strong> MPI definierten Kommunikationsfunktionen(Scatter, Gather u.s.w.) nachbilden.7.1 Die Konzepte des MPI-PrototypenDieser kleine Funktionsumfang beinhaltet jedoch ein paar Probleme, die durchdacht <strong>und</strong> gelöstwerden müssen.7.1.1 ThreadingStandardgemäss würden MPI-Applikationen durch einen MpiRun-Prozess auf dem lokalen wieauch auf entfernten Systemen gestartet. Dieser praktische aber aufwendige Mechanismus hätteeinen NT-Service zur Folge. Diese Technik wurde nicht in den Prototypen aufgenommen. Stattdessen muss auf jedem Knoten eine MPI-Applikation <strong>von</strong> Hand gestartet werden. Der Hauptthreadmuss nun dem lokalen System alle in der Applikation enthaltenen MPI-Routinen mitteilen <strong>und</strong> danndie Funktion MPI_Run aufrufen:int Routine1(){MPI_Init();...MPI_Finalize();return 0;}int Routine2(){MPI_Init();...MPI_Finalize();return 0;}int main( int argc, char *argv[ ], char *envp[ ] ){MPI_Register_routine("Routine1", Routine1);MPI_Register_routine("Routine2", Routine2);MPI_Run(argc, argv);return 0;}MPI_Run liest dann eine der Applikation mitgegebene Konfigurationsdatei <strong>und</strong> startet gemäss dendarin enthaltenen Angaben einen oder mehrere MPI-Threads, die jeweils eine der registriertenRoutinen abarbeiten. Der Hauptthread selber wird zum sogenannten Kommunikationsthread,welcher auf dem Zero-Copy-Layer aufbaut <strong>und</strong> für die Kommunikation zwischen den Knotenverantwortlich ist.Es wurde auch versucht, eine MPI-Implementation ohne Kommunikationsthread zu erreichen. Dieswäre gr<strong>und</strong>sätzlich möglich. Wird der vorliegende Zero-Copy-Layer verwendet, ergeben sichjedoch Probleme:ŒDa die Socket- <strong>und</strong> Pipe-Netzwerk-Layer mit APCs arbeiten, wird das Empfangen immer imKontext des Threads durchgeführt, der ReadFileEx aufgerufen hat. Konkret heisst das, dassimmer der selbe Thread für das Empfangen verantwortlich ist. Ist dieser Thread längere Zeitmit lokalen Berechnungen beschäftigt, dann ist kein Empfangen möglich, auch für die anderenThreads nicht.Œ Bei der GM-Implementation ist es wichtig, dass jederzeit Empfangspuffer zur Verfügungstehen. Sind alle Threads mit lokalen Berechnungen beschäftigt, dann kann es sein, dassdiese Puffer ausgehen.Diplomarbeit <strong>von</strong> Roman Roth Seite 71


Institut für ComputersystemeETH ZürichWas GM betrifft, bleibt nur die Lösung eines zusätzlichen Threads. Werden Sockets oder Pipeseingesetzt, so müsste der Zero-Copy-Layer so umgeschrieben werden, dass er nicht Knoten-zu-Knoten-Kommunikation unterstützt, sondern Thread-zu-Thread. Das würde heissen, dass <strong>von</strong>jedem Thread zu jedem nicht-lokalen Thread eine Verbindung bestehen müsste. So könnten dieReadFileEx jeweils im Kontext des empfangenden Threads aufgerufen werden.Knoten 0Knoten 1MPIThread 0MPIThread 1MPIThread 2MPIThread 3MPIThread 4MPIThread 5Kommunikations-ThreadKommunikations-ThreadZero-Copy-LayerZero-Copy-LayerAbb. 7.1: Das Threadmodell des MPI-Prototypen7.1.2 KommunikationDas grösste Problem, die Kommunikation nämlich, wurde bereits gelöst. Wie bereits erwähntbasiert der MPI-Prototyp auf dem Zero-Copy-Layer. Sämtliche Kommunikation zwischen denKnoten (nicht zwischen den Threads!) wird über diesen Layer abgewickelt.Dabei muss ein Problem erwähnt werden, das nicht befriedigend gelöst werden konnte: DasProblem liegt in der Tatsache, dass der Zero-Copy-Layer zwei Arten der Kommunikation (synchron<strong>und</strong> asynchron) anbietet. Für kleine Meldungen eignet sich die asynchrone Art besser, für grosseMeldungen die synchrone. So wurde der Prototyp auch implementiert. Eine Empfängerapplikationmuss jedoch nicht in jedem Fall wissen, wie gross die Meldung ist, die ihr eine entfernte Applikationschickt. Sie wird deshalb einen genügend grossen Empfangspuffer zur Verfügung stellen. Ist diesergrösser als die kritische Grösse für asynchrone Kommunikation, so wird eine synchroneÜbertragung erwartet. Wenn der Sender jedoch nur eine kleine Meldung verschicken will, so wirddiese asynchron verschickt. Die Empfängerapplikation wird das Paket zwar erhalten, doch dersynchrone Recv-Request bleibt hängig <strong>und</strong> würde bei der nächsten grossen Meldungfälschlicherweise verwendet <strong>und</strong> der zugehörige Puffer überschrieben werden. Dieses Problemlässt sich auf Gr<strong>und</strong> <strong>von</strong> Latenzzeiten <strong>und</strong> (im Falle <strong>von</strong> Myrinet) Fullduplex-Kommunikation nichtbefriedigend lösen. Jede Form <strong>von</strong> Cancel-Requests oder anderen Lösungsversuchen, derentfernten Applikation mitzuteilen, dass die erhaltene Empfangsbereitschaft nicht mehr gültig ist,kann zu spät kommen <strong>und</strong> bleibt wirkungslos.Um diesem Problem wenigstens etwas entgegenzutreten <strong>und</strong> dem Benutzer des MPI-Prototypendie Möglichkeit zu geben, Meldungen zu empfangen, <strong>von</strong> derer Grösse nur eine obere Schrankebekannt ist, kann bei MPI_Send, MPI_Isend, MPI_Recv <strong>und</strong> MPI_Irecv eine negativePuffergrösse angegeben werden <strong>und</strong> so eine synchrone Kommunikation (unabhängig <strong>von</strong> derGrösse) erzwungen werden. Es versteht sich, dass diese Massnahme nicht dem MPI-Standardentspricht.Wie bereits erwähnt, wird der Zero-Copy-Layer nur dann verwendet, wenn zwischen zwei Knotenkommuniziert werden muss. Für lokale Kommunikation zwischen den Threads wäre diese Art vielzu umständlich <strong>und</strong> in keiner Hinsicht leistungsfähig. Deshalb wurde für die lokale Kommunikationnicht nur der Zero-Copy-Layer umgangen, sondern auch gleich der Kommunikationsthread. DerSeite 72Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeSender prüft zuerst die Bereitschaft des Empfängers. Ist dieser bereit, dann wird der Sendepufferin den Empfangspuffer kopiert. Ist er nicht bereit, dann wird ihm lediglich die Sendebereitschaftmitgeteilt. Umgekehrt prüft der Empfänger zuerst die Bereitschaft des Senders. Ist dieser bereitsbereit, dann wird die Meldung kopiert, ansonsten dem Sender die Empfangsbereitschaft mitgeteilt.Auch bei der lokalen Kommunikation wäre es schön, es könnte das Kopieren umgangen werden,bzw. es müsste nur bei Bedarf kopiert werden – Copy-On-Write sozusagen. Zwar liesse sich unterWindows NT mittels <strong>Shared</strong>-<strong>Memory</strong> (vgl. Win32 CreateFileMapping, OpenFileMapping <strong>und</strong>MapViewOfFileEx) ein solches System theoretisch realisieren, jedoch nur mit grossenEinschränkungen oder Änderungen gegenüber dem MPI-Standard. Doch wenn lokal schon mit<strong>Shared</strong>-<strong>Memory</strong> gearbeitet werden soll, dann macht es wenig Sinn, dies in ein <strong>Message</strong>-<strong>Passing</strong>-Modell zu zwängen. Da die lokalen Threads innerhalb des selben Prozesses existieren, ist <strong>Shared</strong>-<strong>Memory</strong> implizit vorhanden. Wieso dies also nicht in seiner natürlichsten <strong>und</strong> schnellsten Formbenutzen? Statt ganze Speicherbereiche via MPI zu kommunizieren, könnte für die lokaleKommunikation auch nur der Pointer des Speicherbereichs gesendet <strong>und</strong> empfangen werden:char* pBuffer = malloc(10000);char* pBuffer;MPI_Send(&pBuffer, 1, MPI_INT, ...); MPI_Recv(&pBuffer, 1, MPI_INT, ...);Dies erfordert vom MPI-Programmierer zwar, dass er sich der Lokalität der einzelnen Threadsbewusst ist, dafür wird er mit der einer leistungsfähigen, lokalen Kommunikation belohnt.7.1.3 SynchronisationIm vorliegenden MPI-Prototypen gibt es nur ein Synchronisationselement, die Barrier. Alle lokalenThreads, die an der Barrier angelangt sind, werden schlafen gelegt. Sobald der letzte, lokaleThread die Barrier erreicht hat, schickt der Kommunikationsthread dem Master-Knoten (er enthältden Thread mit dem Rank 0) eine Meldung, die das erreichen der Barrier signalisiert. Hat derMaster-Knoten <strong>von</strong> allen anderen Knoten diese Meldung bekommen, dann schickt er allen eineBestätigung. Sobald ein Knoten diese Bestätigung erhält, weckt er alle Threads wieder auf.Dank dem lokalen Sammeln der Threads muss nicht jeder Thread das erreichen der Barriersignalisieren, sondern nur jeder Knoten. So kann unter Umständen einiges an Kommunikationgespart werden.7.2 Das API des MPI-PrototypenDas API des MPI-Prototypen ist einerseits durch den Funktionsumfang beschränkt, andererseitsweisen auch die implementierten Funktionen Einschränkungen auf. Dieses Unterkapitel beschreibt,welche Teile implementiert sind, <strong>und</strong> welche Unterschiede zum Standard [6] existieren.Applikationsbeispiel:int SendRoutine(){MPI_Init();MPI_Send(“This is a message.”, 18, MPI_CHAR, 1, 34, MPI_COMM_WORLD);MPI_Finalize();return 0;}int RecvRoutine(){MPI_Status status;char buf[1000];}MPI_Init();MPI_Recv(buf, 1000, MPI_CHAR, 0, 34, MPI_COMM_WORLD, &status);printf(buf);MPI_Finalize();return 0;Diplomarbeit <strong>von</strong> Roman Roth Seite 73


Institut für ComputersystemeETH Zürichint main( int argc, char *argv[ ], char *envp[ ] ){MPI_Register_routine("Send", SendRoutine);MPI_Register_routine("Recv", RecvRoutine);MPI_Run(argc, argv);return 0;}Die Datei MPI.H enthält alle notwendigen Definitionen. Unter anderem auch die folgende Struktur,die den Status eines MPI_Recv dokumentiert:struct MPI_Status{int MPI_SOURCE;int MPI_TAG;int MPI_ERROR;}int MPI_LEN;Rank des AbsendersTag der MeldungMPI_SUCCESS oder einen FehlercodeLänge der empfangenen MeldungMPI definiert eine ganze Reihe <strong>von</strong> Fehlercodes. Da der Prototyp keine Prüfung der Argumentevornimmt, sondern <strong>von</strong> einer korrekten MPI-Applikation ausgeht, werden nur wenige derFehlercodes verwendet:MPI_SUCCESSMPI_ERR_PENDINGMPI_ERR_INTERNKein Fehler aufgetretenBearbeitung des Requests ist noch nicht abgeschlossenEin interner Fehler ist aufgetreten.Beim Versenden <strong>von</strong> Meldungen geht MPI nicht zwingend <strong>von</strong> Characters oder Bytes aus. DieLänge einer Meldung hängt vom Datentyp <strong>und</strong> der Anzahl Elemente ab. Folgende Datentypen sinddefiniert:MPI_CHARMPI_UNSIGNED_CHARMPI_BYTEMPI_SHORTMPI_UNSIGNED_SHORTMPI_INTMPI_UNSIGNEDMPI_LONGMPI_UNSIGNED_LONGMPI_FLOATMPI_DOUBLEMPI_LONG_DOUBLEMPI_LONG_LONG_INT1 Byte1 Byte1 Byte2 Bytes2 Bytes4 Bytes4 Bytes4 Bytes4 Bytes4 Bytes8 Bytes16 Bytes8 BytesLetzte wichtige Definition ist die Obergrenze der verwendbaren Tags. Es sind gr<strong>und</strong>sätzlich Tagszwischen 0 <strong>und</strong> MPI_TAG_UB erlaubt. Im Prototypen ist die Obergrenze 0x7FFFFFFD.Folgende MPI-Funktionen sind implementiert:7.2.1 MPI_Register_routineOriginal: -Prototyp: int MPI_Register_routine(CHAR* pcName,PMPI_ROUTINE pMpiRoutine)Diese Funktion ist nicht im MPI-Standard enthalten. Sie verbindet eine MPI-Routine mit einemNamen. Auf Gr<strong>und</strong> dieses Namen werden Routinen identifiziert <strong>und</strong> gestartet (siehe Kapitel 7.1.1).Diese MPI-Routinen müssen folgende Signatur aufweisen:int MpiRoutine()Der Name ist natürlich beliebig. Die Routine kann einen beliebigen Wert zurückgeben.Seite 74Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme7.2.2 MPI_RunOriginal: -Prototyp: int MPI_Run(int argc, char *argv[])Diese Funktion ist nicht im MPI-Standard enthalten. Sie ersetzt die MpiRun-Applikation, die dieeinzelnen MPI-Applikationen starten soll. Der Funktion wird die Anzahl der Argumente <strong>und</strong> einPointer auf die Argumente übergeben. Im Prinzip werden einfach die beiden ersten Argumente dermain-Funktion weitergereicht.MPI_Run startet alle notwendigen MPI-Threads <strong>und</strong> übernimmt selbst die Aufgabe desKommunikationsthreads.7.2.3 MPI_InitOriginal:Prototyp:int MPI_Init(int *argc, char ***argv)int MPI_Init()MPI_Init initialisiert die nötigen MPI-Strukturen eines MPI-Threads. Diese Funktion muss alserste MPI-Funktion aufgerufen werden. Im Prototypen enthält diese Funktion lediglich eine Barrier,um den Start der Routinen zu synchronisieren.7.2.4 MPI_FinalizeOriginal:Prototyp:int MPI_Finalize(void)int MPI_Finalize(void)Diese Funktion baut alle aufgebauten Datenstrukturen wieder ab. Sie ist die letzte MPI-Funktion,die aufgerufen werden darf. Konkret enthält sie nur eine Barrier.7.2.5 MPI_SendOriginal:Prototyp:int MPI_Send(void* buf, int count, MPI_Datatype datatype,int dest, int tag, MPI_Comm comm);int MPI_Send(void* buf, int count, MPI_Datatype datatype,int dest, int tag, MPI_Comm comm);Blockierendes Senden wird durch MPI_Send implementiert. Gesendet werden count *datatype Bytes des Puffers, auf den buf zeigt. dest ist der Rank des Empfängers, tag das Tag.Der Wert <strong>von</strong> comm wird ignoriert.Wenn count * datatype


Institut für ComputersystemeETH Zürich7.2.6 MPI_IsendOriginal:Prototyp:int MPI_Isend(void* buf, int count, MPI_Datatype datatype,int dest, int tag, MPI_Comm comm,MPI_Request* request);int MPI_Isend(void* buf, int count, MPI_Datatype datatype,int dest, int tag, MPI_Comm comm,MPI_Request* request);Nicht-blockierendes Senden wird durch MPI_Isend erreicht. Gesendet werden count *datatype Bytes des Puffers, auf den buf zeigt. dest ist der Rank des Empfängers, tag das Tag.Der Wert <strong>von</strong> comm wird ignoriert. request ist ein Pointer auf eine MPI_Request Datenstruktur.Sie wird gebraucht, um anschliessend mit einer Test- oder Wait-Funktion den Status des Sendenszu prüfen.Wenn count * datatype


ETH ZürichInstitut für Computersysteme7.2.9 MPI_TestOriginal:Prototyp:int MPI_Test(MPI_Request* request, int* flag,MPI_Status* status);int MPI_Test(MPI_Request* request, int* flag,MPI_Status* status);Wird nicht-blockierend kommuniziert, dann kann mit dieser Testfunktion der Status eines Requestsüberprüft werden. request ist eine zuvor an MPI_Isend oder MPI_Irecv übergebene Request-Datenstruktur. Ist nach dem Aufruf dieser Funktion das flag gleich TRUE, dann ist der Requestbeendet <strong>und</strong> status enthält den Status des Requests. Ansonsten ist er noch im Gange.7.2.10 MPI_TestallOriginal:Prototyp:int MPI_Testall(int count, MPI_Request* array_of_request,int* flag, MPI_Status* array_of_status);int MPI_Testall(int count, MPI_Request* array_of_request,int* flag, MPI_Status* array_of_status);Wird nicht-blockierend kommuniziert, dann kann mit dieser Testfunktion der Status mehrererRequests überprüft werden. array_of_request sind count zuvor an MPI_Isend oderMPI_Irecv übergebene Request-Datenstrukturen. Ist nach dem Aufruf dieser Funktion das flaggleich TRUE, dann sind alle Requests beendet <strong>und</strong> array_of_status enthält die Stati derRequests. Ist flag gleich FALSE, dann ist mindestens ein Request noch im Gange.7.2.11 MPI_WaitOriginal:Prototyp:int MPI_Wait(MPI_Request* request, MPI_Status* status);int MPI_Wait(MPI_Request* request, MPI_Status* status);Diese Funktion entspricht MPI_Test. Sie legt den laufenden Thread jedoch solange schlafen, bisder angegebene Request beendet wurde.7.2.12 MPI_WaitallOriginal:Prototyp:int MPI_Waitall(int count, MPI_Request* array_of_request,MPI_Status* array_of_status);int MPI_Waitall(int count, MPI_Request* array_of_request,MPI_Status* array_of_status);Diese Funktion entspricht MPI_Testall. Sie legt den laufenden Thread jedoch solange schlafen,bis alle angegebenen Requests beendet wurden.7.2.13 MPI_Get_countOriginal:Prototyp:int MPI_Get_count(MPI_Status* status,MPI_Datatype datatype, int* count);int MPI_Get_count(MPI_Status* status,MPI_Datatype datatype, int* count);Wurde eine Meldung erfolgreich empfangen, dann kann mit MPI_Get_count die Anzahl Elementeeines Datentyps ermittelt werden, die die Meldung enthält. status ist dabei der Status <strong>von</strong>MPI_Recv oder einer Test- oder Wait-Funktion.Diplomarbeit <strong>von</strong> Roman Roth Seite 77


Institut für ComputersystemeETH Zürich7.2.14 MPI_BarrierOriginal:Prototyp:int MPI_Barrier(MPI_Comm comm);int MPI_Barrier(MPI_Comm comm);MPI_Barrier implementiert ein Synchronisationspunkt. Alle Threads werden solange schlafengelegt, bis alle Threads diesen Punkt erreicht haben. comm wird ignoriert.7.2.15 MPI_Comm_sizeOriginal:Prototyp:int MPI_Comm_size(MPI_Comm comm, int* size);int MPI_Comm_size(MPI_Comm comm, int* size);Gibt die Anzahl global arbeitender MPI-Threads zurück. comm wird ignoriert.7.2.16 MPI_Comm_rankOriginal:Prototyp:int MPI_Comm_rank(MPI_Comm comm, int* rank);int MPI_Comm_rank(MPI_Comm comm, int* rank);Gibt den Rank des aufrufenden Threads zurück. comm wird ignoriert.7.3 Die Anwendung des MPI-PrototypenNachdem nun ausführlich auf Theorie <strong>und</strong> Schnittstelle eingegangen wurde, zeigt diesesUnterkapitel die konkrete Anwendung des MPI-Prototypen auf.Eine MPI-Applikation, die den Prototypen verwenden will, muss ein #include „Mpi.H“ enthalten<strong>und</strong> mit der Datei MPILIB.LIB gelinkt werden. Alle Applikationen müssen Zugriff auf MPILIB.DLL,ZEROCOPY.DLL <strong>und</strong> ZEROCOPY.CFG haben.Jeder Applikation muss als Argument der Name einer Konfigurationsdatei mitgegeben werden.Diese Datei enthält Angaben, auf welchem Knoten welche Threads laufen, <strong>und</strong> welche Routinendiese abarbeiten. Für jeden Rank gibt es eine Sektion <strong>von</strong> folgendem Format:[Rank]NodeID = Routine = ist eine Zahl <strong>von</strong> 0 an aufsteigend. Es werden so viele Sektionen gelesen, bis eine nichtmehr gef<strong>und</strong>en wird. sagt auf welchem Knoten dieser Rank laufen soll. entspricht dem Namen einer Routine, die mit MPI_Register_routine im System registriertwurde.Beispiel:[Rank0]NodeID = 3Routine = Producer[Rank1]NodeID = 0Routine = Consumer2[Rank2]NodeID = 1Routine = Consumer1Seite 78Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme8 PerformancemessungenIn den vorangegangenen Kapiteln sind nun zwei Prototypen besprochen worden, die beide aufdem sogenannten Zero-Copy-Layer aufbauen. Theorie, Technologie <strong>und</strong> Implementation wurdenausführlich dokumentiert. Dieses Kapitel bespricht nun den praktischen Einsatz, indemverschiedene Messungen durchgeführt werden, die die Performance der Layer <strong>und</strong> Prototypenaufzeigen soll.8.1 Bandbreitenmessungen über SocketsVon allem Anfang dieser Arbeit an waren Hochgeschwindigkeits-Netzwerke wie Myrinet oderGigaBit-Ethernet als Basistechnologie ins Auge gefasst worden. Hohe Bandbreiten <strong>und</strong> kleineLatenzzeiten zeichnen solche Netzwerke aus. Verschiedene Bandbreitenmessungen sollen nunaufzeigen, wieviel der durch diese Netzwerke zur Verfügung gestellten Performance durch dieLayer <strong>und</strong> Prototypen ausgenutzt werden kann.8.1.1 Die Windows Sockets als BasisDa zum Zeitpunkt der Messungen weder das GM-API für Myrinet noch ein Zero-Copy-Protokoll fürdas GigaBit-Ethernet verfügbar waren, mussten als Ausgangspunkt der Messungen wohl oder übeldie Windows Sockets hinhalten. Diese sind natürlich alles andere als Zero-Copy. Doch geht man<strong>von</strong> der Performance der Sockets aus, so können doch erste Aussagen über die Leistungsfähigkeitder Layer bzw. der Prototypen gemacht werden.Als Testplattform standen zwei Dell Optiplex GX1 (Pentium II, 350 MHz, 64 MB RAM) zurVerfügung. Verb<strong>und</strong>en waren diese über ein 100 mbit Ethernet (NIC: DEC DE500, Switch: BayNetworks Bay Stock 100 Base-T Hub), sowie über ein direkt verkabeltes (ohne Switch) GigaBit-Ethernet (Packet Engine G-NIC II).Je zwei Messreihen über jedes Netzwerk soll die Performance der Sockets, <strong>und</strong> damit dieAusgangslage für alle anderen Testresultate, liefern:ŒDie erste Messung verwendet blockierende Sockets. Zwischen den beiden Rechnern wird eineTCP-Verbindung (Stream-Sockets) aufgebaut. Danach wird auf einem Rechner mit sendgesendet, auf dem anderen mit recv empfangen.Œ Die zweite Messung verwendet nicht-blockierende Sockets. Es werden ebenfalls TCP-Verbindungen verwendet. Für das Senden <strong>und</strong> Empfangen werden jedoch die FunktionenWriteFileEx <strong>und</strong> ReadFileEx verwendet, wie sie auch im Socket-Netzwerk-Layereingesetzt werden. Es werden maximal 100 Pakete bzw. eine maximale Summe derPaketgrössen <strong>von</strong> 3 MB beim Start des Tests sofort an die Sockets zum Versenden gegeben.Danach wird erst wieder gesendet, wenn die Sockets das erfolgreiche Versenden einesPaketes gemeldet haben.Getestet werden Paketgrössen zwischen 4 Bytes <strong>und</strong> 2 MB, wobei Paketgrossen <strong>von</strong> 2 i <strong>und</strong>sqrt(2)*2 i Bytes verwendet werden. Alle Messungen werden 3 mal durchgeführt, um eventuelleSchwankungen zu erkennen. Die genauen Resultate befinden sich im Anhang A in den TabellenA.1 bis A.4.Die Einstellungen der Sockets werden gegenüber den Standardwerten leicht verändert. Die Sende<strong>und</strong>Empfangspuffer werden auf je 64 kB erhöht. Zudem wird die Option TCP_NODELAYeingeschaltet, die verhindert, dass kleine Pakete verzögert verschickt werden. Zudem werden dieKeep-Alive-Meldungen unterdrückt.Die Resultate der Messreihen, wie sie in Abbildung 8.1 <strong>und</strong> 8.2 dargestellt sind, zeigen deutlich,dass Kommunikationsformen, die während dem Kommunikationsvorgang die Daten ein oder garmehrmals Kopieren, auf leistungsfähigen Netzwerken nicht mehr genügen, um diese auszulasten.Immerhin vermögen die Sockets das 100 mbit Ethernet maximal zu 70% auszulasten(Protokolloverhead nicht berücksichtigt). Ganz anders sieht das Resultat beim GigaBit-Ethernetaus: Lediglich 11% der Leistung konnten genutzt werden. Das ist zwar viel zu wenig, ist jedochnicht verw<strong>und</strong>erlich, da auch die lokale Kommunikation nicht sehr gut abschneidet (max. 25 % derKopierbandbreite). Myrinet kann immerhin etwa 30% der GM-Performance ausnutzen.Diplomarbeit <strong>von</strong> Roman Roth Seite 79


Institut für ComputersystemeETH ZürichBandbreiten mit Windows Sockets (blockierend)100’000’000Datendurchsatz [Bytes/s]10’000’0001’000’000100’00010’0001 10 100 1’000 10’000 100’000 1’000’000 10’000’000Paketgrösse [Bytes]100 mbit Ethernet GigaBit Ethernet lokal MyrinetAbb. 8.1: Resultate der Bandbreitenmessungen mit blockierenden Windows-SocketsBandbreiten mit Windows Sockets (nicht-blockierend)100’000’000Datendurchsatz [Bytes/s]10’000’0001’000’000100’00010’0001 10 100 1’000 10’000 100’000 1’000’000 10’000’000Paketgrösse [Bytes]100 mbit Ethernet GigaBit Ethernet lokal MyrinetAbb. 8.2: Resultate der Bandbreitenmessungen mit nicht-blockierenden Windows-SocketsWährend die Kurven der Messungen mit blockierenden Sockets einen sehr typischen Verlaufdarstellen, zeigen nicht-blockierenden Sockets einige Schwankungen. Auffallend ist derPerformanceverlust bei Paketgrössen über 64 kB. Es scheint, dass das Versenden <strong>von</strong> Paketen,die grösser als die Sende- bzw. Empfangspuffer sind, mit zusätzlichem Aufwand behaftet sind (ev.zusätzliches Kopieren).Eine zweite, auffällige Irregularität ist beim nicht-blockierenden Versenden über GigaBit-Ethernetbei Paketgrössen um 724 Bytes zu erkennen. Zufällig kann dieses „Loch“ nicht entstanden sein, dadie Kurve stetig aus dem Loch heraussteigt. Doch zu erklären ist dies ohne genaue Kenntnis derTreiber <strong>und</strong> deren Zusammenarbeit mit dem restlichen System, insbesondere mit NDIS, nur sehrschwer.Seite 80Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeEntgegen der Erwartungen entpuppten sich die nicht-blockierenden Sockets (oder dieImplementation der Win32-Funktionen WriteFileEx <strong>und</strong> ReadFileEx) nicht als vorteilhaft. Überweite Bereiche hinweg liegt der Datendurchsatz deutlich unter den blockierenden Varianten.Trotzdem wurden diese Funktionen in den Socket-Netzwerk-Layer eingebaut, mit dem Vorteil, nichtpollen zu müssen.Die Beurteilung der Leistung der Windows Sockets steht hier jedoch nicht im Vordergr<strong>und</strong>.Vielmehr sollen die darauf aufbauenden Schichten betrachtet werden. Als Basis für die folgendenTests sollen deshalb die beiden Messreihen mit nicht-blockierenden Sockets herangezogenwerden.8.1.2 Der Zero-Copy-LayerDa der Netzwerk-Layer sehr stark mit dem Zero-Copy-Layer zusammenhängt, wurde auf eineseparate Messung verzichtet. Doch die Implementation dieses Layers kann die Performance starkbeeinflussen. Beim Socket-Netzwerk-Layer enthält ein asynchrones Senden einen Aufruf <strong>von</strong>WriteFileEx. Synchrones Senden braucht deren zwei, um einen Header zu versenden <strong>und</strong>anschliessend den Datenbereich. Das Empfangen braucht in jedem Fall zwei Aufrufe <strong>von</strong>ReadFileEx.Der Zero-Copy-Layer sollte beim asynchronen Versenden eigentlich keine grossen Einbussenverbuchen. Synchrones Senden ist jedoch entsprechend Aufwendig, da zusätzlich eineEmpfangsbereitschaftsmeldung verschickt werden muss. Abbildung 8.3 <strong>und</strong> 8.4 zeigen, welcheAuswirkungen diese beiden Schichten mit sich bringen.10’000’000Bandbreiten mit dem Zero-Copy-Layer über 100 mbit EthernetDatendurchsatz [Bytes/s]1’000’000100’00010’0001 10 100 1’000 10’000 100’000 1’000’000 10’000’000Paketgrösse [Bytes]Sockets Non-Blocking Zero-Copy Async Zero-Copy SyncAbb. 8.3: Resultate der Bandbreitenmessungen mit dem Zero-Copy-Layer über 100 mbit EthernetDie Resultate der Messung über das 100 mbit Ethernet überraschen kaum. Die asynchroneKommunikationsform scheint keine wesentliche Einbussen gegenüber den nicht-blockierendenSockets hinnehmen zu müssen. Anders die synchrone Form: Die (für Sockets eigentlich unnötige)Synchronisation ist erst ab Paketgrössen <strong>von</strong> etwa 4 kB nicht mehr relevant. Überraschend hochist der Datendurchsatz bei Paketen zwischen 64 kB <strong>und</strong> 1 MB. Die Mischung <strong>von</strong> kleinen (Header)<strong>und</strong> grossen Paketen scheint das System zu stimulieren.Diplomarbeit <strong>von</strong> Roman Roth Seite 81


Institut für ComputersystemeETH Zürich100’000’000Bandbreiten mit dem Zero-Copy-Layer über GigaBit EthernetDatendurchsatz [Bytes/s]10’000’0001’000’000100’00010’0001 10 100 1’000 10’000 100’000 1’000’000 10’000’000Paketgrösse [Bytes]Sockets Non-Blockingt Zero-Copy Async Zero-Copy SyncAbb. 8.4: Resultate der Bandbreitenmessungen mit dem Zero-Copy-Layer über 100 mbit EthernetIn ungefähr das gleiche Bild zeigt das GigaBit-Ethernet: Die asynchrone Kommunikationüberzeugt, die synchrone naturgemäss erst ab einer Paketgrösse <strong>von</strong> etwa 4 kB. Überraschend istnur der Bereich zwischen 724 Bytes <strong>und</strong> etwa 4 kB. Ich vermute, dass die Anzahl der offenenSend-Requests (oder deren Gesamtgrösse) einen Einfluss auf die Leistung haben kann.Die Leistung des Zero-Copy-Layers scheint mir akzeptabel zu sein. Eine gute Ausgangslage fürApplikationen wie die beiden Prototypen, die darauf aufbauen.Auf Gr<strong>und</strong> der soeben vorgestellten Messresultate wurde auch die obere Grenze für asynchronversendete Pakete festgelegt. Für alle folgenden Messungen liegt diese bei 12 kB. Pakete diegrösser sind, müssen synchron verschickt werden.8.1.3 Der MPI-PrototypFür die Messung der Bandbreite des MPI-Prototypen wurden die blockierenden FunktionenMPI_Send <strong>und</strong> MPI_Recv verwendet. Die Abbildungen 8.5 <strong>und</strong> 8.6 stellen die Resultateverglichen mit dem Zero-Copy-Layer dar.Pakete bis zu einer Grösse <strong>von</strong> 12 kB werden asynchron versendet. Dieser Vorgang beinhaltet jeeinen zusätzlichen Kopiervorgang beim Sender <strong>und</strong> Empfänger. Dies <strong>und</strong> die Verwendung einesdedizierten Kommunikationsthreads mindern die Leistung des Prototypen bei kleinen Paketen. DerKnick nach der 12 kB-Grenze rührt <strong>von</strong> der plötzlichen Verwendung der synchronenKommunikationsart her.Seite 82Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme10’000’000Bandbreiten mit dem MPI-Prototypen über 100 mbit EthernetDatendurchsatz [Bytes/s]1’000’000100’00010’0001 10 100 1’000 10’000 100’000 1’000’000 10’000’000Paketgrösse [Bytes]MPI Zero-Copy Async Zero-Copy SyncAbb. 8.5: Resultate der Bandbreitenmessungen mit dem MPI-Prototypen über 100 mbit Ethernet100’000’000Bandbreiten mit dem MPI-Prototypen über GigaBit EthernetDatendurchsatz [Bytes/s]10’000’0001’000’000100’00010’0001 10 100 1’000 10’000 100’000 1’000’000 10’000’000Paketgrösse [Bytes]MPI Zero-Copy Async Zero-Copy SyncAbb. 8.6: Resultate der Bandbreitenmessungen mit dem MPI-Prototypen über GigaBit-EthernetÜber das GigaBit-Ethernet zeigt sich im wesentlichen das selbe Bild, so dass lange Kommentareüberflüssig sind.8.1.4 Der OpenMP-PrototypBeim OpenMP-Prototypen ist es etwas schwieriger einen Bandbreitentest durchzuführen, daSpeicher immer seitenweise (4 kB) verschickt wird. Trotzdem wurde mit folgendem Programmversucht, einen solchen Test durchzuführen. Auf zwei Maschinen läuft je ein OpenMP-Thread.Beide Threads allozieren 16 MB im SHARED-Speicher. Der eine Thread modifiziert zuerst alleSeiten. Nach einer Barrier modifiziert der zweite Thread diese Seiten <strong>und</strong> misst die Zeit, die erbraucht, um die gültigen Kopien aller Seiten zu erhalten.Diplomarbeit <strong>von</strong> Roman Roth Seite 83


Institut für ComputersystemeETH Zürich#define SIZE (16*1024*1024)OMP_PARALLEL(TRUE)OMP_PARALLEL_DECLARELARGE_INTEGER lStart;LARGE_INTEGER lEnd;int i;char* pBuf = OMP_ALLOC(SIZE);OMP_PARALLEL_END_DECLAREOMP_SECTIONSOMP_SECTIONS_DECLAREOMP_SECTIONS_END_DECLAREOMP_SECTION(0)for(i = 0; i < SIZE; i += PAGE_SIZE)p[i] = 1;OMP_BARRIEROMP_BARRIEROMP_SECTION(1)OMP_BARRIERQueryPerformanceCounter(&lStart);for(i = 0; i < SIZE; i += PAGE_SIZE)p[i] = 0;QueryPerformanceCounter(&lEnd);OMP_BARRIERprintf("Time %I64i\n", lEnd.QuadPart - lStart.QuadPart);OMP_END_SECTIONS(WAIT)OMP_END_PARALLELFür diese 16 MB Speicher hat das Testsystem über das 100 mbit Ethernet durchschnittlich 4.393Sek<strong>und</strong>en gebraucht. Dies ergibt einen Durchsatz <strong>von</strong> etwa 3.64 MB/s. Im <strong>Vergleich</strong> dazu erreichtder MPI-Prototyp bei einer Paketgrösse <strong>von</strong> 4 kB <strong>und</strong> synchronem Senden <strong>und</strong> Empfangen einenDurchsatz <strong>von</strong> ungefähr 3.96 MB/s, also knapp 8% mehr. Verwendet man unter MPI jedochasynchrone Kommunikation, was bei solch kleinen Paketen durchaus sinnvoll ist, so werden 7.64MB/s erreicht, was mehr als das Doppelte des DSM-Systems ist.8.2 Profiling über SocketsDie eben geschilderten Bandbreitenmessungen haben verschiedene Spekulationen mit sichgezogen, in welchen Teilen des Systems Zeit verloren gegangen sein könnte. Die folgendenProfile-Messungen sollen Aufschluss geben.Dabei wird der Ablauf einer einzelnen Kommunikation so genau wie möglich ausgemessen. Anwichtigen Stellen im Kommunikationsablauf wird die aktuelle Zeit gemessen <strong>und</strong> gespeichert. DieDifferenzen aus diesen Zeiten zeigen auf, wo viel <strong>und</strong> wo wenig Zeit verbraucht wird. Damitkönnen allfällige Problemstellen lokalisiert werden.WinNT: Für solche Zeitmessungen stellt Windows NT (oder besser gesagt der PentiumProzessor) einen sogenannten Performance Counter zur Verfügung. Dieser arbeitet (beiden verwendeten Maschinen <strong>von</strong> Dell) mit einer Frequenz <strong>von</strong> 1‘193‘182 Hz. Mit jedemTakt wird der Counter um eins erhöht. Der aktuelle Stand kann mitQueryPerformanceCounter ausgelesen werden. Übrigens: Die Frequenz istprozessorabhängig. Sie kann mit QueryPerformanceFrequency ermittelt werden.Jede Messung beeinflusst das vermessene System. Deshalb ist es wichtig vorher dieAuswirkungen der Messung zu verstehen. Zwei Aufrufe der FunktionQueryPerformanceCounter unmittelbar hintereinander bringen Aufschluss darüber. Resultat:Eine Messung verbraucht mindestens 5 Ticks (1 Tick = 1 / 1‘193‘182 Sek<strong>und</strong>en).8.2.1 MPI-Prototyp <strong>und</strong> Zero-Copy-LayerEin MPI_Send auf der einen Maschine, ein MPI_Recv auf einer anderen. Dieser Ablauf einerKommunikation wurde ausgemessen. Dabei wurden folgende Messpunkte gesetzt:Seite 84Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme8.2.1.1 Senden0: Aufruf <strong>von</strong> MPI_Send1: Übergabe des MPI-Requests an den Kommunikationsthread (in Queue)2: Übernahme des MPI-Requests durch den Kommunikationsthread (aus Queue)3: Aufruf der Zero-Copy-Layer-Funktion ZCL_SendXxxx4: Aufruf der Netzwerk-Layer-Funktion NWL_SendXxxx5: Aufruf der Win32-Funktion WriteFileEx im Netzwerk-Layer6: Asynchonous Procedure Call (Ende der Übertragung)7: Aufruf <strong>von</strong> ZCL_HandleSendDone8: Kommunikationsthread signalisiert die Beendigung des MPI-Requests (SetEvent)9: MPI_Wait wird verlassenAbbildung 8.7 zeigt, wieviel Zeitzwischen den Messpunktenverbraucht wurde. Jede Messungwurde 10 mal wiederholt. Dieverwendeten Werte bilden denDurchschnitt dieser Messungen.Um die Zeiten innerhalb der Layerdeutlicher darstellen zu können,wurde die eigentlicheKommunikationszeit (5 – 6) in derGrafik weggelassen.Die vier Messungen wurden mitunterschiedlichen Paketgrössendurchgeführt:ŒŒŒAsync Small: 32 ByteAsync Big: 8 kBSync Small: 16 kBŒ Sync Big: 1MBDie genauen Resultate können der Abb. 8.7: Zeitaufwände des SendevorgangsTabelle 8.1 entnommen werden.Achtung: Diese Werte enthalten den durch die Messung verursachten Mehraufwand!0 - 1 1 - 2 2 - 3 3 - 4 4 - 5 5 - 6 6 - 7 7 - 8 8 - 9Async Small 39.8 30.0 9.3 6.4 9.4 146.3 6.9 18.0 32.8Async Big 34.9 28.9 78.5 6.3 9.6 264.3 7.3 15.9 37.0Sync Small 35.0 29.0 9.2 8.6 9.0 482.8 6.6 17.7 33.9Sync Big 36.2 29.5 9.0 9.2 9.3 11399.9 16.4 24.5 49.1Tab. 8.1: Durchschnittliche Zeitaufwände des Sendevorgangs in TicksZeit [Ticks]250200150100500Zeitaufwände des SendevorgangesAsync Small Async Big Sync Small Sync BigNeben dem eigentlichen Versenden (5 – 6) sind vor allem die Kontextwechsel (1 – 2 <strong>und</strong> 8 – 9)<strong>und</strong> das Aufbereiten des Requests (0 – 1), verursacht durch ein Mutex, zeitintensiv. Die im Zero-Copy-Layer <strong>und</strong> im Netzwerk-Layer verbrauchte Zeit ist praktisch vernachlässigbar, insbesondere,wenn man die Zeit für die Messungen selber (je ca. 5 Ticks) noch abzieht.Auffällig ist jedoch der Wert <strong>von</strong> Async Big/2 – 3: Asynchrones Versenden mit MPI bedingt einKopiervorgang (bei Verwendung <strong>von</strong> Sockets eigentlich unnötig, beim Gebrauch einer echtenZero-Copy-Technologie wie GM jedoch unerlässlich!). Welchen ungeheuren Aufwand dieseKopiererei mit sich bringt zeigt die Grafik ganz deutlich.8.2.1.2 Empfangen0: Aufruf <strong>von</strong> MPI_Recv1: Übergabe des MPI-Requests an den Kommunikationsthread (in Queue)2: Übernahme des MPI-Requests durch den Kommunikationsthread (aus Queue)3: Aufruf der Zero-Copy-Layer-Funktion ZCL_RecvSync4: Asynchonous Procedure Call NWL_RecvBodyComplete (Ende der Übertragung)Diplomarbeit <strong>von</strong> Roman Roth Seite 858 - 97 - 86 - 74 - 53 - 42 - 31 - 20 - 1


Institut für ComputersystemeETH Zürich5: Aufruf <strong>von</strong> ZCL_HandleXxxxRecv6: Kommunikationsthread signalisiert die Beendigung des MPI-Requests7: MPI_Wait wird verlassenAuch hier soll eine Grafik (Abbildung8.8) die Werte der Tabelle 8.2verdeutlichen.Beim asynchronen Empfangen mitMPI wird dem Kommunikationsthreaddie Empfangsabsicht nichtmitgeteilt. Wurde ein Paketempfangen, dann wird in der Queuedes Empfängers nachgeschaut, ober bereits empfangsbereit ist. Fallsnicht, wird das Paket in seine Queueeingefügt. Das ist der Gr<strong>und</strong>, wiesokeine Werte für 1 – 2 <strong>und</strong> 2 – 3existieren.Da der Empfang asynchron erfolgt,kann nicht ermittelt werden, wannder Empfang genau beginnt.Deshalb wurden auch die Werte 3 -4 nicht gemessen.Auch in diesen Werten sind dieMehraufwände durch die Messungenthalten!Zeit [Ticks]250200150100500Zeitaufwände des EmpfangvorgangsAsync Small Async Big Sync Small Sync BigAbb. 8.8: Zeitaufwände des Empfangvorgangs6 - 75 - 64 - 52 - 31 - 20 - 10 - 1 1 - 2 2 - 3 3 - 4 4 - 5 5 - 6 6 - 7Async Small 38.2 - - - 6.6 32.9 56.4Async Big 35.4 - - - 7.0 92.5 59.9Sync Small 33.9 32.0 9.0 - 8.1 30.1 52.9Sync Big 36.4 33.7 8.0 - 9.8 42.6 58.8Tab. 8.2: Durchschnittliche Zeitaufwände des Empfangvorgangs in TicksDas selbe Schema wie beim Senden: Aufwände beim Requestaufbau (0 – 1) <strong>und</strong> beimKontextswitch (1 – 2 <strong>und</strong> 6 –7). Aufwand jedoch auch beim Auswerten des empfangenen Paketes<strong>und</strong> zuordnen zu einem Request (5 – 6). Hier sind wieder Mutex <strong>und</strong> beim Async BigKopiervorgänge im Spiel.Da Kontextswitches <strong>und</strong> Synchronisation mittels Mutex zu den grössten Zeitverbrauchern gehören,wurde mit folgenden kleinen Programmen versucht, den effektiven Verbrauch zu messen:QueryPerformanceCounter(&lStart);WaitForSingleObject(hMutex, INFINITE);ReleaseMutex(hMutex);QueryPerformanceCounter(&lEnd);Die Differenz zwischen lEnd <strong>und</strong> lStart ergibt auf den Testmaschinen einen durchschnittlichenWert <strong>von</strong> 23 Ticks (Messung inklusive).Thread 1 Thread 2Sleep(1000);QueryPerformanceCounter(&lStart);SetEvent(hEvent);Sleep(1000);WaitForSingleObject(hEvent, INFINITE);QueryPerformanceCounter(&lEnd);Die Messung eines Kontextswitches zwischen zwei Threads ergab Werte um 24 Ticks.Abgesehen <strong>von</strong> diesen Elementen sind die Verluste eher gering, der MPI-Prototyp <strong>und</strong> dieKommunikationslayer also durchaus brauchbar.Seite 86Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme8.2.2 OpenMP-Prototyp <strong>und</strong> Zero-Copy-LayerBeim OpenMP-Prototypen wurde versucht, das Senden <strong>und</strong> Empfangen einer Speicherseite,ausgelöst durch einen Page-Fault zu vermessen. Dazu wurde auf einem System eineSpeicherseite vorgängig verändert, so dass diese Seite auf der anderen Maschine nicht mehrgültig ist. Nach einer Barrier wird auf der zweiten Maschine die selbe Seite verändert. Dies löst dieKommunikation aus, die Vermessen werden soll.Folgende Messpunkte wurden auf den beiden Maschinen eingerichtet:Request:Reply:0: Exception-Handler bemerkt Zugriff auf ungültige Speicherseite1: Übergabe des Page-Requests an den Kommunikationsthread (in Queue)2: Übernahme des Page-Requests durch den Kommunikationsthread (aus Queue)3: Aufruf der Zero-Copy-Layer-Funktion ZCL_RecvSync4: Asynchonous Procedure Call NWL_RecvBodyComplete (Ende der Übertragung)5: Aufruf <strong>von</strong> ZCL_HandleSyncRecv6: Kommunikationsthread signalisiert die Beendigung des Page-Requests7: Exception-Handler fährt mit der Arbeit fort8: Exception-Handler wird verlassen0: Empfang eines Page-Requests1: -2: -3: Aufruf der Zero-Copy-Layer-Funktion ZCL_SendSync4: Aufruf der Netzwerk-Layer-Funktion NWL_SendSync5: Aufruf der Win32-Funktion WriteFileEx im Netzwerk-Layer6: Asynchonous Procedure Call (Ende der Übertragung)7: Aufruf <strong>von</strong> ZCL_HandleSendDone8: Freigeben der Speicherseite <strong>und</strong> wecken schlafen gelegter Threads9: Request beendetDie Abbildung 8.9 <strong>und</strong> die Tabelle8.3 geben Auskunft über dieMessresultate.Die Messwerte der eigentlichenKommunikation stellen dengrössten Aufwand dar. DieseZeiten wurden aus der Betrachtung<strong>und</strong> aus der Abbildung entfernt, umdie restlichen Zeiten genauerDarstellen zu können.Wie bei den vorangegangenenMessungen enthalten diese Werteden zusätzlichen Aufwand derMessung.Zeit [Ticks]200180160140120100806040Zeitaufwände der OpenMP-Kommunikation8 - 97 - 86 - 75 - 64 - 53 - 42 - 31 - 20 - 1200Page RequestPage ReplyAbb. 8.9: Zeitaufwände im OpenMP-Prototypen0 - 1 1 – 2 2 - 3 3 - 4 4 - 5 5 - 6 6 – 7 7 – 8 8 – 9Page Request 22.3 13.1 17.9 1180.2 36.7 9.1 46.0 30.7 -Page Reply 25.7 - - 6.4 8.2 388.4 6.6 6.6 16.3Tab. 8.3: Durchschnittliche Zeitaufwände der OpenMP-Kommunikation in TicksBeim Page-Request sind die Zeitaufwände zwischen den Messpunkten 6 <strong>und</strong> 8 am grössten.Dieser Aufwand geht mehrheitlich auf Kosten eines Kontextswitches <strong>und</strong> eines Mutex.Diplomarbeit <strong>von</strong> Roman Roth Seite 87


Institut für ComputersystemeETH ZürichUngewöhnlich hoch ist die Messung 4-5. Diese Messpunkte befinden sich innerhalb des Zero-Copy-Layers. Die entsprechenden Messungen mit dem MPI-Prototypen haben wesentlich bessereWerte ans Licht geführt. Cache-Misses oder Page-Fault könnten Antworten sein.8.3 Messungen über Named-PipesLeider haben die Messungen über die Sockets gezeigt, dass diese keines Falls die Leistungenausnützen können, die das darunterliegende Netzwerk bieten würde. Deshalb sind die Messungendes Zero-Copy-Layers bzw. der Prototypen nicht unbedingt aussagekräftig. Aus diesem Gr<strong>und</strong>ewurde eine zweite Messreihe gestartet. Dieses mal wurde lokal über Named-Pipes gemessen.Verwendet wurde ein Dell Precision 410 (400 MHz Dual-Pentium II, 256 MB 100 MHz RAM) <strong>und</strong>ein Dell GX1 (350 MHz Pentium II, 64 MB RAM).In zwei Prozessen laufen je ein Thread, die über eine Named-Pipe verb<strong>und</strong>en sind. Sende- <strong>und</strong>Empfangspuffer wurden auf 64 kB festgelegt. Gr<strong>und</strong>sätzlich wurden nicht-blockierende Funktionenzum Senden (WriteFileEx) <strong>und</strong> Empfangen (ReadFileEx) verwendet.8.3.1 LatenzmessungFür die Latenzmessung wurde ein Ping-Pong-Verfahren verwendet. Genauer gesagt, wurden dieZeiten für 100'000 Ping-Pong-Meldungen mit jeweils 1 Byte Payload gemessen. FolgendeResultate wurden erreicht:1 CPU 2 CPUsNamed-Pipes 0.125 ms 0.103 msZero-Copy-Layer, Async 0.191 ms 0.171 msZero-Copy-Layer, Sync 0.404 ms 0.313 msTab. 8.4: Latenzzeiten (Zeit für ein Ping-Pong)Auf Gr<strong>und</strong> der grösseren Datenpakete (16 Byte Header + 1 Byte Payload) <strong>und</strong> zusätzlicherVerarbeitungsaufwand ist die Latenz bei den asynchronen Paketen via Zero-Copy-Layer r<strong>und</strong> 50bis 60% grösser, was sich vor allem bei kleinen Paketen auswirken wird. Nicht überraschend ist dieLatenz bei synchronen Paketen: Sie liegt um den doppelten Wert der asynchronen, da ein Ping-Pong mit synchronen Paketen im wesentlichen zwei Ping-Pongs mit asynchronen Paketenentspricht.8.3.2 BandbreitenmessungNoch wichtiger als die Latenz sind die Resultate der Bandbreitenmessungen. Um die Systemejederzeit so weit wie möglich auszulasten, wurden jeweils nicht nur ein WriteFileEx aufgerufen,sondern immer mehrere pendent gehalten. Genauer gesagt, waren immer bis zu 10 Aufrufe (mitzusammen maximal 10 MB Paketspeicher) zur gleichen Zeit aktiv.Seite 88Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme100’000’00010’000’0001’000’000100’00010’0001 10 100 1’000 10’000 100’000 1’000’000 10’000’000Abb. 8.10: Bandbreiten über Named-Pipes mit 1 CPUPipes Async SyncDie genauen Resultate können in der Tabelle A.5 im Anhang A nachgelesen werden. DieAbbildungen 8.10 <strong>und</strong> 8.11 veranschaulichen die Resultate:100’000’00010’000’0001’000’000100’00010’0001 10 100 1’000 10’000 100’000 1’000’000 10’000’000Pipes Async SyncAbb. 8.11: Bandbreiten über Named-Pipes mit 2 CPUsDeutlich zu sehen sind die Auswirkungen der grösseren Latenzzeiten bei den Paketen mit Grössenunterhalb <strong>von</strong> ca. 16 kB. Bei grösseren Paketen gleichen sich alle drei Kurven an. Sogar diesynchronisierte Kommunikation kann mithalten. Bei Grössen über 128 kB wird etwa 95% derLeistung der Pipes erreicht.Interessant ist der Kreuzungspunkt der Async- <strong>und</strong> Sync-Kurve auf der Dual-CPU-Maschine beietwa 45 kB. Pakete, die grösser sind, lassen sich offensichtlich über die synchroneKommunikationsart effizienter verschicken. Gründe dafür können in Page-Faults, Cache-Misses<strong>und</strong> Konkurrenzverhalten der beiden Prozessoren vermutet werden.Diplomarbeit <strong>von</strong> Roman Roth Seite 89


Institut für ComputersystemeETH Zürich8.3.3 ProfilingÄhnlich, wie in Kapitel 8.2 wurden auch bei den Messungen über Named-Pipes Profiling-Informationen gesammelt, um allfällige Schwachstellen in den Layern zu lokalisieren. 10 malwurden die Etappen eines Sende- bzw. Empfangvorgangs ausgemessen. Der Durchschnitt dieserWerte, korrigiert um die Verfälschung durch die Messung, sind in den Tabellen 8.5 <strong>und</strong> 8.6dargestellt. Die vollständigen Tabellen sind in Angang A (Tabellen A.6 bis A10) abgedruckt.Legende:1 2 3 4 5 6 71 CPU 7.5 1.0 4.5 1.7 2.1 1.6 3.22 CPU 5.0 0.5 2.4 0.5 1.3 0.7 1.8Tab. 8.5: Zeitaufwände für das asynchrone Senden <strong>und</strong> Empfangen in µs1: Senden – Allozieren <strong>und</strong> Aufbereiten des Send-Requests2: Senden – Zeit im Zero-Copy-Layer3: Senden – Zeit im Netzwerk-Layer4: Senden Call-Back – Zeit im Netzwerk-Layer5: Senden Call-Back – Zeit im Zero-Copy-Layer6: Empfangen Call-Back – Zeit im Netzwerk-Layer7: Empfangen Call-Back – Zeit im Zero-Copy-LayerMessungen dieser Art sind auf Gr<strong>und</strong> <strong>von</strong> Störungen aus dem System (Cache, Interrupts, System-Threads, Services <strong>und</strong> Server-Prozesse) mit sehr viel Vorsicht zu interpretieren. <strong>Vergleich</strong>t mandiese Werte mit den gemessenen Latenzzeiten, auf welche sicherlich mehr Verlass ist, dannerscheinen diese Werte als eher zu klein: Rechenbeispiel für das Dual-CPU-System:Ping-Pong-Latenz Zero-Copy-Layer Asynchron: 171 µsPing-Pong-Latenz Named-Pipes: 103 µsLatenz-Differenz Ping-Pong (= 2 x Send/Recv) 68 µsLatenz-Differenz für ein Send/Recv 34 µsGeht man da<strong>von</strong> aus, dass der Sende-Call-Back sowie der Empfangs-Call-Back parallel ablaufenkönnen, dann kommt man auf eine maximale Summe aus Tabelle 8.5 <strong>von</strong> etwa 10 µs. Entwedersind diese Werte zu klein geraten, oder die Störungen durch das System wirken sich auf Dauermehr aus, als diese Zahlen aussagen können.Die Werte als absolut hinzunehmen wäre gewagt, doch relativ zueinander könnten sie durchausaussagekräftig sein: So sind die hohen Werte <strong>von</strong> 1 <strong>und</strong> 3 sehr wohl erklärbar: 1 resultiert aus derAllokation <strong>von</strong> Speicher, 3 aus dem Aufbereiten des Sende-Requests. Unverständlich dagegensind die eher hohen Werte in 5 <strong>und</strong> 7, denn in diesen beiden Abschnitten wird so gut wie nichtsgetan.Trotz dieser Schwierigkeiten in der Interpretation ist es bemerkenswert, dass die Zahlen aus denMessungen auf zwei unterschiedlichen Systemen die selben Grössenordnungen aufweisen.Deshalb soll hier auf die Werte der Messungen mit synchroner Kommunikation nicht verzichtetwerden:1 2 3 4 5 6 7 8 9 101 CPU 1.9 1.8 1.9 0.9 1.8 3.0 50.5 0.7 1.9 2.32 CPU 0.7 1.3 1.5 0.5 0.9 1.6 52.1 0.1 1.2 1.4Tab. 8.6: Zeitaufwände für das synchrone Senden <strong>und</strong> Empfangen in µsLegende: 1: Senden – Allozieren <strong>und</strong> Aufbereiten des Send-Requests2: Senden – Zeit im Zero-Copy-Layer3: Senden – Zeit im Netzwerk-Layer4: Senden Call-Back – Zeit im Netzwerk-Layer5: Senden Call-Back – Zeit im Zero-Copy-Layer6: Empfangen – Zeit im Zero-Copy-Layer (Teil 1)7: Empfangen – Zeit im Netzwerk-Layer8: Empfangen – Zeit im Zero-Copy-Layer (Teil 2)9: Empfangen Call-Back – Zeit im Netzwerk-Layer10: Empfangen Call-Back – Zeit im Zero-Copy-LayerSeite 90Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeAuffälligster Wert ist die Nummer 7, das Versenden des Paketes, das die Empfangsbereitschaftsignalisieren soll. Verglichen mit dem asynchronen Versenden <strong>von</strong> Paketen ist dieser Wertaussergewöhnlich hoch. Konkrete Gründe fehlen jedoch.Diplomarbeit <strong>von</strong> Roman Roth Seite 91


Institut für ComputersystemeETH ZürichSeite 92Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für Computersysteme9 Die ApplikationenDie bisherigen Messungen beschränken sich auf das Ermitteln <strong>von</strong> charakteristischen Werten wieBandbreite oder Latenz. Praktische Applikationen haben jedoch nur selten ein so extremesVerhalten wie diese Tests. Die gemessenen Werte sind daher nicht unbedingt aussagekräftig wasdie Leistungsfähigkeit eines Systems betrifft. Deshalb wurden zwei Anwendungen geschrieben, diediese Systeme (bzw. die Prototypen) im praktischen Einsatz untersuchen sollen.9.1 Quick-SortDie erste Applikation ist der klassische <strong>und</strong> weitverbreitete Quick-Sort [17]. Da diesesSortierverfahren nach dem Prinzip „Teile <strong>und</strong> Herrsche“ funktioniert, ist es auf einfache Weiseparallelisierbar, indem die entstandenen Teile <strong>von</strong> verschiedenen Threads weiter behandeltwerden.So wie sich die beiden Prototypen in ihren Eigenschaften sehr stark unterscheiden, sounterscheiden sich auch die beiden Implementationen.9.1.1 Quick-Sort mit MPIDie MPI-Variante entspricht weitgehend der klassischen Implementation. In einem grossen Array inThread 0 befinden sich die zu sortierenden (Integer-)Elemente. Thread 0 partitioniert diesen Arraynun gemäss Quick-Sort in zwei Teile. Das grössere Teil behält Thread 0 für dieWeiterverarbeitung, das kleinere schickt er an Thread 1. Beide Threads teilen ihr Teilstückwiederum in zwei Teile, behalten das Grössere <strong>und</strong> leiten das Kleinere an einen nächsten Threadweiter. Dabei ist klar festgelegt, welcher Thread an welche Threads delegieren kann:Thread... ...delegiert an...0 1 2 4 81 - 3 5 92 - - 6 103 - - 7 114 - - - 125 - - - 136 - - - 147 - - - 15Tab. 9.1: Delegationsschema bei 15 ThreadsSobald alle Threads ein Stück des gesamten Arrays erhalten haben, werden diese Teile lokalmittels Quick-Sort sortiert, also nicht mehr weiter delegiert. Klassischerweise würden nun diesortierten Teile den hierarchischen Weg entlang zurück zum Thread 0 geschickt. Im Beispiel <strong>von</strong>Tabelle 9.1 würde Thread 11 sein Teil an Thread 3 schicken, dieser würde das erhaltene Teil <strong>und</strong>sein Teil an Thread 1 schicken <strong>und</strong> <strong>von</strong> dort zu Thread 0 gelangen. Um Kommunikationsaufwandeinzusparen, wird der hierarchische Weg umgangen <strong>und</strong> die Teile direkt an Thread 0 geschickt.9.1.2 Quick-Sort mit OpenMPEtwas komplizierter ist die Implementation mit OpenMP. Das Problem liegt in der Eigenschaft <strong>von</strong>OpenMP, dass eine Speicherseite nur immer <strong>von</strong> einem Thread gleichzeitig beschrieben werdenkann. Leider kann beim Partitionieren des Quick-Sort-Algorithmus nicht festgelegt werden, wogeteilt wird. Im allgemeinen ist die Teilung irgendwo innerhalb einer Speicherseite. Dies hat zurFolge, dass die beiden Teile nicht parallel weiterbearbeitet werden können. Nur Teile, die nichtunmittelbar aneinander angrenzen, können parallel bearbeitet werden.Um entscheiden zu können, welche Teile bearbeitet werden, <strong>und</strong> welche nicht, wird im Master-Thread eine Liste aller Teile geführt. Nachdem ein Thread ein Teil partitioniert hat, werden dieseDiplomarbeit <strong>von</strong> Roman Roth Seite 93


Institut für ComputersystemeETH Zürichbeiden Teile am Ende der Liste angefügt. So entsteht eine Liste, die am Anfang die eher Grossen<strong>und</strong> am Ende die kleinen Teile enthält.Aus dieser Liste werden nun <strong>von</strong> links nach rechts Teile an die Threads verteilt. Dabei wird immergeprüft, dass die Teile keine gemeinsamen Speicherseiten aufweisen. In Abbildung 9.1 ist einmöglicher Ablauf abgebildet.Abb. 9.1: 5 Durchgänge eines Partitionierungsablaufs (Grau: Wird bearbeitet, Weiss: Bleibt in der Liste)Der Ablauf des Quick-Sort-Algorithmus hat in OpenMP also etwa folgende Struktur:Solange nicht vollständig sortiert{OMP_MASTERGehe durch die Liste <strong>und</strong> weise jedem Thread ein Teil zu.OMP_END_MASTEROMP_BARRIERDie Threads partitionieren das erhaltene Teil in zwei Teile.OMP_BARRIEROMP_MASTERDie entstandenen Teile werden in die Liste eingefügt.OMP_END_MASTER}Es wäre theoretisch möglich, dieses Partitionieren solange durchzuführen, bis jedes Teil nur nochaus einem Element bestünde. Spätestens dann wäre der Array sortiert. Dieses Verfahren wärejedoch ineffizient. Deshalb existieren zwei Kriterien, die das weitere Partitionieren (<strong>und</strong> damit auchdie aufwendige Listenführung) unterbinden:ŒŒWenn ein Teil kleiner als eine obere Schranke ist, dann wird es nicht mehr partitioniert,sondern durch einen Thread lokal vollständig mittels Quick-Sort sortiert. Dieses Teil ist dann zuEnde bearbeitet <strong>und</strong> wird nicht mehr in die Liste eingefügt.Wenn mindestens doppelt so viele Teile in der Liste sind, wie es Threads gibt, dann wird auchnicht mehr weiter geteilt, sondern die Teile in den nächsten Durchgängen durch die Threadsvollständig sortiert. Es müssen doppelt so viele Teile sein, weil in einem Durchgang ja nurjedes zweite Teil bearbeitet werden kann.Kommunikation <strong>und</strong> damit Zeit kann gespart werden, indem die Zuteilung der Teile auf die Threadsetwas intelligent gestaltet wird: Nachdem ein Teil durch einen Thread partitioniert wurde, werdendie beiden Teile in die Liste eingefügt. Diese Listeneinträge enthalten nicht nur die Randpositionender Teile, sondern auch, welcher Thread diese Teile zuletzt bearbeitet hat. Beim Verteilen der Teileauf die Threads wird versucht, jedes Teil dem Thread zuzuteilen, welcher dieses Teil zuletztbearbeitet hat. Dadurch können Page-Faults <strong>und</strong> damit Kommunikation gespart werden.Um die aufwendige Behandlung beim ersten Beschreiben <strong>von</strong> Speicherseiten nach einer Barrier zuumgehen, wurde der Lock-Mechanismus des OpenMP-Prototypen angewendet.9.1.3 Die PerformanceUm die Leistungsfähigkeit wenigstens grössenordnungsmässig einzuschätzen, wurden die beidenImplementationen auf folgender Testanordnung ausgemessen:Testrechner: 300 MHz Pentium II, Intel 440LX Chipset, 128 MB SpeicherKommunikation: Zero-Copy-Layer mit Named-PipesApplikationen: MPI- bzw. OpenMP-Applikationen, 2 Prozesse mit je 2 ThreadsTestgrösse: 40 MB gefüllt mit 32-bit Integer-WertenSeite 94Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeDie Resultate (Durchschnitte aus mehreren Messungen):MPI:14.7 secOpenMP ohne Lock: 24.5 secOpenMP mit Lock: 20.9 sec9.2 Gauss-EliminationAls zweite Applikation wurde die Gauss-Elimination mit partiellem Pivotieren [17] implementiert. DieSpalten einer Matrix A werden dabei ro<strong>und</strong>-robin an die einzelnen Threads verteilt. Von links nachrechts ist jeweils ein Thread für die Suche des Pivotelements in einer Spalte verantwortlich.Danach bearbeiten alle Threads die ihnen zugeteilten Spalten gemäss dem Pivotelement.9.2.1 Gauss-Elimination mit MPIDie Implementation mit MPI ist eigentlich unproblematisch. Der Master-Thread initialisiert dieMatrix. Danach sendet er ro<strong>und</strong>-robin die Spalten der Matrix an die einzelnen Threads. Thread 0sucht anschliessend in der Spalte 0 das Pivotelement, ordnet um <strong>und</strong> teilt die Elemente der Spaltedurch das Pivotelement. Dann wird die Position des Pivotelements, sowie die errechnetePivotspalte an alle Threads (inkl. sich selbst) geschickt. Parallel wird nun umgeordnet <strong>und</strong> dieSpalten neu errechnet. Danach sucht Thread 1 das Pivotelement in der Spalte 1, u.s.w.So einfach die Implementation ist, so effizient scheint der Algorithmus zu funktionieren.9.2.2 Gauss-Elimination mit OpenMPAuf den ersten Blick scheint die Implementation mit OpenMP noch einfacher als mit MPI, denn essind keine expliziten Sende- <strong>und</strong> Empfangsfunktionen notwendig. Für den optimalen Ablauf desAlgorithmus kommen jedoch zwei kleine Tricks zum Zuge, die aus einer TreadMarks-Implementation übernommen wurden.Da der OpenMP-Prototyp nur Single-Writer-Funktionalität aufweist, muss darauf geachtet werden,dass die Daten der Matrix etwas intelligent im Speicher abgelegt werden. Da jede Spalte einemThread zugeteilt wird, müssen die Daten jeder Spalte in separaten Speicherseiten liegen.Für den korrekten Ablauf des Algorithmus wären eigentlich zwei Barriers notwendig. Es kannjedoch eine eingespart werden, wenn die Position des gef<strong>und</strong>enen Pivotelementsabwechslungsweise in zwei Variablen gespeichert wird. Daher die folgenden Zeilen im Code:<strong>und</strong>if (cur_pivot & 0x01)*pivot_odd = pivot_col;else*pivot_even = pivot_col;pivot_col = (cur_pivot & 0x01) ? *pivot_odd : *pivot_even;Obwohl der Algorithmus sehr elegant aussieht, arbeitet er im OpenMP-Prototypen nur sehr träge.Der Gr<strong>und</strong>ablauf des Algorithmus sieht etwa folgendermassen aus:Für jede Spalte{Pivotelement suchenBarrierParallel die Spalten bearbeiten}Parallel die Spalten bearbeiten heisst, dass die Speicherseiten beschrieben <strong>und</strong> damitvom Read-Only- in den Read/Write-Zustand übergehen. Bei der Barrier werden die Seiten dannwieder in den Read-Only-Zustand zurückgesetzt. Bei einer 1024x1024-Matrix passiert dies über500'000 mal! Dies ist insofern tragisch, weil der Besitzer der Seite selbst die Seite beschreibt <strong>und</strong>daher jedesmal eine Kopie der unveränderten Seite angelegt wird, was unheimlich viel Zeitbeansprucht.Diplomarbeit <strong>von</strong> Roman Roth Seite 95


Institut für ComputersystemeETH ZürichDiese Tatsache war der Auslöser, warum der Lock-Mechanismus in den OpenMP-Prototypeneingebaut wurde.9.2.3 Die PerformanceAuch diese Messungen zeigen höchstens eine Grössenordnung der Leistungsfähigkeit derPrototypen. Sie zeigen jedoch sehr deutlich, welche Auswirkung der nachträglich eingebaute Lock-Mechanismus hat.Testrechner: 300 MHz Pentium II, Intel 440LX Chipset, 128 MB SpeicherKommunikation: Zero-Copy-Layer mit Named-PipesApplikationen: MPI- bzw. OpenMP-Applikationen, 2 Prozesse mit je 2 ThreadsTestgrösse: 2000x2000 / 3000x3000 Matrix gefüllt mit Werten vom Typ FloatDie Resultate (Durchschnitte aus mehreren Messungen):MPI:183 sec / 627 secOpenMP ohne Lock: 606 sec / 1950 secOpenMP mit Lock: 214 sec / 703 sec9.3 Bessere TestapplikationenDie beiden vorgestellten Applikationen haben eine Gemeinsamkeit: Die Daten werden in Arraysgespeichert. Diese Tatsache bevorzugt den MPI-Prototypen. Man kann sich jedoch Applikationenausdenken, die auf komplexeren Datenstrukturen wie Listen, Bäume oder anderen Graphenoperieren. Bei solchen Applikationen könnte OpenMP unter Umständen besser abschneiden alsMPI. Der Gr<strong>und</strong> liegt darin, dass OpenMP-Applikationen im <strong>Distributed</strong>-<strong>Shared</strong>-<strong>Memory</strong> problemlosmit Pointern arbeiten können <strong>und</strong> zwar auch über die Grenzen eines Knotens hinweg. Für einenproblemlosen Einsatz wären jedoch weitere Synchronisationselemente im OpenMP-Prototypenvorteilhaft. Bei MPI müssten solche Datenstrukturen durch aufwendiges Verpacken in einenserialisierten Zustand gebracht <strong>und</strong> auf Empfängerseite wieder Entpackt werden.Seite 96Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeA MessresultateA.1 Bandbreitenmessungen über SocketsA.1.1 100 mbit EthernetRechner: 2 x Dell Optiplex GX1 (350 MHz Pentium II, 64 MB RAM)Netzwerkkarte: DEC DE500Switch: Bay Networks Bay Stock 100 Base-T HubPaketgrösse[Bytes]SocketBlockingSocketNon-BlockingZero-CopyAsyncZero-CopySync4 54’996 43’991 45’620 12’434 21’9465 69’553 55’855 56’069 15’606 27’3658 111’077 88’319 76’445 25’030 43’90511 152’562 120’500 109’667 34’348 60’38016 221’022 174’482 158’037 49’972 87’99722 304’448 239’543 208’096 68’856 121’04132 445’758 346’929 304’819 98’622 176’28045 636’350 487’052 456’954 138’130 248’99264 907’777 697’995 629’421 196’560 350’89790 1’270’907 974’084 833’165 277’633 496’461128 1’725’678 1’379’231 1’398’431 393’561 738’345181 2’768’770 1’926’642 2’065’401 555’980 1’117’500256 3’782’779 2’630’690 2’665’605 808’846 1’646’003362 8’828’678 3’615’504 4’220’873 1’194’788 2’332’924512 9’134’447 5’680’973 5’691’109 1’723’022 3’551’672724 9'270'441 8’690’872 7’027’627 2’429’465 4’425’1161’024 9’053’752 8’711’161 7’998’514 3’454’251 5’419’0191’448 8’697’981 8’260’175 8’667’363 4’877’635 6’607’0702’048 8’699’449 8’523’320 8’607’324 5’948’409 7’599’2772’896 8’721’846 8’593’647 8’616’200 7’955’021 8’331’3994’096 8’734’019 8’608’574 8’616’452 8’345’502 8’013’1595’792 8’762’111 8’663’228 8’651’595 8’308’915 8’759’1768’192 8’764’561 8’664’225 8’665’482 8’415’849 8'976'37311’585 8’758’396 8’685’240 8'751'107 8’518’676 8’789’94716’384 8’749’764 8’714’030 8’705’709 8’732’694 6’590’64823’170 8’752’116 8'724'075 8’705’502 8’627’624 7’106’40432’768 8’741’652 8’710’182 8’650’490 8'756'837 7’328’17546’340 8’770’333 8’669’397 8’624’518 8’702’907 7’643’93665’536 8’766’105 8’594’386 7’069’033 8’651’562 7’815’32392’681 8’714’285 7’333’465 6’927’434 8’659’903 7’892’926131’072 8’497’871 6’808’069 8’455’402 8’562’302 8’044’789185’363 8’411’002 7’369’514 7’218’192 8’332’272 8’072’678262’144 8’362’823 7’553’335 8’119’009 8’117’108370’727 8’279’703 7’474’567 8’049’844 8’128’147524’288 8’370’618 7’175’932 7’809’689 8’219’072741’455 8’375’549 7’119’991 7’548’803 8’066’3831’048’576 8’234’623 6’535’936 6’486’514 7’873’4921’482’910 8’469’755 7’089’308 7’094’128 7’004’6072’097’152 8’394’041 6’425’269 5’881’529 6’439’751Tab. A.1: Bandbreitenmessungen über 100 mbit Ethernet (in Bytes/Sec)MpiDiplomarbeit <strong>von</strong> Roman Roth Seite 97


Institut für ComputersystemeETH ZürichA.1.2 GigaBit EthernetRechner: 2 x Dell Optiplex GX1 (350 MHz Pentium II, 64 MB RAM)Netzwerkkarte: Packet Engine G-NIC IISwitch: -Paketgrösse[Bytes]SocketBlockingSocketNon-BlockingZero-CopyAsyncZero-CopySync4 123’456 83’581 85’273 17’843 26’2545 167’887 105’028 106’590 22’778 32’8848 246’429 167’338 166’477 36’608 52’59011 337’289 228’468 228’872 49’562 72’31116 489’790 332’897 334’015 72’534 104’96822 674’815 458’856 462’121 99’831 143’93532 1’133’563 662’752 660’569 148’499 209’71645 1’460’136 919’690 922’704 206’562 293’80964 1’915’571 1’311’137 1’283’190 294’486 418’67290 2’684’673 1’836’212 1’727’749 410’055 595’213128 3’835’174 2’591’043 2’353’300 586’763 859’430181 5’427’085 3’576’135 3’135’071 807’709 1’254’300256 10’385’595 5’586’733 4’101’161 1’159’375 1’991’939362 11’336’790 8’006’402 5’283’993 1’650’326 2’985’255512 11’980’283 9’442’800 6’644’383 2’289’405 3’869’643724 12’482’601 6’114’055 8’209’358 3’194’758 4’856’1601’024 13’121’088 6’840’167 10’066’012 4’596’783 5’948’1751’448 13’406’839 7’937’970 12’511’533 6’072’338 7’130’9272’048 13’616’559 9’124’591 12’551’758 8’248’959 8’961’9002’896 13’708’318 10’120’700 13'132'723 10’951’334 9’980’2484’096 13’492’677 11’022’321 12’303’803 12’918’575 10’773’4565’792 13’413’438 11’561’090 12’076’869 12’411’887 12’442’0858’192 13’334’689 11’864’915 11’949’819 11’947’817 12'801'60311’585 13’532’189 12’316’523 12’420’237 11’699’524 13’094’49316’384 13’644’530 12’596’374 12’238’128 11’800’447 8’696’63323’170 14’210’219 12’456’501 12’562’554 12’170’834 9’872’46832’768 13’807’020 12’822’442 12’321’238 12’295’229 10’529’67546’340 14’289’500 12’457’813 13’114’506 12’262’920 11’228’14565’536 14'291'537 12'870'325 12’234’753 12'659'866 11’375’86692’681 13’880’918 12’388’551 11’950’349 12’487’098 12’222’666131’072 13’245’427 9’450’743 11’718’438 12’132’009 11’869’551185’363 12’826’451 11’870’490 11’638’225 11’920’650 11’998’598262’144 12’536’804 9’737’235 11’676’760 11’884’766370’727 12’163’420 10’974’841 10’526’484 11’357’897524’288 12’092’416 9’740’687 11’397’756 11’358’641741’455 11’679’429 11’563’440 11’842’487 11’813’2221’048’576 11’469’509 11’345’653 11’340’470 11’199’1401’482’910 11’405’639 9’833’229 7’724’370 9’599’3182’097’152 11’925’728 8’517’249 8’477’555 8’336’891Tab. A.2: Bandbreitenmessungen über GigaBit Ethernet (in Bytes/Sec)MpiSeite 98Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeA.1.3 Lokale Loopback-MessungRechner: 1 x Dell Percision 410 (2 x 400 MHz Pentium II, 256 MB RAM)Netzwerkkarte: -Switch: -Paketgrösse[Bytes]SocketBlockingSocketNon-Blocking4 102’096 58’4265 126’980 73’4138 202’728 115’94811 320’870 157’21016 467’630 232’33022 566’252 268’33432 869’472 376’82645 1’195’194 517’12964 1’988’722 761’22190 3’123’465 1’068’847128 4’605’912 1’518’304181 4’798’344 2’135’814256 7’494’059 3’051’671362 10’482’579 4’857’138512 11’554’861 6’659’764724 12’791’992 8’367’6221’024 15’973’089 11’257’2911’448 15’972’702 11’409’0872’048 23’343’592 13’844’5672’896 24’006’862 16’770’4184’096 23’506’675 19’160’9655’792 23’718’184 23’345’3828’192 23’459’579 23’212’65211’585 23’924’787 23’355’59116’384 24'665'376 23'646'62923’170 24’140’012 22’253’47732’768 24’544’301 22’026’57946’340 23’853’738 22’812’89665’536 23’472’457 22’090’93692’681 21’916’876 18’104’906131’072 24’304’191 16’221’008185’363 20’302’224 17’170’430262’144 19’670’955 15’955’291370’727 17’871’444 16’260’570524’288 18’783’060 12’833’012741’455 18’450’868 12’119’9471’048’576 21’154’379 11’328’4331’482’910 19’403’937 10’233’3152’097’152 20’664’569 9’386’002Tab. A.3: Lokale Bandbreitenmessungen über Sockets (in Bytes/Sec)Diplomarbeit <strong>von</strong> Roman Roth Seite 99


Institut für ComputersystemeETH ZürichA.1.4 Myricom GM-SocketsRechner: 2 x Dell Optiplex GX1 (350 MHz Pentium II, 64 MB RAM)Netzwerkkarte: Myrinet (SAN)Switch: 16-Port Myrinet SwitchSoftware: Myricom GM Release 0.18Paketgrösse[Bytes]SocketBlockingSocketNon-Blocking4 50’530 37’7585 67’235 48’4748 122’057 82’17211 175’914 118’99916 296’948 183’15022 464’112 282’90132 770’696 443’20745 1’160’087 694’10664 1’712’456 1’016’69790 2’023’826 1’722’522128 2’937’631 2’523’175181 3’584’414 2’713’326256 5’168’109 3’088’539362 7’376’160 3’642’270512 15’755’391 4’782’396724 15’773’333 6’726’3561’024 14’852’353 14’814’9451’448 15’350’975 15’359’7172’048 15’455’699 15’310’6342’896 15’658’666 15’635’8484’096 20’749’355 10’207’3545’792 19’839’824 13’089’8048’192 20’464’463 13’697’46111’585 16’483’654 16’379’38316’384 22’109’863 13’178’06023’170 18’609’099 17’937’89332’768 22’674’253 18’297’07346’340 23'594'215 18'504'17465’536 22’635’857 17’641’72292’681 15’811’072 6’589’520131’072 19’808’889 9’187’608185’363 14’776’908 9’518’756262’144 18’037’170 7’286’451370’727 13’855’838 9’139’805524’288 17’533’567 6’105’283741’455 14’038’727 7’055’6261’048’576 16’744’369 5’071’1281’482’910 14’599’650 5’119’1712’097’152 15’454’516 5’015’582Tab. A.4: Bandbreitenmessungen über GM-Sockets (in Bytes/Sec)Seite 100Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeA.2 Bandbreitenmessungen über Named-PipesRechner: Dell Percision 410 (2 x 400 MHz Pentium II, Intel 440BX, 256 MB RAM)Netzwerkkarte: -Switch: -Paket- 1 CPU 2 CPUgrösse Pipes Async Sync Pipes Async Sync4 98’615 62’310 24’378 130’857 67’218 32’0775 122’800 78’115 30’408 163’206 84’345 39’8548 197’572 124’785 48’698 261’581 134’794 63’94311 271’684 171’291 67’058 359’234 185’209 87’96216 396’429 248’704 97’279 521’993 269’018 127’33222 543’064 343’355 134’083 718’722 370’838 175’33932 797’689 498’445 194’931 1’045’359 539’393 250’09645 1’119’691 690’126 274’562 1’463’785 751’709 351’26964 1’578’044 985’278 391’354 2’069’533 1’070’124 486’17790 2’222’874 1’377’521 546’812 2’898’890 1’499’173 678’732128 3’171’192 1’972’087 778’124 4’051’137 2’119’756 950’360181 4’478’139 2’785’146 1’099’850 5’671’765 2’978’591 1’338’408256 6’022’945 3’743’694 1’526’303 7’755’867 4’065’839 1’754’683362 8’414’524 5’171’897 2’140’388 10’751’080 5’603’773 2’430’329512 11’405’972 7’231’577 3’016’747 14’969’783 7’779’323 3’403’342724 15’540’611 9’885’083 4’258’159 20’258’248 10’734’784 4’780’5591’024 20’517’070 13’055’473 5’901’445 27’377’094 14’627’115 6’671’1451’448 26’856’172 17’050’653 8’174’477 34’099’792 18’669’172 9’204’6352’048 32’659’801 20’073’940 10’721’284 37’141’765 18’820’610 12’254’2912’896 42’097’984 25’699’699 14’762’244 44’764’172 24’091’137 16’502’0884’096 47’971’779 32’709’022 19’159’218 54’364’284 32’574’340 21’953’0985’792 49’111’268 38’532’175 23’417’030 61’177’733 39’132’068 27’342’9688’192 55’057’993 43’439’881 29’894’493 67’841’896 54’603’349 32’912’61811’585 59’406’194 46’636’312 35’034’064 73’738’747 59’301’363 39’553’24516’384 68’483’262 51’250’790 41’744’493 72’592’267 71’382’235 47’376’08223’170 60’738’994 54’383’781 48’406’003 75’866’878 71'532'729 54’626’58632’768 60’792’587 55’441’593 53’897’599 90'399'667 67’659’454 61’435’03546’340 61’750’378 55’686’311 56’551’538 89’921’865 69’217’898 67’257’42565’536 63’141’766 56’490’590 59’089’945 86’621’218 60’318’997 72’888’94092’681 63’292’750 56’832’676 60’828’272 87’964’562 61’590’077 76’495’211131’072 63'375'716 57'104'851 62'908'964 87’913’770 62’666’723 78'118'880185’363 58’040’674 56’951’159 61’535’786 82’120’846 62’823’215 77’984’317262’144 56’988’153 55’668’442 57’815’080 72’533’257 62’507’161 71’695’879370’727 57’974’799 52’193’870 53’686’176 69’157’783 60’702’029 64’441’829524’288 53’239’343 53’695’578 62’424’659 62’365’431741’455 54’517’855 53’976’238 63’971’643 61’346’8691’048’576 54’613’485 53’974’596 63’488’071 61’193’2911’482’910 54’799’311 54’446’954 63’232’111 60’590’6412’097’152 54’156’070 54’642’376 61’193’706 60’565’4912’965’820 55’219’568 55’006’411 55’732’203 55’112’3424’194’304 55’426’418 55’278’100 56’796’690 56’070’456Tab. A.5: Bandbreitenmessungen lokal über Named-Pipes (in Bytes/Sec)Diplomarbeit <strong>von</strong> Roman Roth Seite 101


Institut für ComputersystemeETH ZürichA.3 Profiling-Informationen über Named-Pipes1 CPU 2 CPUsRechnertyp Dell Optiplex GX1 Dell Precision 410CPU-Taktrate 350 MHz 400 MHzSpeicher 64 MB 256 MBPerformance Counter Frequenz 1‘193‘182 Hz 398‘790‘000 HzVerbrauch der Messung min. 5 Takte min. 550 TakteTab. A.6: Angaben zu den Rechnern <strong>und</strong> den Performance-CounternA.3.1 Asynchrone Kommunikation1 2 3 4 5 6 7Serie 1 54 6 14 7 7 7 9Serie 2 9 6 9 9 7 6 9Serie 3 9 7 10 6 8 7 8Serie 4 9 7 9 7 7 7 9Serie 5 9 6 13 7 8 7 9Serie 6 10 6 9 7 7 7 10Serie 7 9 6 9 7 7 7 9Serie 8 10 5 10 6 8 7 9Serie 9 9 7 11 7 9 7 8Serie 10 11 6 10 7 7 7 8Durchschnitt 13.9 6.2 10.4 7.0 7.5 6.9 8.8Korrigiert 8.9 1.2 5.4 2.0 2.5 1.9 3.8Zeit [µs] 7.5 1.0 4.5 1.7 2.1 1.6 3.2Tab. A.7: Gemessene Profilingwerte bei 1 CPU in Takten (bzw. µs)1 2 3 4 5 6 7Serie 1 14'713 825 3'141 1'069 1'223 1'123 1'610Serie 2 1'368 766 1'478 728 1'078 815 1'262Serie 3 1'207 770 1'342 728 951 720 1'225Serie 4 1'145 708 1'270 708 926 819 1'190Serie 5 1'148 725 1'228 724 1'032 788 1'249Serie 6 1'227 748 1'416 696 1'056 857 1'294Serie 7 1'089 746 1'204 696 1'239 817 1'268Serie 8 1'033 735 1'224 732 1'002 799 1'218Serie 9 1'196 707 1'288 737 1'053 816 1'273Serie 10 1'193 745 1'435 724 1'079 783 1'234Durchschnitt 2'531.9 747.5 1'502.6 754.2 1'063.9 833.7 1'282.3Korrigiert 1'981.9 197.5 952.6 204.2 513.9 283.7 732.3Zeit [µs] 5.0 0.5 2.4 0.5 1.3 0.7 1.8Tab. A.8: Gemessene Profilingwerte bei 2 CPUs in Takten (bzw. µs)Legende:1: Senden – Allozieren <strong>und</strong> Aufbereiten des Send-Requests2: Senden – Zeit im Zero-Copy-Layer3: Senden – Zeit im Netzwerk-Layer4: Senden Call-Back – Zeit im Netzwerk-Layer5: Senden Call-Back – Zeit im Zero-Copy-Layer6: Empfangen Call-Back – Zeit im Netzwerk-Layer7: Empfangen Call-Back – Zeit im Zero-Copy-LayerSeite 102Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeA.3.2 Synchrone Kommunikation1 2 3 4 5 6 7 8 9 10Serie 1 10 7 7 6 8 9 80 6 7 8Serie 2 7 7 7 6 7 8 74 6 8 8Serie 3 8 6 8 6 7 8 79 5 7 8Serie 4 7 7 7 6 7 8 74 6 7 8Serie 5 7 7 7 6 7 8 74 5 7 8Serie 6 7 7 8 6 8 9 78 7 8 8Serie 7 6 8 7 6 7 9 73 5 9 7Serie 8 7 7 8 6 7 9 73 6 7 7Serie 9 8 7 7 6 7 9 76 6 7 7Serie 10 6 8 7 7 6 9 71 6 6 8Durchschnitt 7.3 7.1 7.3 6.1 7.1 8.6 75.2 5.8 7.3 7.7Korrigiert 2.3 2.1 2.3 1.1 2.1 3.6 60.2 0.8 2.3 2.7Zeit [µs] 1.9 1.8 1.9 0.9 1.8 3.0 50.5 0.7 1.9 2.3Tab. A.9: Gemessene Profilingwerte bei 1 CPU in Takten (bzw. µs)1 2 3 4 5 6 7 8 9 10Serie 1 1'457 1'275 1'706 942 939 1'600 25'182 602 1'183 1'168Serie 2 777 1'055 1'073 731 923 1'212 23'980 577 979 1'046Serie 3 736 1'022 1'018 753 876 1'162 25'082 562 943 1'067Serie 4 720 976 1'031 685 972 1'114 23'942 567 959 1'051Serie 5 935 1'148 1'299 684 889 1'313 22'057 568 1'115 1'280Serie 6 768 955 1'106 698 928 1'082 20'630 567 1'035 1'107Serie 7 759 1'000 1'052 735 925 1'191 21'239 567 973 1'049Serie 8 754 1'047 992 699 902 1'252 20'968 567 997 1'074Serie 9 770 1'048 998 699 994 1'011 20'423 567 1'007 1'043Serie 10 787 980 1'089 735 880 1'056 20'738 567 1'021 1'074Durchschnitt 846.3 1'050.6 1'136.4 736.1 922.8 1'199.3 22'424 571.1 1'021.2 1'095.9Korrigiert 296.3 500.6 586.4 186.1 372.8 649.3 20'774 21.1 471.2 545.9Zeit [µs] 0.7 1.3 1.5 0.5 0.9 1.6 52.1 0.1 1.2 1.4Tab. A.10: Gemessene Profilingwerte bei 2 CPUs in Takten (bzw. µs)Legende: 1: Senden – Allozieren <strong>und</strong> Aufbereiten des Send-Requests2: Senden – Zeit im Zero-Copy-Layer3: Senden – Zeit im Netzwerk-Layer4: Senden Call-Back – Zeit im Netzwerk-Layer5: Senden Call-Back – Zeit im Zero-Copy-Layer6: Empfangen – Zeit im Zero-Copy-Layer (Teil 1)7: Empfangen – Zeit im Netzwerk-Layer8: Empfangen – Zeit im Zero-Copy-Layer (Teil 2)9: Empfangen Call-Back – Zeit im Netzwerk-Layer10: Empfangen Call-Back – Zeit im Zero-Copy-LayerDiplomarbeit <strong>von</strong> Roman Roth Seite 103


Institut für ComputersystemeETH ZürichSeite 104Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeB AufgabenstellungAbb. B.1: Aufgabenstellung, Seite 1Diplomarbeit <strong>von</strong> Roman Roth Seite 105


Institut für ComputersystemeETH ZürichAbb. B.2: Aufgabenstellung, Seite 2Seite 106Diplomarbeit <strong>von</strong> Roman Roth


ETH ZürichInstitut für ComputersystemeC Quellenverzeichnis[1] Honghui Lu, Sandhya Dwarkadas, Alan L. Cox, Willy Zwaenepoel:<strong>Message</strong> <strong>Passing</strong> Versus <strong>Distributed</strong> <strong>Shared</strong> <strong>Memory</strong> on Networks of WorkstationsRice University, 1995[2] OpenMP Architecture Review Board:OpenMP Fortran Application Program Interface 1.0Oktober 1997[3] OpenMP: A Proposed Industry Standard API for <strong>Shared</strong> <strong>Memory</strong> ProgrammingOktober 1997[4] Cristiana Amza, Alan L. Cox, Sandhya Dwarkadas, Pete Keleher, Honghui Lu, RamakirshnanRajamony, Weimin Yu, Willi Zwaenepoel:TreadMarks: <strong>Shared</strong> <strong>Memory</strong> Computing on Networks of WorkstationsRice University, 1996[5] Willi Zwaenepoel:TreadMarks <strong>Shared</strong> <strong>Memory</strong> Computing on a Network of PCsRice University[6] <strong>Message</strong> <strong>Passing</strong> Interface Forum:MPI: A <strong>Message</strong>-<strong>Passing</strong> Interface StandardJuni 1995[7] <strong>Message</strong> <strong>Passing</strong> Interface Forum:MPI-2: Extensions to the <strong>Message</strong>-<strong>Passing</strong> InterfaceJuli 1997[8] David A. Solomon:Inside Windows NT, Second EditionMicrosoft Press, 1998[9] Art Baker:The Windows NT Device Driver BookPrentice Hall PTR, 1997[10] Peter G. Viscarola, W. Anthony Mason:Windows NT Device Driver DevelopmentMaxMillan Technical Publishing, 1998[11] Rajeev Nagar:Windows NT File System InternalsO’Reilly, September 1997[12] Win32 Software Development KitMicrosoft, 1996[13] Windows NT Version 4.0 Device Driver KitMicrosoft, 1996[14] Christopher A. L. Vinckier:<strong>Distributed</strong> <strong>Shared</strong> <strong>Memory</strong> with MyrinetUniversity of Ghent, 1998[15] Myricom:The GM <strong>Message</strong> <strong>Passing</strong> Systemhttp://www.myri.com/gm/doc/gm.pdf, 1998[16] Myricom:Myrinet: A Gigabit-per-Second Local-Area Networkhttp://www.myri.com/research/publications/hot.ps, 1995[17] Robert Sedgewick:Algorithmen in C++Addison-Wesley, 1992Diplomarbeit <strong>von</strong> Roman Roth Seite 107

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!