Racedetectie in Parallelle Programma s door Gecontroleerde Heruitvoering

Vergelijkbare documenten
Les 9: Meerdradige uitvoering

Examen Geavanceerde Computerarchitectuur

Centrale begrippen hoofdstuk 3. Waarom multiprogramming? Vandaag. processen proces state: running, ready, blocked,... Vragen??

Geheugenmodellen voor parallelle en gedistribueerde systemen

Nederlandse samenvatting (Dutch summary)

Computerarchitectuur en netwerken Toets 1 4 okt

Cover Page. The handle holds various files of this Leiden University dissertation

informatica. hardware. overzicht. moederbord CPU RAM GPU architectuur (vwo)

Geheugenbeheer. ICT Infrastructuren 2 december 2013

Digitale en analoge technieken

computerarchitectuur antwoorden

Computerarchitectuur. Terugblik / discussie / oefenopgaven

Tim Mallezie Architectuur van besturingssystemen: Vraag A2.

TI-2720 Operating System Concepten. 6 november 2012, uur. docent: H.J. Sips. Dit is een tentamen met 9 open vragen

1 Aanvulling cosy deeltijd

Examen besturingssystemen

Computerarchitectuur. H&P Ch 5. Thread-Level Parallelism

Examen besturingssystemen

Virtueel Geheugen en demand paging (1)

Deel I Hoofdstuk 4: Modelleren van Toestand

ONTWERP VAN GEDISTRIBUEERDE SOFTWARE ACADEMIEJAAR STE EXAMENPERIODE, 15 JANUARI 2010, 14U 17U30 VRAAG 1: INLEIDENDE BEGRIPPEN[20 MIN]

Examen besturingssystemen

Hoofdstuk 3: Processen: Beschrijving en Besturing. Wat is een proces? Waarom processen? Wat moet het OS ervoor doen? Is het OS zelf een proces?

Software Processen. Ian Sommerville 2004 Software Engineering, 7th edition. Chapter 4 Slide 1. Het software proces

Geheugenbeheer. ICT Infrastructuren. hoofdstukken 7 en 8.1

Gelijktijdigheid: Wederzijdse Uitsluiting & Synchronisatie Concurrency: Mutual Exclusion & Synchonization (5e ed: , Appendix A.

Computerarchitectuur. App. B. Review of Memory Hierarchy

ONTWERP VAN GEDISTRIBUEERDE SOFTWARE ACADEMIEJAAR STE EXAMENPERIODE, 23 JANUARI 2012, 8U30 12U00 VRAAG 1: VERDEELDE SYSTEMEN [10 MIN]

imangine Stichting SchoolLan

TECHNISCHE UNIVERSITEIT EINDHOVEN ComputerSystemen Deeltentamen B (weken 6..9) vakcode 2M208 woensdag 19 Maart 2003, 9:00-10:30

Examen Geavanceerde Computerarchitectuur

Semaforen. Semaforen p. 1/2

De Arduino-microcontroller in de motorvoertuigentechniek (2)

Cover Page. The handle holds various files of this Leiden University dissertation.

Gedistribueerd Programmeren - Samenvatting

2 Algemene opbouw van een computersysteem

ICT Infrastructuren: Processen en Threads. 18 november 2013 David N. Jansen

Sequentiële Logica. Processoren 24 november 2014

Voorkennis: C, basiskennis microprocessoren (bij voorkeur ARM7 processor)

Algoritmisch denken: analyseren, ontwerpen, beschrijven van oplossingen en implementeren met Alice

Visual Basic.NET. Visual Basic.NET. M. den Besten 0.3 VB. NET

Computertechniek vorige examens

De AT90CAN microprocessor van ATMEL in de motorvoertuigentechniek (2)

Multi-core systemen. door Alexander Melchior

Tentamen Computersystemen

n-queens minimale dominantie verzamelingen Chessboard Domination on Programmable Graphics Hardware door Nathan Cournik

G. Schottert Handleiding Freekie 1. Nederlandse handleiding. Freekie DMX ADRES INSTELLINGEN 1

Examen computerarchitectuur

Handleiding bij de Booktest Generator

In Vlaanderen bestaat er nog geen leerlijn programmeren! Hierdoor baseren wij ons op de leerlijn die men in Nederland toepast voor basisscholen.

Internet Veiligheidspakket van KPN Handleiding Windows XP, Vista, 7,8 Versie

1=2720/2725 Operating System Concepten

Van Poort tot Pipeline. Ben Bruidegom & Wouter Koolen-Wijkstra AMSTEL Instituut Universiteit van Amsterdam

WELKOM BIJ BOMBERBOT! LES 2: SEQUENTIES I LES 2: SEQUENTIES I WAAR GAAT DEZE LES OVER? INTRODUCTIE

' Het tentamen is gesloten boek, dus het is niet toegestaan om het tekstboek, slides of eigen gemaakte aantekeningen te gebruiken.

Temperatuur logger synchronisatie

Opgaven Registers Concurrency, 29 nov 2018, Werkgroep.

Examen besturingssystemen

Uitwerking oefententamen Computerarchitectuur December 2016

VHDL overzicht. Digitale Systemen (ET1 410) VHDL? VHDL? Sequentieel vs. Concurrent 2/15/2011

Sequentiële gepijplijnde machine

Inhoudsopgave. Hoofdstuk 1.RMI...2

Succes! Theo DʼHondt 13 juni 2010

Informatie & Databases

Departement industriële wetenschappen en technologie

von-neumann-architectuur Opbouw van een CPU Processoren 1 december 2014

Herconfigureerbare Hardware in Ieders Bereik

Hoofdstuk 7: Als Excel vastloopt

UML. From weblog Dennis Snippert

Opdracht 1 Topics on Parsing and Formal Languages - fall 2010

High Performance Computing

Les 11: systeemarchitectuur virtuele machines

AFO 139 Automatische export

Let op dat de scoping regels gerespecteerd blijven; het volgende voorbeeld mag geen fout melden.

Vraag 1 (2 punten) (iii) Een lees-opdracht van virtueel adres 2148 seg 0, offset idem

Inhoud vandaag. Interrupts. Algemeen ARM7 AIC

3. Structuren in de taal

TECHNISCHE UNIVERSITEIT EINDHOVEN FACULTEIT DER TECHNISCHE NATUURKUNDE

Examen besturingssystemen

Examen Programmeren 2e Bachelor Elektrotechniek en Computerwetenschappen Faculteit Ingenieurswetenschappen Academiejaar juni 2011

Opgave Tussentijdse Oefeningen Jaarproject I Reeks 4: Lcd Interface & Files

AFO 142 Titel Aanwinsten Geschiedenis

IMPULSFONDS VOOR HET MIGRANTENBELEID

Studie en implementatie van input replay door Frank Cornelis

INSTALLEREN MET SYMANTEC GHOST

OCI koppeling webshop leveranciers

Cover Page. Author: Vu, Van Thieu Title: Opportunities for performance optimization of applications through code generation Issue Date:

Examen computerarchitectuur

College 13: Patterns (2)

HANDLEIDING VAN DATARECORDER SOFTWARE (FOR WS-9010)

Besturing van de Miniatuurwereld OC32. Apparaatdefinities Servo s en gerelateerde zaken

Vakinhoudelijke uitwerking Keuzevak Applicatieontwikkeling van het profiel MVI vmbo beroepsgericht

OPTIMALISATIE VAN MPEG-4-WAVELETCODE VOOR DE TRIMEDIAPROCESSOR

2. Syntaxis en semantiek

EWMA Control Charts in Statistical Process Monitoring I.M. Zwetsloot

HET BESTURINGSSYSTEEM

Scan-pad technieken. Zet elk register om in een scan-pad register (twee opeenvolgende D-latches: master-slave):

TI-2720 Operating System Concepten. 21 januari 2013, uur. docent: H.J. Sips. Dit is een tentamen met 9 open vragen

Digitale Systemen (ET1 410)

Een computerprogramma is opgebouwd uit een aantal instructies die op elkaar volgen en die normaal na elkaar uitgevoerd worden.

Transcriptie:

Racedetectie in Parallelle Programma s door Gecontroleerde Heruitvoering Michiel Ronsse Promotoren: Prof. dr. ir. K. De Bosschere Prof. dr. ir. J. Van Campenhout Proefschrift ingediend tot het behalen van de graad van Doctor in de Toegepaste Wetenschappen Vakgroep Elektronica en Informatiesystemen Voorzitter: Prof. dr. ir. J. Van Campenhout Faculteit Toegepaste Wetenschappen Academiejaar 1998 1999

Aan mijn ouders, Walter Ronsse en Laura Brys

i Dankwoord Nu de resultaten van een jarenlang onderzoek zijn neergeschreven in een doctoraal proefschrift is het moment aangebroken om een aantal mensen te bedanken. Mijn dank gaat in de eerste plaats uit naar mijn promotoren: Prof. Jan Van Campenhout en Prof. Koen De Bosschere. Prof. Van Campenhout was mijn oorspronkelijke promotor, maar naarmate het leiderschap van de vakgroep ELIS meer tijd in beslag begon te nemen nam Prof. De Bosschere deze taak over. Als eerste promovendus van Prof. De Bosschere kan ik getuigen dat hij deze taak met verve volbracht heeft. Aangezien mijn onderzoek in het GOA-project FORMOSA past, werden er nogal wat FORMOSA-vergaderingen aan gewijd. Daarom dank aan de leden van dit project. Naast mijn twee promotoren zijn dit Prof. Albert Hoogewijs en Noemie Slaats van de Vakgroep Zuivere Wiskunde en Computeralgebra en Prof. Erik D Hollander, Bart Van Assche en Henk Neefs van ELIS. Eveneens dank aan de oude collega s die mij op het juiste debugpad gezet hebben: Luk Levrouw, Koen Audenaert en Koen Bastiaens. Ik ben eveneens dank verschuldigd aan Prof. Willy Zwaenepoel (Rice University, Houston (TX)) omdat hij mij de mogelijkheid geboden heeft om gedurende twee maanden onderzoek te verrichten in zijn departement. Mijn dank gaat ook uit naar Prof. Jacques Chassin de Kergommeaux (LMC-IMAG, Université Joseph Fourier te Grenoble) voor de samenwerking in het kader van een Tournesol-project. Jacques, je voudrais vous remercier pour votre excellente coopération dans le cadre du projet Tournesol. J ai apprécié entre autre l environnement chaleureux qui régnait lors de ma visite d un mois à Grenoble. Dank aan de leden van de PARIS-groep in het algemeen en aan Mark Christiaens in het bijzonder (de nieuwe debugger) en aan de leden van ELIS, vooral de personen die s middags warm eten en de koffiedrinkers. Bijzondere dank gaat uit naar het IWT voor de financiële steun gedurende vier jaar en aan de kritische proeflezers van dit proefschrift (Mark Chris-

ii tiaens, Koen De Bosschere en Jan Van Campenhout). Tenslotte dank aan de personen die gezorgd hebben voor de belangrijkste steun: de morele steun. Daarom, dank aan mijn ouders en broers. Michiel Ronsse Gent, 28 April 1999

Inhoud 1 Inleiding 1 1.1 Debuggen........................... 1 1.2 Soorten niet-determinisme.................. 4 1.3 Oorzaken van niet-determinisme.............. 5 1.4 Voorgestelde oplossing.................... 6 1.5 Overzicht van dit proefschrift................ 7 2 Parallelle computerarchitecturen 9 2.1 Parallelle architectuurklassen................ 9 2.2 Parallelle hardwaremodellen................ 10 2.2.1 Systemen met gedistribueerd geheugen....... 10 2.2.2 Systemen met gemeenschappelijk geheugen.... 12 2.3 Parallelle programmeermodellen.............. 22 2.4 Virtuele parallelle computers................ 26 2.5 Het testplatform........................ 27 2.5.1 De hardware..................... 27 2.5.2 Het besturingssysteem................ 31 2.5.3 Netwerken van computers............. 39 3 Races en niet-determinisme bij parallelle programma s 45 3.1 Niet-determinisme...................... 45 3.2 Oorzaken van niet-determinisme.............. 45 3.2.1 Deterministische uitvoering van een programma 48 3.2.2 Reële programma s.................. 48 3.2.3 Parallelle programma s............... 49 3.3 Equivalente uitvoeringen.................. 50 3.4 Behandeling van niet-determinisme............ 51 3.5 Niet-determinisme bij gemeenschappelijk geheugen... 55 3.6 Samenvatting......................... 62

iv INHOUD 4 Klokken 65 4.1 Inleiding............................ 65 4.2 Logische klokken....................... 75 4.2.1 Scalaire klokken.................... 75 4.2.2 Vectorklokken..................... 78 4.2.3 Matrixklokken.................... 82 4.2.4 Samenvatting..................... 85 4.3 Gespiekte klokken...................... 85 4.3.1 Scalaire klokken.................... 86 4.3.2 Vectorklokken..................... 89 4.3.3 Matrixklokken.................... 91 4.4 Wat met fysische tijd?..................... 93 4.5 Efficiënte implementaties.................. 98 4.6 Besluit............................. 98 5 Gecontroleerde heruitvoering 101 5.1 Heruitvoeren van synchronisatieraces........... 101 5.1.1 Bepalen van de volgorde.............. 102 5.1.2 Het geval x................... 104 5.1.3 Het geval x................... 104 5.1.4 De ROLT-methode.................. 106 5.2 Detectie van dataraces.................... 116 5.2.1 Detectie van geheugentoegangen.......... 117 5.2.2 Detectie van parallelle segmenten......... 118 5.2.3 Verwijderen van oude segmenten......... 120 5.2.4 Detectie van conflicterende operaties....... 123 5.3 Overzicht............................ 124 5.3.1 Uitgewerkt voorbeeld................ 124 5.3.2 Debugcyclus...................... 126 6 Instrumentatie 129 6.1 Bestaande technieken..................... 130 6.1.1 De hardware aanpassen............... 130 6.1.2 Aanpassen van het besturingssysteem....... 130 6.1.3 Aanpassen van het programma........... 131 6.2 JiTI............................... 135 6.2.1 Onderscheid tussen code en data.......... 136 6.2.2 Instrumentatie van een instructie.......... 138 6.2.3 Instrumentatie van een proces........... 140 6.2.4 De eigenlijke instrumentatieroutines........ 141

INHOUD v 6.2.5 Dynamisch aangemaakte code........... 144 6.2.6 Selectieve instrumentatie.............. 145 6.2.7 JiTI en parallelle programma s........... 146 6.2.8 Bepalen van de uitgevoerde code.......... 146 6.2.9 Instrumentatie van functieoproepen........ 147 6.3 Experimentele evaluatie................... 150 7 Validatie 153 7.1 Solaris............................. 153 7.1.1 Opname........................ 154 7.1.2 Weergave....................... 164 7.1.3 Detectie van dataraces................ 167 7.1.4 Detectie van conflicterende geheugentoegangen. 178 7.1.5 Artificiële uitvoeringen............... 178 7.2 TreadMarks.......................... 180 7.2.1 Opname........................ 181 7.2.2 Weergave....................... 181 7.3 Athapascan.......................... 182 7.3.1 Niet-determinisme bij Athapascan......... 182 8 Evaluatie 191 8.1 Solaris............................. 191 8.1.1 Gebruik........................ 191 8.1.2 De benchmark-suite................. 193 8.1.3 Resultaten....................... 196 8.2 TreadMarks.......................... 200 8.3 Athapascan.......................... 201 9 Besluit 207 9.1 Conclusies van het onderzoek................ 207 9.2 Verder onderzoek....................... 209 9.3 Belangrijkste originele bijdragen.............. 209

vi INHOUD

Tabellen 4.1 Verschil tussen logische, gespiekte en fysische klokken.. 99 4.2 Gedrag van logische, gespiekte en fysische klok...... 99 5.1 De operaties die we uitvoeren tijdens de heruitvoering.. 125 6.1 De verschillende types spronginstructies......... 138 6.2 De opcodes van de lees- en schrijfinstructies....... 141 6.3 De opcodes van de ADD-instructies............. 142 6.4 Code gebruikt om de conditievlaggen uit te lezen..... 144 6.5 Tabel met conditiewaarden................. 145 6.6 Resultaten voor een aantal testprogramma s: instructies. 151 6.7 Resultaten voor een aantal programma s: tijden..... 152 8.1 Uitvoeringstijden bij opname, weergave en detectie... 197 8.2 Aantal uitgevoerde en opgeslagen operaties........ 198 8.3 Aantal gecreëerde en vergeleken segmenten........ 200 8.4 Een aantal testresultaten voor Athapascan (alle tijden in seconden)............................ 205

viii TABELLEN

Figuren 2.1 Algemene structuur van een MIMD multiprocessor.... 11 2.2 Een multiprocessor zonder gemeenschappelijk geheugen 13 2.3 Een multiprocessor met gemeenschappelijk geheugen.. 14 2.4 De verschillende stadia die een geheugenoperatie ondergaat............................... 18 2.5 Een sequentieel consistent geheugenmodel........ 19 2.6 Een processor-consistent geheugenmodel......... 20 2.7 Een zwak consistent geheugenmodel............ 21 2.8 De verschillende geheugenmodellen............ 23 2.9 Voorstelling van een DSM-systeem............. 24 2.10 Het TSO-geheugenmodel van de SPARC-processor: schrijfoperaties worden gebufferd (FIFO), leesoperaties lezen uit de buffer of uit het geheugen............... 28 2.11 Het PSO-geheugenmodel van de SPARC-processor.... 28 2.12 Verschillen tussen het TSO- en PSO-geheugenmodel... 30 2.13 Het gebruik van registervensters............... 32 2.14 De verschillende parallelle niveaus gebruikt door Solaris 34 2.15 De verschillende synchronisatielagen gebruikt door Solaris................................ 34 2.16 Implementatie van de mutexfuncties............ 36 2.17 Implementatie van de conditievariabelen......... 37 2.18 Implementatie van de semafoorfuncties.......... 38 2.19 Onderscheid tussen eager en lazy release consistency.... 40 2.20 De gelaagde opbouw van Athapascan............ 41 2.21 Een Athapascan-systeem bestaat uit knopen en draden. 42 3.1 De veschillende types geheugenoperaties.......... 55 3.2 Semafooroperaties die geen (links) en wel (rechts) een ordening garanderen. Op deze figuur stellen de vertikale lijnen de operaties per draad voor (tijd loopt naar beneden)............................... 59

x FIGUREN 3.3 Observatie op mutexniveau................. 60 4.1 Een mogelijke voorstelling van een programma-uitvoering. 68 4.2 De p relatie........................... 69 4.3 De relatie........................... 69 4.4 De relatie........................... 70 4.5 Onderscheid tussen causale en ware-tijdsverleden.... 74 4.6 De voortgang van de Lamporttijd.............. 77 4.7 Lamportklokwaarden en paden............... 77 4.8 Door het gebruik van Lamportklokken verliezen we informatie............................. 78 4.9 De propagatie van vectorklokken.............. 80 4.10 Vectorklokken zijn streng consistent............ 80 4.11 Vectorklokken geven de causale relaties weer....... 82 4.12 De propagatie van matrixklokken.............. 83 4.13 Matrixklokken en het causale verleden........... 84 4.14 Twee uitvoeringen die enkel in ware-tijdskenmerken verschillen............................. 86 4.15 De voortgang van SC L -klokken............... 87 4.16 De voortgang van SC 1 -klokken............... 87 4.17 De voortgang van SC A -klokken.............. 89 4.18 De voortgang van V C L -klokken.............. 89 4.19 De voortgang van V C 1 -klokken............... 90 4.20 De voortgang van V C A -klokken.............. 90 4.21 De voortgang van MC LL -klokken............. 91 4.22 De voortgang van MC 1L -klokken.............. 92 4.23 De voortgang van MC AL -klokken............. 92 4.24 De voortgang van MC L1 -, MC 11 - en MC A1 -klokken... 94 4.25 De voortgang van MC LA -, MC 1A - en MC AA -klokken.. 95 4.26 Het gebruik van een externe, en dus fysische, klok..... 96 4.27 Het gebruik van fysische tijd als een logische klok.... 97 5.1 Voorbeeld van een uitvoering................ 103 5.2 De minimale relatie...................... 105 5.3 Invoer van een artificieel koppel.............. 105 5.4 Voorbeeld van een uitvoering................ 109 5.5 De klokwaarden die horen bij de operaties........ 109 5.6 De incrementen van de klokwaarden van de draden... 111 5.7 Het opleggen van de volgorde e 1 2 e2 1 is overbodig.... 112 5.8 Een uitgewerkt voorbeeld.................. 112

FIGUREN xi 5.9 Verschil tussen logische en gespiekte Lamportklokken.. 114 5.10 Vectorklokken worden ook gehecht aan segmenten... 118 5.11 Het verwijderen van oude segmenten m.b.v. matrixklokken121 5.12 Verwijderen van segmenten m.b.v. logische en gespiekte klokken............................. 122 5.13 Een uitvoering bestaande uit twee onafhankelijke delen. 123 5.14 Voorbeeld van een uitvoering................. 125 5.15 De voorgestelde debugcyclus................ 127 6.1 De single-stepmethode.................... 131 6.2 De Paradyn methode..................... 131 6.3 De compileer/link-cyclus.................. 132 6.4 De JiTI methode........................ 136 6.5 Duplicatie van een proces.................. 137 6.6 JiTI gebruikt een trampoline om het terugkeeradres te bewaren............................. 139 6.7 Selectieve instrumentatie van een parallel programma.. 146 6.8 Het gebruik van de Procedure Linkage Table......... 148 6.9 Situatie na de instrumentatie door JiTI........... 149 7.1 De vijf synchronisatieniveaus................ 155 7.2 Weergave op het lockniveau................. 156 7.3 Weergave op het mutexniveau............... 156 7.4 Weergave op het semafoorniveau.............. 158 7.5 De semafoor- en mutexfuncties worden geïnstrumenteerd.158 7.6 Een opname die tot deadlock leidt............. 161 7.7 Onderscheid maken tussen lees- en schrijflocks...... 162 7.8 De cond wait-operatie..................... 163 7.9 De impliciete synchronisatieprimitieven.......... 164 7.10 Tijdens de heruitvoering krijgt elke draad een eigen LWP 166 7.11 Het gebruik van gepagineerde bitmaps.......... 169 7.12 Verdwenen dataraces..................... 172 7.13 Eraser ontdekt te veel dataraces............... 173 7.14 Eraser ontdekt te weinig dataraces............. 173 7.15 Rwlocks testen alsof het mutexen zijn........... 175 7.16 Leeslocks testen op hun correct gebruik.......... 176 7.17 Semafooroperaties zijn al dan niet geordend....... 177 7.18 Grafische voorstelling van een uitvoering......... 179 7.19 Race bij boodschapversturing................ 184 7.20 Synchronisatie-operaties worden geserializeerd tijdens de heruitvoering......................... 185

xii FIGUREN 7.21 Een uitvoering die steeds deterministisch is........ 186 7.22 Een uitvoering die steeds deterministisch is........ 187 7.23 Een uitvoering die nooit deterministisch is........ 187 8.1 Het testprogramma...................... 192 8.2 Gebruik van RECPLAY indien er een datarace is..... 194 8.3 Gebruik van RECPLAY indien er geen datarace is..... 195 8.4 Histogram van de incrementen............... 199 8.5 Grootte van het registreerbestand in functie van het aantal bits dat we gebruiken om incrementen op te slaan... 199 8.6 Aantal segmenten dat op de lijst staat (LU)......... 201 8.7 Aantal segmenten dat op de lijst staat en dat vergeleken wordt.............................. 202 8.8 Vergelijking tussen logische en gespiekte matrixklokken 203 8.9 De grootte van de bitmap tijdens de uitvoering (LU)... 204 8.10 De grootte van het registreerbestand in functie van een extra increment........................ 204

Hoofdstuk 1 Inleiding Wie nooit gevallen is, heeft geen juist besef van wat er nodig is om te staan. MULTATULI 1.1 Debuggen Iedereen die reeds een computerprogramma geschreven heeft weet dat dit gepaard gaat met vallen en opstaan. Inderdaad, zelfs de meest ervaren programmeur maakt (tik)fouten waardoor een programma niet doet wat er van verwacht wordt. Sommige fouten worden onmiddellijk zichtbaar doordat de compiler het programma niet wil compileren. In dit geval is het meestal eenvoudig om de fout te verwijderen omdat de compiler ons zowel de naam van het bestand als het regelnummer vertelt waar de fout zich bevindt. Indien de fout iets subtieler is zal ze (hopelijk) opduiken tijdens een uitvoering. Een foutief programma kan immers een foutief resultaat produceren of simpelweg hangen of vastlopen. In dit geval moet de programmeur het programma debuggen. Bij parallelle programma s programma s die uit een aantal deeltaken of draden bestaan die parallel worden uitgevoerd op een computer met verschillende processors treden fouten nog frequenter op. Het schrijven van parallelle programma s is immers heel wat moeilijker dan het schrijven van vergelijkbare sequentiële programma s. Een be-

2 Inleiding langrijk onderdeel van een uitvoering van een parallel programma is namelijk de interactie tussen de verscheidene processors: er is communicatie en synchronisatie nodig. Het is deze interactie die meestal voor moeilijkheden zorgt. Algemeen kunnen we stellen dat er bij parallelle programma s drie soorten fouten kunnen optreden: programmeerfouten: dit zijn de fouten die ook kunnen optreden bij het schrijven van sequentiële programma s; synchronisatiefouten: dit soort fouten wordt veroorzaakt door een verkeerd (of onvoldoende) gebruik van synchronisatie. Deze fouten kunnen zich uiten in de vorm van een patstelling (deadlock), waarbij geen enkele draad nog verder kan, of in de vorm van racecondities. Deze laatste ontstaan als twee of meerdere draden hetzelfde gemeenschappelijke object gebruiken, en minstens één ervan het wijzigt. De uitkomst van zo n raceconditie is onbepaald: de inhoud van het object na de raceconditie (bij twee schrijfoperaties), of de waarde die gelezen wordt (bij een lees- en schrijfoperatie) hangt af van de volgorde van die twee operaties; prestatiefouten: dit soort fouten heeft tot gevolg dat de snelheidswinst na parallellisatie niet voldoet aan de verwachtingen. Mogelijke oorzaken hiervan zijn een flessenhals (bottleneck), het niet volledig uitbuiten van het potentiële parallellisme dat het algoritme ons biedt, enz. Tegenwoordig zijn de meeste commerciële programma s meerdradig, waarbij er bv. één draad gebruikt wordt per document dat open is (bv. de M ultiple Document Interface (MDI) van Windows ), en bijkomende draden voor het hertekenen van het scherm, voor het afhandelen van de invoer, voor het afdrukken van een document, enz. In dit geval zijn de draden horende bij de documenten echter van elkaar afgeschermd waardoor er geen rechtstreekse communicatie is tussen de verschillende draden. Inderdaad, het besturingssysteem zorgt via de event loop en via MDI voor de communicatie, bv. door toetsenbordinvoer naar het juiste document te sturen. Bij het ontwikkelen van zo n applicatie moeten we dus in feite geen rekening houden met de parallelle aard van het programma. Het ontwikkelen van expliciet parallelle programma s waarbij we snelheidswinst wensen te behalen door een probleem in deelproblemen te splitsen vereist echter wel communicatie tussen de deeltaken.

1.1 Debuggen 3 Het feit dat het schrijven van expliciet parallelle programma s niet eenvoudig is zorgt ervoor dat er weinig ontwikkeld worden. Nochtans zouden vele rekenintensieve algoritmen gebaat zijn met een parallelle implementatie en kan men tegenwoordig beschikken over goedkope parallelle computers. De snelheidswinst die een parallelle versie kan opleveren weegt blijkbaar niet op tegen de hogere ontwikkelingskost. De moeilijkheden eigen aan het schrijven van parallelle programma s zouden moeten opgevangen worden door betere ontwikkelingsmethoden- en hulpmiddelen. Men vindt inderdaad, in toenemende mate, een steeds groter wordend assortiment van generatie-, analyse- en visualisatiehulpmiddelen speciaal opgezet voor dit doel. Debuggers voor parallelle programma s vormen daar een volwaardig onderdeel van. Dit proefschrift wil op dit vlak een bijdrage bieden. Laten we daarom eerst eens de bestaande debugtechnieken overlopen. Wensen we een programma te debuggen, dan kunnen we dit op drie manieren doen: statisch (at compile-time): door studie van de programmacode moeten we normaal gezien in staat zijn om alle fouten op te sporen. Zonder dynamische informatie is dit echter moeilijk: we moeten met enorm veel mogelijke uitvoeringen rekening houden. Bovendien geven statische hulpmiddelen enkel mogelijke fouten omdat vele hypothesen betreffende een programma-uitvoering onbeslisbaar zijn. Een voorbeeld van een programmeerhulpmiddel dat gebruik maak van statische informatie is lint [SunS94]. Dit hulpmiddel test een beperkt aantal zaken o.a. of parameters van functies van het juiste type zijn, of variabelen worden gelezen vóór ze geschreven worden,.... Lint zal ons echter meestal niet kunnen vertellen dat er een deling door nul wordt uitgevoerd. dynamisch (at run-time): door informatie over de uitvoering van het programma door observatie (bv. watch- en breakpoints) te verzamelen, beschikken we over informatie die veel specifieker is dan de programmacode. De informatie is concreter maar geldt slechts voor de beschouwde invoer. Hiermee kunnen we dus zorgen dat een welbepaalde programma-uitvoering correct is, zonder dat we kunnen garanderen dat het programma zelf correct is. Voorbeelden van zulke programmeerhulpmiddelen zijn Insure++ [Para96] en Purify [Puri]. post-mortem: hierbij maken we gebruik van informatie die

4 Inleiding beschikbaar is op het moment dat het programma vastloopt (bv. een core-dump). Deze methode is dus niet bruikbaar als het programma niet correct is, maar toch op een normale manier wordt beëindigd. Ook hier kunnen we beschikken over specifieke informatie wat één uitvoering betreft, maar maakt het gebrek aan dynamische informatie ook deze debugmethode moeilijk. Het is duidelijk dat dynamische informatie zeer belangrijk is bij het debuggen. Het is in principe mogelijk om alle dynamische informatie te verzamelen tijdens één enkele uitvoering, bv. door na elke uitgevoerde instructie een geheugendump en de inhoud van de processorregisters weg te schrijven naar de schijf. Op deze manier wordt het programma echter enorm vertraagd, en verzamelen we overbodige informatie zodat de programmeur door de bomen het bos niet meer ziet. Het verzamelen van de dynamische informatie kan men echter ook verdelen over een aantal uitvoeringen. Tijdens een eerste uitvoering kan men bv. de waarde van een variabele op een aantal tijdstippen op scherm tonen. De programmeur kan dan tijdens nieuwe uitvoeringen als het ware inzoomen op de programma-uitvoering door enkel nog data te verzamelen in verdachte functies. De data die tijdens deze opeenvolgende uitvoeringen verzameld worden kunnen de programmeur helpen bij het zoeken naar de fout. Deze methode wordt door de meeste programmeurs gebruikt en noemt men cyclisch debuggen. Het is duidelijk dat deze methode slechts werkt als opeenvolgende uitvoeringen hetzelfde programmaverloop kennen: de uitvoering moet herhaalbaar en dus deterministisch zijn. Vele programma s, en vooral parallelle programma s, zijn echter niet-deterministisch. 1.2 Soorten niet-determinisme Er bestaan twee soorten niet-determinisme: extern niet-determinisme: het programma geeft verschillende resultaten bij herhaalde uitvoeringen met dezelfde invoer. Alhoewel dit soort niet-determinisme meestal ongewenst is, en dus een fout in het programma is, hoeft dit niet altijd het geval te zijn; het probleem met de acht koninginnen bv. heeft verschillende mogelijke oplossingen. intern niet-determinisme: het programma geeft dezelfde resultaten

1.3 Oorzaken van niet-determinisme 5 bij herhaalde uitvoeringen met dezelfde invoer, maar het interne programmaverloop is verschillend. Dit om te vermijden dat we programma s die verschillende alternatieven hebben om tot een oplossing te komen, moeten overspecificeren en we aldus het potentiële parallellisme in het programma beperken. 1.3 Oorzaken van niet-determinisme Bovendien is het zo dat de meeste, zelfs correcte, parallelle programma s niet-deterministisch zijn; het interne verloop van het programma wordt niet (volledig) bepaald door de programmacode, maar o.a. door de relatieve snelheidsverschillen waarmee de processen worden uitgevoerd. We zouden parallelle programma s volledig deterministisch kunnen maken (ook intern) [Aude95a]; deze programma s zullen echter nooit het door de multiprocessor aangeboden parallellisme volledig kunnen benutten. Er zijn heel wat zaken die het programmaverloop bepalen, en die aldus een oorzaak van niet-determinisme kunnen zijn: 1. de programmacode zelf; 2. de processors die het programma uitvoeren (caches, klokfrequenties); 3. het besturingssysteem (bv. systeemoproepen, interrupts, signalen); 4. de begintoestand van het programma (niet geïnitializeerde variabelen,... ); 5. de interactie tussen de verschillende draden: synchronisatie en communicatie, eventueel in de vorm van racecondities; 6. de invoer (zowel aan het begin als tijdens de uitvoering; zowel van toetsenbord als schijf, netwerk,... ); Het is natuurlijk de bedoeling dat de eerste 4 punten het programmaverloop bepalen. Puntje 5 treedt enkel op bij parallelle programma s, maar is voor deze programma s wel de belangrijkste oorzaak van nietdeterminisme. Het laatste puntje is de belangrijkste oorzaak van nietdeterminisme bij sequentiële programma s maar treedt natuurlijk ook op bij parallelle programma s.

6 Inleiding Om herhaalde identieke programma-uitvoeringen te krijgen zullen we o.a. moeten zorgen voor dezelfde programmacode, wat evident is en ook geen problemen stelt, dezelfde initiële invoer, wat meestal ook geen probleem is als deze invoer uit een bestand komt. De behandeling van de andere oorzaken van niet-determinisme ligt echter niet voor de hand. 1.4 Voorgestelde oplossing De methode die in dit proefschrift verder uitgewerkt en ontwikkeld wordt is deze van gecontroleerde programma-heruitvoering (execution replay of record/replay). Deze methode bestaat erin dat men tijdens elke normale uitvoering van het programma (eventueel enkel tijdens een testfase) een minimale hoeveelheid informatie over het programmaverloop opneemt en opslaat. Met deze informatie kan men dan op gecontroleerde wijze (en onder bepaalde voorwaarden) het programma een onbeperkt aantal maal op deterministische manier heruitvoeren, zelfs als op dat ogenblik een traditionele debugger gebruikt wordt die de oorspronkelijke uitvoering ontoelaatbaar zou verstoord hebben. Daarbij beperken we ons tot het niet-determinisme dat eigen is aan parallelle programma s, en dat dus veroorzaakt wordt door de communicatie en synchronisatie. We zullen wel vermelden hoe men kan omgaan met niet-determinisme dat ook aanwezig is in sequentiële programma s, maar deze methoden werden noch bestudeerd, noch geïmplementeerd. Dit proefschrift is het logische vervolg van de proefschriften van Luk Levrouw [Levr95] en Koen Audenaert [Aude95a]. Levrouw bestudeerde in zijn werk gecontroleerde heruitvoering voor sequentieel consistente parallelle machines en stelde een nieuwe (formeel beschreven) heruitvoeringsmethode voor (ROLT, zie paragraaf 5.1.4). Audenaert richtte zijn aandacht vooral op de detectie van dataraces en het detecteren van ordeningen in een uitvoering. Dit proefschrift combineert gecontroleerde heruitvoering met dataracedetectie voor systemen zonder sequentieel consistent geheugenmodel. De belangrijkste originele bijdragen zijn: het feit we gecontroleerde heruitvoering op twee niveaus doen [Rons97c]. Tijdens een opname registreren we namelijk enkel de volgorde van de synchronisatie-operaties, waarna we tijdens een eerste heruitvoering testen of we een equivalente heruitvoering

1.5 Overzicht van dit proefschrift 7 kunnen krijgen door enkel deze volgorde terug op te dringen (detectie van dataraces). Bovendien slaan we tijdens de opname de benodigde vectorklokken voor deze dataracedetectie niet op maar bereken we ze on-the-fly tijdens de heruitvoering. Aan de hand van drie implementaties ([Rons97b, Rons97a, Rons98a]) werd aangetoond dat deze methode werkt en bovendien slechts een kleine vertraging invoert; het gebruik van matrixklokken om oude informatie tijdens de dataracedetectie te verwijderen. Bovendien hebben we gespiekte klokken ingevoerd die betere resultaten leveren dan de normale logische versie [DB97]; de technieken die we gebruiken om een programma te instrumenteren ([Rons98c]). 1.5 Overzicht van dit proefschrift Aangezien dit proefschrift debugmethoden voor parallelle computerarchitecturen behandelt geven we in hoofdstuk 2 een overzicht van hedendaagse parallelle computerarchitecturen. In hoofdstuk 3 gaan we dieper in op niet-determinisme bij parallelle programma s en bepalen we op welke manier we zullen omgaan met dit niet-determinisme. Om op een eenvoudige manier om te gaan met de relaties die optreden tussen gebeurtenissen van verschillende draden hebben we klokken nodig. In hoofdstuk 4 geven we een overzicht van bestaande klokken, en voeren we een nieuwe variant in. In hoofdstuk 5 stellen we gecontroleerde heruitvoering voor, waarbij we steunen op hoofdstukken 3 en 4. Om gegevens over een programma-uitvoering te verzamelen hebben we een instrumentatiehulpmiddel nodig. Hoofdstuk 6 begint met een overzicht van de bestaande hulpmiddelen, nadien volgt een beschrijving van ons hulpmiddel. Aangezien we de voorgestelde technieken moeten toetsen aan de werkelijkheid beschrijven we in hoofdstuk 7 de implementaties die we zullen gebruiken voor de validatie van de methode. In hoofdstuk 8 doen we de eigenlijke evaluatie aan de hand van een aantal benchmarkprogramma s.

8 Inleiding Ten slotte sluiten we dit proefschrift af met een aantal conclusies in hoofdstuk 9.

Hoofdstuk 2 Parallelle computerarchitecturen Vele handen maken licht werk Chinees spreekwoord 2.1 Parallelle architectuurklassen Volgens Flynn [Flyn66, Flyn72] kunnen we vier verschillende klassen van computerarchitecturen onderscheiden. Deze klassen verschillen van elkaar door het aantal instructie- en datastromen. Daarbij is een instructiestroom een sequentie van instructies die door een processor worden uitgevoerd, en is een datastroom een sequentie van data die door een instructiestroom worden gemanipuleerd. De vier verschillende architectuurklassen zijn SISD, MISD, SIMD en MIMD. De eenvoudigste klasse is de SISD (single instruction stream, single data stream) familie. Aangezien er één instructiestroom is die inwerkt op één datastroom is er maar één processor nodig. De meeste oudere monoprocessorsystemen vallen in deze categorie. De meeste hedendaagse processors behoren echter tot de SIMD (single instruction stream, multiple data stream) familie. Deze systemen bestaan uit één of verschillende processors die dezelfde instructies uitvoeren, maar die op verschillende datastromen inwerken. Deze bewerkingen worden dus parallel uitgevoerd. SIMD-computers zijn vooral interessant voor bewerkingen op vectoren waar elk element

10 Parallelle computerarchitecturen van de lijst dezelfde bewerking moet ondergaan. Een oud voorbeeld van een SIMD-systeem met meerdere processors is de Illiac IV (1972); een meer recent voorbeeld van een SIMD-systeem is de Connection Machine-2 van wijlen Thinking Machines Corporation. Moderne SIMD-processors zijn o.a. de Pentium-familie van Intel met de MMXinstructies en de SPARC-processor van SUN met de VIS-instructies. Bij MISD (multiple instruction stream, single data stream) systemen worden er tegelijk verschillende bewerkingen op dezelfde data uitgevoerd. Wegens hun beperkte toepasbaarheid bestaan er weinig MISD-computers (o.a. de Multiflow Trace 7/200). Ook MIMD (multiple instruction stream, multiple data stream) computers zijn parallelle computers. Het is de meest algemene parallelle computerarchitectuur: er zijn verschillende instructiestromen (één per processor) die elk inwerken op hun eigen datastroom. De verschillende processors voeren hun instructies simultaan uit (in parallel) waarbij er af en toe interactie zal zijn tussen de verschillende processors (voor communicatie of synchronisatie). Het is voor deze klasse van parallelle computers dat we in dit proefschrift gecontroleerde heruitvoering zullen bestuderen. 2.2 Parallelle hardwaremodellen Figuur 2.1 op de rechterpagina toont de structuur van een algemene MIMD multiprocessor. Het noodzakelijke interconnectienetwerk (bus, crossbar, meerlaags netwerk,... ) verbindt de processors met de geheugeneenheden. Bestaande systemen kunnen door specialisatie afgeleid worden van deze algemene structuur. Daarbij kunnen we twee grote klassen van systemen onderscheiden door de aan- of afwezigheid van gemeenschappelijk geheugen. Er bestaan ook hybride systemen die tot beide klassen behoren: deze hebben zowel gemeenschappelijk als lokaal geheugen. Een parallelle computer zonder gemeenschappelijk geheugen, en waar alle processors dus enkel toegang hebben tot hun lokaal geheugen, wordt een computer met gedistribueerd geheugen genoemd. 2.2.1 Systemen met gedistribueerd geheugen. Bij een systeem met gedistribueerd geheugen hebben we verschillende processors die elk een lokaal geheugen hebben maar waar er geen

2.2 Parallelle hardwaremodellen 11 Figuur 2.1: Algemene structuur van een MIMD multiprocessor.

12 Parallelle computerarchitecturen gemeenschappelijk geheugen is (zie Figuur 2.2 op de pagina hiernaast). Het geheugen is gedistribueerd over de verschillende processors; dit betekent dat elke processor slechts rechtstreeks toegang heeft tot zijn eigen lokaal geheugen en er dus gescheiden fysische adresruimten zijn. Elke processor plaatst zijn eigen data in zijn eigen geheugen. Uitwisseling van data tussen de verschillende processors gebeurt door het versturen van berichten (message passing) over een netwerk. Het netwerk kan een bus, een maas, een hypercubus,... zijn. 2.2.2 Systemen met gemeenschappelijk geheugen. We definiëren een systeem met gemeenschappelijk geheugen als een systeem met meerdere processors en één of meerdere gemeenschappelijke geheugenmodules (shared memory) (zie Figuur 2.3 op pagina 14). Er is één gemeenschappelijke fysische adresruimte die alle gemeenschappelijke geheugenmodules omvat. Bij zulke systemen heeft elke processor rechtstreeks toegang tot dit gemeenschappelijk geheugen. We leggen er de nadruk op dat we het onderscheid tussen systemen met en zonder gemeenschappelijk geheugen maken op basis van de aard van het geheugen. Deze aard (gemeenschappelijk of gedistribueerd) kan verschillend zijn van het (logische) programmeermodel dat we op de machine kunnen gebruiken (zie verder). Zowel het uitwisselen van data (de communicatie) als de synchronisatie 1 tussen de verschillende processors gebeurt door de data in het gemeenschappelijk geheugen te plaatsen. Hierbij gebruiken we synchronisatieprimitieven om de toegang tot de gemeenschappelijk data in goede banen te leiden. 2 Deze primitieven kunnen volledig in software opgebouwd worden, waarbij verschillende geheugentoegangen per synchronisatie-operatie nodig zijn. Bekende softwaremethoden zijn het bakkerij-algoritme en de algoritmen van Peterson en Dekker. Nadeel van softwareprimitieven is dat ze ingewikkeld en inefficiënt zijn. Bovendien werken de meeste softwareprimitieven enkel als het geheugensysteem sequentieel consistent is (zie verder). Aangezien 1 Onder synchronisatie verstaan we gecoördineerd temporeel gedrag van verschillende processen. Door te synchroniseren kunnen we activiteiten tussen deze verschillende processen coördineren. Dit gebeurt door gebruik te maken van synchronisatieprimitieven. 2 Alle processors garanderen atomische toegang tot geheugenplaatsen waarvan de breedte kleiner is dan de busbreedte. Schrijven twee processors op hetzelfde moment naar dezelfde geheugenplaats, dan is de eindwaarde één van beide waarden.

2.2 Parallelle hardwaremodellen 13 Figuur 2.2: Algemene structuur van een multiprocessor zonder gemeenschappelijk geheugen.

14 Parallelle computerarchitecturen Figuur 2.3: Algemene structuur van een multiprocessor met gemeenschappelijk geheugen.

2.2 Parallelle hardwaremodellen 15 processorontwerpers meestal een zwakker geheugenmodel gebruiken, door bv. niet langer te eisen dat de geheugenoperaties in programmavolgorde uitgevoerd worden, zijn softwareprimitieven dus niet altijd bruikbaar. Men kiest voor zwakkere geheugenmodellen omdat die diverse hardware-optimalisaties mogelijk maken, zoals schrijfbuffers en out-of-order geheugentoegangen. Omdat softwaresynchronisatie dan niet langer bruikbaar is, ondersteunen hedendaagse processors hardwaresynchronisatieprimitieven. Een basisprimitief is het zogenaamde atomische read-modify-write of test-and-set primitief. Met behulp van zo n operatie kan men inderdaad synchronisatie-operaties bouwen. Deze primitieven maken gebruik van hardware-uitbreidingen om atomische operaties mogelijk te maken. Er bestaan twee methoden om atomische operaties mogelijk te maken. De eerste methode maakt gebruik van vergrendelbare bussen. Deze bussen bevatten een signaal dat aangeeft of de bus al dan niet gebruikt mag worden. Indien een processor een atomische operatie wil uitvoeren wordt de bus vergrendeld, en tijdens deze periode kunnen de andere processors de bus niet gebruiken. Het vergrendelen van de bus kan expliciet of impliciet gebeuren. Bij de INTEL-processors kan men de bus expliciet vergrendelen gedurende de uitvoering van één instructie door het LOCK-prefix vóór de instructie te plaatsen. Door het combineren van dit prefix met de CMPXCHG-instructie (compare-andexchange) vanaf de 80486 3 verkrijgt men een atomische lees-schrijfoperatie. De processors van de SPARC-familie hebben instructies die automatisch tot het vergrendelen van de bus leiden. Deze instructies zijn de synchronisatie-operaties SWAP, LDSTUB (load store unsigned byte) en CASA (compare and swap) 4 en 64-bit geheugenoperaties 5. Voordeel van deze methode is de eenvoudige implementatie, nadeel is dat de volledige toegang tot het geheugen wordt afgesloten, terwijl men in feite enkel de toegang tot één geheugenplaats wil verhinderen. Het vergrendelen van de bus voor één specifiek geheugenadres is echter niet mogelijk. We kunnen wel een aparte lees- en schrijfoperatie (naar hetzelfde adres) uitvoeren en nadien testen of er tussen deze operaties door een andere processor geschreven werd naar hetzelfde adres. Bij deze methode maakt men dus geen gebruik van een atomische leesen schrijfoperatie maar van twee gescheiden geheugenoperaties zonder 3 Bij oudere types INTEL-processors kan men zich behelpen met de SRL-instructie. 4 Enkel bij de UltraSPARC 5 Niet meer bij de 64-bits UltraSPARC.

16 Parallelle computerarchitecturen de bus ondertussen te vergrendelen. De schrijfoperatie is echter wel een speciale schrijfoperatie: de operatie lukt enkel indien de processor ondertussen geen schrijfoperatie naar hetzelfde adres gedetecteerd heeft. Na de schrijfoperatie kan de processor testen of deze laatste operatie al dan niet geslaagd is. Voorbeelden hiervan zijn LDx L (load memory data into integer register locked) en STx C (store integer register data into memory conditional) bij de Alpha-processor, LL (load linked) en SC (store conditional) bij de MIPS-processor en lwarx (load word and reserve) en stwcx (store word conditional) bij de PowerPC. Het detecteren of een andere processor ondertussen naar hetzelfde adres geschreven heeft kan eenvoudig gebeuren. Bij de MIPS-processor bv. lukt de schrijfoperatie niet indien de variabele zich niet meer in de cache bevindt. Dit laatste kan betekenen dat de cachelijn geïnvalideerd is doordat een andere processor naar hetzelfde adres geschreven heeft (zodat onze lees/schrijfcyclus niet meer atomisch is). Het kan echter evengoed betekenen dat een er een schrijfoperatie naar een ander adres in dezelfde cachelijn geweest is (false sharing) of dat de processor de lijn zelf verwijderd heeft (indien de cache vol was). Om het laatste te vermijden wordt er aangeraden om geen geheugenoperaties uit te voeren tussen de lees- en de schrijfoperatie. Aangezien de schrijfoperatie ook mislukt indien er ondertussen een exceptie of een interrupt optreedt wordt er bovendien aangeraden om zo weinig mogelijk instructies uit te voeren tussen de lees- en de schrijfinstructie. Zoals reeds vermeld werd, gebruiken hedendaagse processors geheugenmodellen die zwakker zijn dan sequentiële consistentie. Een belangrijke eigenschap van een geheugenmodel is de consistentie dat het aanbiedt: als een processor een bepaalde variabele verandert, wanneer zien de andere processors deze verandering dan? Aangezien geheugenmodellen belangrijk zullen blijken te zijn voor ons onderzoek, bestuderen we eerst de voornaamste geheugenmodellen. Geheugenmodellen Een geheugenmodel is een formele specificatie van de semantiek van het geheugen: het specificeert namelijk welke operaties men ondeelbaar kan uitvoeren, en legt vast wat het resultaat is tengevolge van het uitvoeren van operaties op het geheugen. In die zin moet het model beschouwd worden als een contract tussen het geheugen en applicaties. In wat nu volgt beschouwen we enkel lees- en schrijfoperaties die inwerken op data. De problemen die optreden bij het wijzigen van code

2.2 Parallelle hardwaremodellen 17 (self modifying code) beschouwen we dus niet. Om de vier bekendste geheugenmodellen op een uniforme manier te definiëren vermelden we eerst een aantal hulpdefinities, waarbij we een geheugenoperatie definiëren als een lees- of schrijfoperatie van/naar een geheugenplaats: een geheugenoperatie is doorgegeven op het moment dat ze de processor verlaten heeft en ze in uitvoering is in het geheugensysteem; een leesoperatie door processor p i is op een bepaald moment uitgevoerd met betrekking tot processor p k, indien een schrijfoperatie doorgegeven op dat ogenblik door processor p k de waarde die processor p i leest niet meer kan veranderen; een schrijfoperatie door processor p i is op een bepaald moment uitgevoerd met betrekking tot processor p k, indien een leesoperatie doorgegeven door processor p k de waarde teruggeeft die geschreven wordt door processor p i ; een operatie is lokaal uitgevoerd, indien ze uitgevoerd is met betrekking tot de processor die haar uitvoert; een operatie is universeel uitgevoerd, indien ze uitgevoerd is met betrekking tot alle processors; een leesoperatie is globaal uitgevoerd indien ze universeel uitgevoerd is, en de schrijfoperatie die de bron is van de gelezen waarde, universeel uitgevoerd is. Een operatie wordt dus doorgegeven, is een tijdje later lokaal uitgevoerd en is nog later universeel uitgevoerd (zie Figuur 2.4 op de pagina hierna). Voor leesoperaties hebben we bovendien nog het ogenblik waarop de operatie globaal uitgevoerd is. Naargelang een geheugenmodel een onderscheid maakt tussen gewone lees- en schrijfoperaties en synchronisatie-operaties kunnen we twee types geheugenmodellen onderscheiden: uniforme modellen behandelen synchronisatie-operaties als gewone geheugenoperaties. Synchronisatie-operaties die zowel lezen als schrijven (bv. SWAP) moeten zowel voldoen aan de eisen die gelden voor lees- als voor schrijfoperaties.

18 Parallelle computerarchitecturen Figuur 2.4: De verschillende stadia die een geheugenoperatie ondergaat: een operatie is eerst doorgegeven, daarna lokaal uitgevoerd, daarna universeel uitgevoerd en eventueel globaal uitgevoerd (enkel voor leesoperaties).

2.2 Parallelle hardwaremodellen 19 Figuur 2.5: De canonische voorstelling van een sequentieel consistent geheugenmodel. hybride modellen leggen andere eisen wat betreft consistentie op aan synchronisatie-operaties in vergelijking met gewone leesen schrijfoperaties. Merk op dat we bij het hybride model in staat moeten zijn om onderscheid te maken tussen de synchronisatie-operaties en de gewone lees- en schrijfoperaties: we zullen speciale instructies moeten gebruiken om te synchroniseren. De vier bekendste geheugenmodellen kunnen dan als volgt gedefinieerd worden [Adve89, Adve90, Adve91, Adve95, Aham92, Ghar93] (gerangschikt van sterk naar zwak): Sequentiële consistentie (uniform): vóór een LEES- of SCHRIJF-operatie wordt doorgegeven, moeten alle voorgaande LEES-operaties van deze processor globaal uitgevoerd zijn, en alle voorgaande SCHRIJF-operaties van deze processor universeel uitgevoerd zijn. 6 Dit komt erop neer dat de uitvoering van een programma verloopt alsof de operaties door de verschillende processors in een sequentiële volgorde zijn uitgevoerd én dat de operaties van elke processor in deze volgorde voorkomen zoals gespecificeerd door de programmacode. Figuur 2.5 toont de canonische implementatie van een sequentieel consistent geheugenmodel. Aangezien er een schakelaar (switch) gebruikt 6 Voor sequentiële consistentie betreft het slecht een voldoende voorwaarde, en geen definitie.

20 Parallelle computerarchitecturen Figuur 2.6: De canonische voorstelling van een processor-consistent geheugenmodel. Schrijfoperaties worden gebufferd in een FIFO-buffer, leesoperaties lezen uit de buffer of uit het geheugen (in die volgorde) en er is geen centrale schakelaar meer. wordt om toegang te krijgen tot het geheugen, ziet elke processor de geheugenoperaties in dezelfde volgorde. Processor-consistentie (uniform): vóór een LEES-operatie wordt doorgegeven, moeten alle voorgaande LEES-operaties van deze processor universeel uitgevoerd zijn; vóór een SCHRIJF-operatie wordt doorgegeven, moeten alle voorgaande LEES- en SCHRIJF-operaties van deze processor universeel uitgevoerd zijn. Schrijfoperaties die door een processor worden uitgevoerd, worden door elke processor in die volgorde geobserveerd. De volgorde van schrijfoperaties van twee verschillende processors kan echter verschillend geobserveerd worden. Uit de canonische implementatie van een processor-consistent geheugenmodel (Figuur 2.6) halen we dat er geen centrale schakelaar meer gebruikt wordt, en dat een processor kan lezen uit zijn schrijfbuffer (FIFO). Zwakke consistentie (hybried): vóór een normale LEES- of SCHRIJF-operatie wordt doorgegeven, moeten alle voorgaande SYNCHRONISATIE-operaties van deze processor universeel uitgevoerd zijn;

2.2 Parallelle hardwaremodellen 21 Figuur 2.7: De canonische voorstelling van een zwak consistent geheugenmodel. Dit geheugenmodel verschilt van het processor-consistent model doordat de buffer niet langer van het FIFO-type is en doordat synchronisatieoperaties een speciale behandeling krijgen. vóór een SYNCHRONISATIE-operatie wordt doorgegeven, moeten alle voorgaande normale LEES- en SCHRIJF-operaties van deze processor universeel uitgevoerd zijn; SYNCHRONISATIE-operaties zijn sequentieel of processor-consistent met betrekking tot elkaar. Bij zwakke consistentie moet de programmeur, door het aanbrengen van synchronisatie-operaties, voor de consistentie zorgen aangezien de gewone geheugenoperaties onderling niet meer geordend zijn. De geheugenoperaties zijn enkel nog geordend met de synchronisatieoperaties en deze synchronisatie-operaties zijn sequentieel of processorconsistent (Figuur 2.7). Release-consistentie (hybried): vóór een normale LEES- of SCHRIJF-operatie wordt doorgegeven, moeten alle voorgaande ACQUIRE-operaties van deze processor universeel uitgevoerd zijn; vóór een RELEASE-operatie wordt doorgegeven, moeten alle voorgaande normale LEES- en SCHRIJF-operaties van deze processor universeel uitgevoerd zijn; SYNCHRONISATIE-operaties zijn sequentieel of processor-consistent met betrekking tot elkaar.

22 Parallelle computerarchitecturen Release-consistentie is zwakke consistentie met twee types synchronisatie-operatoren: acquire (vergrendeloperatie) en release (ontgrendeloperatie). Beide zijn processor-consistent. Release-consistentie verschilt van zwakke consistentie door het feit dat normale geheugenoperaties bij release-consistentie geordend zijn met de acquire-operatie ervòòr en de release-operatie erna, terwijl geheugenoperaties bij zwakke consistentie steeds geordend zijn met de synchronisatie-operatie ervoor en erna, ongeacht het een acquire of release betreft. Figuur 2.8 toont een schematisch overzicht van de verschillen tussen de vier beschouwde modellen. 2.3 Parallelle programmeermodellen Naast de twee soorten parallelle computerarchitecturen (gemeenschappelijk en gedistribueerd geheugen) bestaan er ook twee soorten programmeermodellen: gemeenschappelijke variabelen: in dit geval kunnen de verschillende processors informatie uitwisselen doordat beide dezelfde variabele (bv. a) kunnen lezen en wijzigen. versturen van boodschappen: dit programmeermodel laat enkel uitwisseling van informatie toe m.b.v. boodschappen. Dit kan zowel asynchroon als synchroon gebeuren. Algemeen wordt aangenomen dat het schrijven van parallelle programma s die gebruik maken van gemeenschappelijke variabelen eenvoudiger is dan met boodschapsversturing. Het gebruik van gemeenschappelijke variabelen is namelijk een logische uitbreiding van het schrijven van een sequentieel programma voor een monoprocessor. Alhoewel het evident lijkt om gemeenschappelijke variabelen te gebruiken bij machines met gemeenschappelijk geheugen en boodschappen bij machines met gedistribueerd geheugen is dit niet noodzakelijk. Voor sommige toepassingen is het mogelijk dat de methode die overeenkomt met de architectuur van de machine niet de meest geschikte is voor het programma dat men wenst te schrijven. In dit geval kan men de andere manier softwarematig implementeren: boodschappen bij gemeenschappelijk geheugen: we kunnen boodschapsversturing implementeren d.m.v. (operaties op) buffers in het gemeenschappelijk geheugen;

2.3 Parallelle programmeermodellen 23 Figuur 2.8: De verschillende geheugenmodellen en de ordeningen die ze opleggen. Merk op dat de operaties ook moeten voldoen aan de lokale data-afhankelijkheden. De figuur toont enkel de minimale ordeningen die opgelegd worden.

24 Parallelle computerarchitecturen Figuur 2.9: Een DSM-systeem biedt de gebruiker een logische gemeenschappelijke adresruimte bij afwezigheid van een fysische gemeenschappelijke adresruimte.

2.3 Parallelle programmeermodellen 25 gemeenschappelijke variabelen bij gedistribueerd geheugen: in de gevallen dat het voor de programmeur eenvoudiger is om communicatie via gemeenschappelijke variabelen te gebruiken op een machine die alleen boodschapversturing toelaat, kan er m.b.v. boodschappen een systeem geïmplementeerd worden (Figuur 2.9 op de pagina hiernaast) dat de gebruiker de illusie van één gemeenschappelijke virtuele adresruimte geeft (Distributed Shared Memory; DSM). De verschillende fysische adresruimten worden dan samengebracht tot één logische adresruimte. Zoals vermeld zijn er naast de twee soorten architecturen en de twee soorten programmeermodellen ook twee soorten interactie mogelijk bij parallelle computers. Deze interactie tussen de verschillende processen zorgt er precies voor dat we een parallel programma hebben; zonder interactie tussen de processen hebben we enkel een verzameling uitvoeringen van sequentiële programma s. De twee nodige soorten interactie zijn: communicatie: uitwisseling van data tussen de verschillende processen; synchronisatie: zorgen dat operaties in de juiste volgorde uitgevoerd worden. Bij synchrone boodschapsversturing vallen beide interacties steeds samen: het versturen van een boodschap zorgt zowel voor communicatie (de data die verstuurd wordt) als voor synchronisatie (de boodschap kan slechts ontvangen worden nadat ze verstuurd is). Wenst men enkel synchronisatie, dan kan men dit bereiken door het versturen van een lege boodschap. Bij het gebruik van gemeenschappelijke variabelen of asynchrone boodschapsversturing daarentegen zijn beide soorten interactie gescheiden. Data wordt uitgewisseld door het schrijven en lezen van gemeenschappelijke variabelen of communicatiebuffers, en we moeten expliciet synchronisatie aanbrengen om te vermijden dat de variabele gelezen wordt vóór hij geschreven is (anders hebben we een raceconditie). Deze synchronisatie gebeurt m.b.v. speciale systeemroutines die bewerkingen uitvoeren op gemeenschappelijke geheugenplaatsen of op de communicatie-kanalen. We kunnen twee types synchronisatie gebruiken: mutuele exclusie: dit soort synchronisatie heeft tot doel ervoor te zorgen dat een gemeenschappelijk object niet door twee of meer