Faculteit Ingenieurswetenschappen Vakgroep Elektronica en informatiesystemen Voorzitter: prof. dr. ir. J. Van Campenhout Parallellisatie en optimalisatie voor de Cell BE architectuur door Michiel Questier Promotor: prof. dr. ir. K. De Bosschere Thesisbegeleider: ir. S. Rul en dr. ir. H. Vandierendonck Afstudeerwerk ingediend tot het behalen van de graad van burgerlijk ingenieur in de computerwetenschappen Academiejaar 2006–2007 Toelating tot bruikleen De auteur geeft de toelating dit afstudeerwerk voor consultatie beschikbaar te stellen en delen van het afstudeerwerk te copiëren voor persoonlijk gebruik. Elk ander gebruik valt onder de beperkingen van het auteursrecht, in het bijzonder met betrekking tot de verplichting de bron uitdrukkelijk te vermelden bij het aanhalen van resultaten uit dit afstudeerwerk. Michiel Questier 4 juni 2007 i Dankwoord In de eerste plaats wens ik mijn promotor, prof. dr. ir. K. De Bosschere, te bedanken om mij de mogelijkheid te geven deze scriptie in alle vrijheid af te werken. Vervolgens wens ik mijn begeleiders, ir. Sean Rul en dr. ir. Hans Vandierendonck, te bedanken voor hun steun en goede ideëen. Ook ir. Peter Bertels verdient een woord van dank voor zijn hulp bij de presentaties en zijn aanstekelijke enthousiasme. Verder mag ik ook mijn medestudenten niet vergeten, die de laatste jaren aangenamer en interessanter hebben gemaakt. Bedankt Pieter, Francis, Simon, Michiel, Jeroen, Tom en alle anderen die ik hier vergeet. Als laatste wil ik vooral mijn ouders bedanken voor hun onvoorwaardelijke steun gedurende al deze jaren. ii Parallellisatie en optimalisatie voor de Cell BE architectuur door Michiel Questier Afstudeerwerk ingediend tot het behalen van de graad van burgerlijk ingenieur in de computerwetenschappen Academiejaar 2006–2007 Universiteit Gent Faculteit Toegepaste Wetenschappen Promotor: prof. dr. ir. K. De Bosschere Samenvatting Traditionele superscalaire microarchitecturen met een enkele rekenkern bereiken stilaan hun limiet wat prestatie en vermogenverbruik betreft. Daarom wordt steeds vaker voor een CMP-architectuur (Chip multiprocessor ) gekozen, waarbij meerdere (eenvoudige) kernen op één chip worden geı̈ntegreerd. De Cell Broadband Engine, hierna Cell , is een heterogene multiprocessor met niet minder dan negen kernen. In eerste instantie wordt de architectuur van de Cell onderzocht. Deze blijkt complex maar uiterst krachtig te zijn, waardoor bij het programmeren steeds de onderliggende architectuur in het achterhoofd moet worden gehouden. Om bestaande programma’s aan te passen wordt een programmeermodel voorgesteld, dat stapsgewijs het potentieel van de Cell aanwendt. Dit model wordt toegepast om een eenvoudig algoritme voor matrixvermenigvuldiging aan te passen. Hieruit blijkt dat met relatief weinig inspanning de uitvoeringstijd met een grootteorde daalt. Vervolgens wordt de opgedane kennis gebruikt om een meer complex algoritme te optimaliseren voor de Cell . Het betreft Clustal W , een programma uit de bio-informatica om meerdere DNA-sequenties te aligneren. Na een toelichting van de onderliggende concepten, profileren we het programma. Aan de hand van een groot aantal inputs wordt nagegaan wat de invloed van de parameters zijn. Hieruit kan een beeld worden gevormd over de tijdsverdeling van de verschillende stappen in het algoritme. Na parallellisatie en optimalisatie van de meest tijdsrovende functies, bekomen we een aangepast programma dat de mogelijkheden van de Cell zo optimaal mogelijk tracht uit te buiten. iii We besluiten dat de Cell een hoge complexiteit en een stijle leercurve heeft, maar potentieel een enorme prestatie met zich meebrengt. Afhankelijk van de input kan Clustal W met ongeveer een factor 2 worden versneld ten opzichte van de traditionele implementatie. Trefwoorden: computerarchitectuur, parallellisatie, bioinformatica iv Parallelization and optimization for the Cell BE architecture Michiel Questier Supervisor(s): Sean Rul, Hans Vandierendonck, prof. Koen De Bosschere B. Synergistic Processor Element Abstract— This article gives a short overview of the Cell architecture. The differences with more traditional processor architectures are highlighted. This shows an innovative but complex design, so that incremental modifications are necessary in order the facilitate the optimization of an algorithm for the Cell. This process is illustrated by adapting Clustal W for execution on the Cell. The SPEs are the real workhorses of the Cell. Eight in total, they implement a design optimized for data intensive computations. They consist of the following elements: • The core is a 128-bit RISC vector processor with a totally new instruction set. Because of the wide registers, SIMD instructions are especially suited for the SPE. • Each SPE has its own local store. This memory is only 256 KiB large, and contains all the necessary instructions and data. Data can be transferred from and to the main memory or local stores of other SPEs by issuing explicit Direct Memory Access (DMA) instructions. • All SPEs are connected with the EIB through a Memory Flow Controller (MFC). The MFC works asynchronically with the execution core of the SPE, so that computations and data transfers can be executed in parallel. These elements enable high theoretical performance on the SPEs, but because of the high complexity and responsibility of the programmer, this is often difficult to achieve. Keywords—Computer architecture, parallelization, bioinformatics I. I NTRODUCTION S INCE frequency scaling of complex out-of-order architectures has diminished over the years, and power consumption is becoming increasingly troublesome, more and more microprocessors are implementing multiple cores. Instead of relying on one overly complex core to do all calculations, computations are parallelized over multiple simple cores. By keeping the individuals cores simple, problems such as power consumption are much less of an issue for multi-core processors. However, the task of parallelizing the workload over multiple cores is often a difficult task for the programmer. The Cell BE architecture [2] builds upon the idea of multiprocessors to achieve high performance, but extends this by implementing more and simpler heterogeneous cores in comparison with many of the other modern multiprocessors. This comes at a cost however, since the complexity is high and the learning curve for a programmer steep. C. Element Interconnect Bus The EIB sits at the heart of the Cell and connects the processor elements with each other and the main memory. Since the SPEs rely on the EIB to provide new data by issuing DMA requests, a high bandwidth is necessary to avoid a bottleneck. The EIB offers a total peak bandwidth of 204,8 GB/s between all elements and 25,6 GB/s to main memory. Because of this high bandwidth, data can be transferred to and from the local store of the SPEs at high speed, offsetting the limitation of it’s small size. II. T HE C ELL A RCHITECTURE The Cell consist of two different types of processor elements: one PowerPC Processor Element (PPE) and a total of eight identical Synergistic Processor Elements (SPE). These are connected with each other and the main memory through the Element Interconnect Bus (EIB). III. C LUSTAL W A. Description A. PowerPC Processor Element Clustal W is a program for multiple sequence alignation. The alignation of DNA-sequences is useful for determining the properties of new sequences by matching them to sequences with known properties. The algorithm consists of three main phases: 1. Pairwise alignment (PW). Aligning all sequences at once consumes too much time because the search space grows too big. The first phase aligns all possible pairs of sequences and stores the score of the alignment, which indicates how closely the two sequences match, in a matrix. 2. Guide tree (GT). Based on the scores from the previous phase, a guide tree is built. This facilitates the execution of the next phase. 3. Progressive alignment (PA). The last phase uses the guide tree to choose a new sequence to be aligned with a group of The PPE is a 64-bit RISC processor based on the existing PowerPC architecture. Its instruction set is fully compatible with the POWER instruction set, enabling the PPE to execute existing binaries for the PowerPC, in particular an operating system. Since the design is very much alike with most processors, the targeted functionality of the PPE is extremely broad. In theory it is able to adequately execute most programs. Because the SPEs are designed for high computational speed, the PPE’s main responsibility is to offer control for the execution on the SPEs. The PPE most often serves as a master thread for the SPEs, communicating with them to ensure synchronisation and an optimal workload balance. v already aligned sequences. The end result is a global alignment of all sequences. B. Analysis The execution times of the phases scales differently with varying parameters. The two defining parameters are the number of sequences, N , and the average length of the sequences, L. The complexity of the phases [3] indicates how the execution of the phases will scale. However, the global impact of the phases on the total execution time can not be determined by the time complexity alone. By executing Clustal W and timing the execution time of the phases for a large number of different input sets, an empirical conclusion can be drawn on the most time consuming phases. A problem arises however, because the test input sets have very similar parameters. Only the length of the sequences varies, while the number of sequences remains almost the same. To overcome this limitation, random input sets are generated with requested parameters such as length and number. The randomness of the sequences has no perceived impact on the execution times. The result of a select number of input sets is illustrated in Figure 1. The small local stores soon prove to be a limiting factor, and the execution fails when the input sets grow to a significant size. This can be avoided by only keeping the relevant data for the current computation in the local store. As soon as data becomes obsolete, even if for a short amount of time, it should be replaced with necessary or soon to be necessary data. Normally, this would severely limit the performance. Since DMA transfers can be executed in parallel with computations and the EIB offers very high bandwidth, this is not so much of an issue on the Cell. By limiting memory consumption throughout the algorithm, a very wide set of inputs can be executed. C.2 Progressive alignment Parallelizing the progressive alignment proves to be more difficult. The only parallelism found is two independent loops in the frequently called pdiff function. Theoretically, this would lead to a speedup of 2 for the progressive alignment. Creating a new thread each time pdiff is called creates too much overhead, which can be solved by statically creating two threads at the beginning of the last phase. A call to pdiff can then be replaced by writing a signal message in the mailboxes of the SPEs. Even though the reduction of the thread creation overhead significantly speeds up the progressive alignment, the parallelized version is still slower than the original one. This is due to the large amounts of communication needed in order to correctly synchronize the threads. Only a small fraction of the time the two SPE threads are alive is spent by useful calculations. The lack of parallelism and the need for significant communication makes the phase of the progressive alignment poorly suited for execution on the SPEs. IV. R ESULTS Fig. 1. Normalized execution time of different phases in function of length (above) and number (below) of sequences. This clearly shows that on average, the pairwise alignment takes the longest amount of time, followed by the progressive alignment. As noted, the guide tree phase does not scale with increasingly large sequences, and therefore is only the most important phase when the number of sequences is very large in comparison with the length of the sequences. In order to optimize the average case, pairwise alignment is optimized first. Progressive alignment follows next. C. Parallelization and Optimization C.1 Pairwise alignment Parallelizing the phase of the pairwise alignment is relatively easy since the alignment of two sequences is not dependent on other sequences. Since there are no dependencies for the calculation of the matrix elements, the workload can easily be divided among all SPEs. However, it is not that trivial to divide the workload into equal parts. This can be solved by letting the PPE dynamically balance the workload, guaranteeing that all SPEs receive approximately the same amount of work. vi Even though only the first phase can be sped up, a global speedup of 1,6 to 2,2 for input set A and B from the BioPerf suite [1]. V. C ONCLUSIONS The Cell offers high performance, but the complexity is likewise very high. Incremental optimization is the key to achieve high performance while limiting the difficulties for the programmer. R EFERENCES [1] Bader, D.A. and Sachdeva, V., An Open Benchmark Suite for Evaluating Computer Architecture on Bioinformatics and Life Science Applications, [2] IBM, Cell Broadband Engine programming handbook version 1.0, [3] Chaichoompu, K. and Kittitornkun, S. and Tongsima, S., MT-ClustalW: multithreading multiple sequence alignment, 20th International Parallel and Distributed Processing Symposium Inhoudsopgave 1 Inleiding 1 1.1 Probleemstelling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Doelstelling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.3 Onderverdeling van de scriptie . . . . . . . . . . . . . . . . . . . . . . . . . 4 2 De Cell BE architectuur 2.1 2.2 2.3 2.4 2.5 6 Achtergrond . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.1.1 Inleiding en motivatie . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.1.2 De drie prestatielimiterende muren . . . . . . . . . . . . . . . . . . . 7 Overzicht van de Cell Broadband Engine architectuur . . . . . . . . . . . . 9 2.2.1 De processorelementen . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.2.2 Element Interconnect Bus . . . . . . . . . . . . . . . . . . . . . . . . 10 2.2.3 Memory Interface Controller . . . . . . . . . . . . . . . . . . . . . . 11 PowerPC Processor Element . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.3.1 PowerPC Processor Unit . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.3.2 PowerPC Processor Storage Subsystem . . . . . . . . . . . . . . . . 14 2.3.3 PowerPC instructies . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Synergistic Processor Element . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.4.1 Synergistic Processor Unit . . . . . . . . . . . . . . . . . . . . . . . . 16 2.4.2 Memory Flow Controller . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.4.3 SPU instructieset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Programmeeromgeving . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3 Matrixvermenigvuldiging 3.1 3.2 24 Inleiding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.1.1 Keuze algoritme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.1.2 Gelijkaardig werk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Programmeermodel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 vii 3.3 3.4 3.5 3.6 Matrixvermenigvuldiging op de PPU . . . . . . . . . . . . . . . . . . . . . . 28 3.3.1 Aanpassingen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3.3.2 Resultaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 Matrixvermenigvuldiging op een enkele SPU . . . . . . . . . . . . . . . . . . 29 3.4.1 Aanpassingen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.4.2 DMA-transfers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 3.4.3 Resultaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Parallellisatie over meerdere SPU’s . . . . . . . . . . . . . . . . . . . . . . . 35 3.5.1 Keuze parallellisatieschema . . . . . . . . . . . . . . . . . . . . . . . 35 3.5.2 Resultaten en schaalbaarheid . . . . . . . . . . . . . . . . . . . . . . 35 Optimalisaties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.6.1 Dubbele buffering . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.6.2 Lusontvouwing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 4 Clustal W 4.1 4.2 43 Algemene beschrijving . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 4.1.1 Achtergrond . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 4.1.2 Beschrijving algoritme . . . . . . . . . . . . . . . . . . . . . . . . . . 44 4.1.3 Aanverwant werk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 4.2.1 Theoretische analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 4.2.2 Empirische analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 4.3 Initiële aanpassingen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 4.4 Paarsgewijze alignatie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 4.4.1 Uitvoering op de SPU . . . . . . . . . . . . . . . . . . . . . . . . . . 57 4.4.2 Parallellisatie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 4.5 Progressieve alignatie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4.6 Mogelijke uitbreidingen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 5 Besluit 70 A Bijkomende resultaten 72 A.1 Gedetailleerde resultaten matrixvermenigvuldiging . . . . . . . . . . . . . . 72 A.2 Gedetailleerde resultaten empirische analyse Clustal W . . . . . . . . . . . . 76 viii Lijst van figuren 2.1 Overzicht van de opbouw van de Cell . . . . . . . . . . . . . . . . . . . . . 9 2.2 Fysieke implementatie van de Cell . . . . . . . . . . . . . . . . . . . . . . . 10 2.3 Overzicht van de opbouw van de PPE . . . . . . . . . . . . . . . . . . . . . 12 2.4 Gedetailleerd overzicht van de opbouw van de PPE 2.5 Overzicht van de opbouw van de SPE . . . . . . . . . . . . . . . . . . . . . 16 2.6 Gedetailleerd overzicht van de opbouw van de SPU 2.7 Overzicht van de opbouw van de MFC 2.8 Schematische voorstelling verloop DMA transfer . . . . . . . . . . . . . . . 22 3.1 Uitvoeringstijd matrixvermenigvuldiging op de PPU met en zonder luste- . . . . . . . . . . . . . 13 . . . . . . . . . . . . . 17 . . . . . . . . . . . . . . . . . . . . 20 geling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 3.2 Vergelijking uitvoering matrixvermenigvuldiging op de PPU en de SPU. . . 34 3.3 Schaling matrixvermenigvuldiging in functie van aantal SPU’s en dimensie. 3.4 Conceptuele werking dubbele buffering. . . . . . . . . . . . . . . . . . . . . 37 3.5 Verhouding DMA-latentie en berekeningen tussen origineel en gebufferd 36 algoritme. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 3.6 Uitvoeringstijd voor origineel, gebufferd en ontvouwd algoritme. . . . . . . . 40 3.7 Verhouding berekeningen gebufferd en ontvouwd algoritme op origineel. . . 42 4.1 Uitvoeringstijd van Clustal W bij variërende lengte en aantal sequenties . . 52 4.2 Uitvoeringstijd deelfasen bij vaste lengte en aantal sequenties. . . . . . . . . 53 4.3 Procentueel aandeel van de verschillende deelfasen op de uitvoeringstijd. . . 55 4.4 Verhouding van de uitvoeringstijden bij enkeldradige uitvoering. . . . . . . 60 4.5 Keuze statisch en dynamisch verdeelde werklast. . . . . . . . . . . . . . . . 62 4.6 Genormaliseerde verhouding van de uitvoeringstijden bij meerdradige uitvoering. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 ix Lijst van tabellen 2.1 Overzicht van de belangrijkste PPU instructies. . . . . . . . . . . . . . . . . 15 2.2 Aandeel latentie DMA-fases bij kloksnelheid van 3.2 GHz. . . . . . . . . . . 22 2.3 Overzicht van de belangrijkste SPU instructies. . . . . . . . . . . . . . . . . 23 3.1 Opgemeten verhouding DMA-transfer en berekeningen bij matrixvermenigvuldiging op 1 SPU. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 4.1 Tijdscomplexiteit van het originele Clustal W-algoritme. . . . . . . . . . . . 48 A.1 Resultaten matrixvermenigvuldiging origineel algoritme. . . . . . . . . . . . 73 A.2 Resultaten matrixvermenigvuldiging gebufferd algoritme. . . . . . . . . . . 74 A.3 Resultaten matrixvermenigvuldiging algoritme met lusontvouwing. . . . . . 75 A.4 Resultaten empirische analyse Clustal W. . . . . . . . . . . . . . . . . . . . 77 x Hoofdstuk 1 Inleiding 1.1 Probleemstelling De in 1965 opgestelde wet van Moore voorspelde dat het aantal transistoren op een geı̈ntegreerd circuit om de 18 maanden zou verdubbelen [28]. Hoewel dit recent werd afgezwakt naar een verdubbeling om de twee jaar, blijkt deze wetmatigheid over de laatste 40 jaar stand te houden. In het domein van de computerarchitectuur heeft dit als gevolg dat de complexiteit van een microarchitectuur potentieel om de twee jaar verdubbelt. Om deze ontwerpsvrijheid om te zetten in een prestatieverhoging, werden technieken ontwikkeld zoals superscalaire architecturen en out-of-order uitvoering. Kenmerkend aan deze technieken is dat ze een enkele rekenkern uitbreiden met extra functionaliteit om zo een prestatieverhoging uit de instructiestroom te extraheren. De traditionele techieken hebben echter een aantal beperkingen: • De meeste technieken hebben als doel om het aantal instructies per klokcyclus (IPC) te verhogen. Een belangrijke observatie hierbij is dat opeenvolgende instructies niet altijd afhankelijk van elkaar zijn, en dus niet in de aangeboden volgorde moeten worden uitgevoerd om de correctheid van het programma te bewaren. Hoewel studies [18] uitwijzen dat dit Instruction Level Parallelism theoretisch een IPC van 60 tot 70 kan opbrengen, blijkt in de praktijk slechts 1.5 tot 2 consistent haalbaar te zijn. De reden hiervoor is dat het detecteren van onafhankelijkheden tussen instructies niet triviaal is. • Hoewel de prestatie van nieuwe processors vrij goed de wet van Moore volgt, is de groei bij de snelheid van het geheugen minder snel. Omdat de groei in snelheid exponentieel is, wordt de kloof steeds groter. Om deze Memory Gap [36] te overbruggen zijn er over de jaren heen meerdere niveau’s van tussengeheugen ingevoerd. Omdat 1 snelle geheugens duur zijn en veel plaats op de chip innemen, is het idee om dicht bij de processor kleine maar snelle geheugens te plaatsen die de processor voldoende snel van data en instructies kunnen voorzien. Naarmate het niveau van het geheugen zich verder van de processor bevindt, wordt dit groter maar trager. Het probleem hiermee is dat er steeds meer niveau’s nodig zijn om de kloof te dichten, wat op lange termijn en op het gebied van schaalbaarheid geen werkbare oplossing is. • Door de miniaturisatie van de transistoren is het mogelijk om de kloksnelheid van nieuwe ontwerpen op te voeren. Er lijkt echter een grens te zijn aan deze frequentieschaling. Een van de redenen is dat het steeds moeilijker wordt om transistoren met een kleinere afmeting te produceren1 . Veel problematischer is echter dat het vermogenverbruik kubisch afhankelijk is van de frequentie. P V ⇒P 1 2 V f Ca 2 ∼ f 1 3 ∼ f Ca 2 ∼ Een verdubbeling in kloksnelheid voor eenzelfde ontwerp heeft dus een verachtvoudiging van het vermogenverbruik tot gevolg. Dit heeft grote consequenties op onder andere het gebied van betrouwbaarheid en koeling. • Door de jaren heen is er veel onderzoek gedaan naar technieken die de prestatie van een enkele microprocessor kan verhogen. De meest veelbelovende zijn echter al geı̈mplementeerd, en nieuwe technieken zijn meestal ofwel moeilijk realiseerbaar of bieden weinig meerwaarde ten opzichte van hun hardwarekost. Er is dus sprake van een verminderde meerwaarde van nieuwe technieken. Vanwege de voorgaande problemen wordt er steeds meer gekozen voor Chip Multiprocessor architecturen, waar meerdere (eenvoudige) rekenkernen op één enkele chip worden geı̈ntegreerd. Indien het uitgevoerde programma in twee of meer onafhankelijke delen kan worden opgesplitst, kan elke kern afzonderlijk een deel van het programma uitvoeren. Op deze manier is een aanzienlijke versnelling mogelijk ten opzichte van de uitvoer op een enkele kern, zonder dat een individuele kern heel complex hoeft te zijn. Daardoor worden problemen zoals schaling en vermogenverbruik beter aangepakt dan bij traditionele microarchitecturen met een enkele kern. Er zijn echter een aantal bedenkingen te maken bij deze techniek: 1 Huidige processors zijn ontworpen volgens een 65 nm ontwerp, met een verwachte verkleining naar 45 nm op het einde van dit jaar 2 • Omdat de prestatieverhoging wordt bekomen door het opsplitsen van een programma in meerdere afzonderlijke delen, is het detecteren van dit parallellisme van groot belang. De verantwoordelijkheid hiervan ligt nog bij de programmeur, die dus extra inspanningen moet leveren om maximale prestatie te halen uit een chip met meerdere kernen. Hij moet ook de communicatie tussen de verschillende draden op zich nemen, en andere synchronisatieproblemen oplossen. Uit de praktijk blijkt het concept van parallelle uitvoering niet eenvoudig om als progammeur mee te redeneren. Er wordt dan ook veel onderzoek gedaan naar automatische detectie van parallellisme op programma- of functieniveau. • Vaak blijken algoritmes slechts voor een bepaald deel parallelliseerbaar te zijn. Indien een deel van het programma niet over meerdere kernen valt te verdelen, is er een bovengrens op de maximaal verkrijgbare versnelling, uitgedrukt in de wet van Amdahl: versnelling = 1 f: (1 − f ) + Sf versnelbare f ractie programma S: versnelling van f ractie programma Indien bijvoorbeeld slechts de helft van een programma kan versneld worden (f = 0.5) met een versnelling van een factor 2 (S = 2), dan is de totale versnelling van het programma slechts 4/3. De bovengrens op de versnelling wordt in dit geval bereikt wanneer S = ∞ en bedraagt slechts 2. • Het komt vaak voor dat meerdere uitvoeringsdraden op een gemeenschappelijke variabele werken. Om te voorkomen dat het aanpassen van een variabele teniet wordt gedaan door een andere draad of dat een draad verder rekent met een oude waarde, moet het geheugen consistent worden gehouden voor alle draden. Er bestaan verschillende coherentieprotocollen om dit probleem op te lossen, die rekening houden met gedeeld geheugen (waarbij elke draad hetzelfde beeld heeft van het geheugen) of lokaal geheugen (waarbij elke draad slechts inwerkt op zijn eigen deel van het geheugen). Ondanks deze bijkomende moeilijkheden lijken CMP-ontwerpen toch de toekomst te worden. Dualcore processors, met twee kernen, lijken standaard te worden, en steeds vaker duiken modellen met vier kernen op. De Sun Niagara [3] heeft zelfs acht kernen, en de Intel Teraflops Research Chip [16] maar liefst 80. De Cell is een CMP-architectuur die het 3 idee van dualcores nog extremer doortrekt. In totaal zijn er niet minder dan negen rekenkernen aanwezig. Om de grootte van de chip te beperken, zijn acht van deze kernen sterk vereenvoudigd in vergelijking met superscalaire out-of-order microarchitecturen. Daarnaast is het geheugenmodel radicaal anders: de acht eenvoudige kernen hebben slechts een beperkt lokaal geheugen. Indien data tussen deze lokale geheugens of het hoofdgeheugen moet worden uitgewisseld, zijn er expliciete Direct Memory Acces (DMA) instructies nodig. Hoewel de Cell een ongelooflijke theoretische maximumprestatie van 200 GFLOPS heeft, is de complexiteit en de verantwoordelijkheid voor de programmeur zeer hoog. 1.2 Doelstelling Het doel van deze scriptie is te onderzoeken wat de pijnpunten zijn van het programmeren voor de Cell . Daarnaast wordt nagegaan op welke manier efficiënt deze problemen kunnen worden voorkomen, zowel voor nieuwe programma’s als voor het aanpassen van bestaande programma’s. Naast een aantal welbekende technieken voor parallellisatie blijken ook een aantal specifieke optimalisaties noodzakelijk te zijn om maximale prestatie op de Cell te bekomen. 1.3 Onderverdeling van de scriptie In hoofdstuk 2 wordt de architectuur van de Cell meer in detail bekeken. Hoewel er hierover erg veel valt te schrijven, wordt enkel toegespitst op de punten waar de Cell verschilt van de meeste andere architecturen en die belangrijk zijn voor de rest van dit werk. De onderliggende redenen van de ontwerpsbeslissingen worden hierbij uiteen gezet. Hoofdstuk 3 licht de verschillen tussen de Cell en andere microarchitecturen verder toe aan de hand van een eenvoudig voorbeeld. Er wordt gekozen voor matrixvermenigvuldiging, een triviaal en welbekend algoritme. De nadruk ligt dan ook niet zozeer op het algoritme zelf, maar op de nodige aanpassingen die moeten worden gedaan om maximale prestatie te bekomen. Hieruit blijkt dat de complexiteit in eerste instantie meevalt, maar dat er enkele extra stappen moeten worden ondernomen om de moeilijkheden van de Cell te omzeilen. Desondanks blijkt de bekomen prestatieverhoging groot te zijn, waardoor het beloofde potentieel van de Cell gerechtvaardigd blijkt te zijn. Vervolgens wordt de opgedane kennis toegepast op een interessanter probleem. In hoofdstuk 4 wordt Clustal W aangepast, een programma gebruikt in de bioinformatica om meerdere DNA-sequenties te aligneren. Na een uiteenzetting van de belangrijkste concepten van het algoritme, wordt het programma aangepast voor uitvoering op de Cell . Na een grondige analyse aan de hand van een groot 4 aantal inputs wordt een beeld bekomen van de belangrijkste parameters die invloed hebben op de uitvoeringstijd van het programma. Dit laat ons vervolgens toe om onze aandacht toe te spitsen op de functies die het grootste aandeel in de uitvoeringstijd hebben. Deze delen van het programma worden geparallelliseerd en geoptimaliseerd, rekening houdend met de onderliggende architectuur en de opgedane kennis uit de vorige hoofdstukken. Hoewel het aanpassen van een bestaand en vrij complex algoritme een grote inspanning vergt, blijkt ook hier de bekomen versnelling, afhankelijk van de input, aanzienlijk te zijn. Hoofdstuk 5 vat de belangrijkste besluiten samen. 5 Hoofdstuk 2 De Cell BE architectuur 2.1 2.1.1 Achtergrond Inleiding en motivatie De Cell Broadband Engine, hierna Cell , is de eerste telg van een nieuwe familie microprocessors, de Cell Broadband Processor Architecture (CBEA). De CBEA is een uitbreiding op de bestaande 64-bits PowerPC-architectuur, en is ontwikkeld door Sony, Toshiba en IBM. Oorspronkelijk was de Cell bedoeld voor gebruik in spelconsoles zoals de Playstation 3, maar de architectuur en implementatie zijn ontworpen om een fundamentele stap voorwaarts te zetten in de prestatie van processors. Daardoor is een veel breder gamma aan toepassingen mogelijk. De Cell is een heterogene multiprocessor met negen processors. In dat opzicht wordt de recente trend in processors gevolgd. Vernieuwend is echter dat de Cell twee soorten gespecialiseerde processors bevat, namelijk een enkel PowerPC Processor Element (PPE) en acht Synergistic Processor Elements (SPE). Het eerste type, de PPE, is gebaseerd op de bestaande 64-bits PowerPC-architectuur, en is er volledig compatibel mee. Daardoor kan de PPE bestaande 32-bit en 64-bit besturingssystemen en applicaties draaien. Het tweede type, de SPE, is geoptimaliseerd voor rekenintensieve taken. Het idee, en de verklaring voor het synergistische in de naam van de SPE’s, is dat de SPE’s en de PPE van elkaar afhankelijk zijn om alle functionaliteit op een optimale manier te implementeren. De SPE’s hangen af van de PPE om het besturingssysteem te draaien en de controle-intensieve algoritmes uit te voeren, terwijl de PPE hoofdzakelijk op de SPE’s vertrouwt om de overgrote meerderheid van de prestatie te verzorgen. De SPE’s kunnen in een standaard hoogniveautaal zoals C en C++ worden 6 geprogrammeerd. Naast een uitgebreide instructieset bieden ze ook ondersteuning voor SIMD-instructies. De PPE ondersteunt de bestaande PowerPC Architecture Vector/SIMD Multimedia Extension. Het grootste verschil tussen de SPE’s en de PPE is echter de manier waarop het geheugen wordt benaderd. De PPE werkt op de traditionele manier in op het hoofdgeheugen, waarbij loads en stores data transfereren tussen private registers en het hoofdgeheugen, eventueel met caches als tussenliggende laag. De SPE’s hebben echter enkel toegang tot het hoofdgeheugen via Direct Memory Access (DMA) commando’s van en naar een beperkt lokaal geheugen waarin zowel data als instructies worden opgeslagen. De loads en stores uit de SPE-instructieset werken enkel in op dit lokaal geheugen in plaats van op het hoofdgeheugen. Vanwege de asynchrone werking tussen de rekeneenheid van de SPE en de DMA-controller, wordt er parallellisme bekomen op het gebied van berekeningen en opvragen van data. De achterliggende redenering is dat de latentie van het hoofdgeheugen stijgt in verhouding met de processorsnelheid. Uitgedrukt in klokcycli is er sprake van een factor honderd en meer over de laatste 20 jaar [20]. Dit heeft als gevolg dat de theoretische piekprestatie van een processor beperkt wordt door de latentie van het geheugen. Indien een load bij een traditionele processor een cachemisser op alle niveaus tot gevolg heeft, wordt de processor voor enkele honderden cycli geblokkeerd. Hiertegenover staat dat het aantal nodige cycli om een DMA-transfer op te zetten vanuit de SPE’s relatief klein is [26]. Een bijkomend probleem bij conventionele architecturen is dat zelfs met kostelijke speculatie vaak slechts enkele onafhankelijke geheugentoegangen tegelijk kunnen worden aangevraagd. Bij een model met expliciete DMA-instructies kan elke SPE een groot aantal gelijktijdige toegangen hebben uitstaan, zonder dat er nood is aan speculatie. Hiertoe moeten geheugentoegangen echter voldoende op voorhand worden aangevraagd, zodat de DMA-controller deze kan ordenen en op tijd kan afhandelen. Indien dit efficiënt gebeurt, is de data beschikbaar op het moment dat de SPE er berekeningen wil op uitvoeren. 2.1.2 De drie prestatielimiterende muren De Cell probeert de drie factoren die prestatie het meest beperken te omzeilen: vermogen, geheugen en processorfrequentie. De vermogenmuur Recent wordt er steeds meer aandacht besteed aan de dissipatie van vermogen in een geı̈ntegreerd circuit. De limiterende factor in het ontwerp is dan ook steeds meer dit vermogenverbruik onder controle krijgen, en niet de beschikbare werkmiddelen zoals tran- 7 sistoren. Indien de prestatie van een processor moet toenemen, is het dus nodig om vermogenefficiëntie aan een zelfde tempo te laten meeschalen met prestatieverhogingen. De manier waarop dit in de Cell wordt aangepakt is door een onderscheid te maken tussen processors geoptimaliseerd voor het uitvoeren van besturingssystemen en programma’s met een moeilijk controleverloop enerzijds, en processors geoptimaliseerd voor rekenintensieve taken anderzijds. Deze specialisatie vinden we terug in de PPE en de SPE respectievelijk. Beide soorten processors kunnen worden geoptimaliseerd voor hun specifieke toepassing, wat de verhouding tussen prestatie en vermogenverbruik in zo’n gevallen sterk vergroot. Doordat de processors relatief simpel kunnen worden ontworpen, kan een hoge klokfrequentie worden bekomen aan een laag werkingsvoltage. De geheugenmuur Ondanks innovatieve aanpassingen, zoals het integreren van de geheugencontroller, is de latentie tussen het hoofdgeheugen en processors de grens van 1000 klokcycli aan het naderen [19]. De prestatie van een programma wordt dan ook gedomineerd door de manier waarop datatransfer wordt aangepakt. Optimaliseren van programma’s wordt dan ook steeds vaker een oefening in het optimaal benutten van de caches. De Cell lost dit op twee manieren op. • Drie lagen in de geheugenhiërarchie: hoofdgeheugen, lokaal geheugen voor elke SPE, en een groot aantal registers voor elke SPE. • Asynchrone DMA-aanvragen tussen het hoofdgeheugen en het lokaal geheugen. Door deze aanpassingen kan de programmeur een groot aantal zinnige datatransfers aanvragen, terwijl er in parallel wordt gerekend met de data die zich reeds in het lokaal geheugen bevindt. De frequentiemuur Om de frequentie van nieuwe processors op te voeren, zijn steeds diepere pijplijnen nodig1 . Moderne processors zijn echter op een punt gekomen waar de meerwaarde van extra pijplijntrappen sterk vermindert, zeker wanneer vermogenverbruik in rekening wordt genomen. Door het gespecialiseerd ontwerp van de PPE en de SPE zijn diepe pijplijnen niet nodig. De SPE bekomt zijn efficiëntie uit een groot registerbestand, waardoor een groot 1 De Intel Pentium IV Prescott-core met 31 pijplijntrappen is daar een extreem voorbeeld van. 8 Figuur 2.1: Overzicht van de opbouw van de Cell aantal lusoptimalisaties mogelijk is. De latentie van het lokaal geheugen is bovendien deterministisch en bedraagt 6 klokcycli. Hierdoor is de compiler in staat om een zo optimaal mogelijk statische scheduling van de instructies te bepalen, zodat out-of-order technieken niet nodig zijn. 2.2 2.2.1 Overzicht van de Cell Broadband Engine architectuur De processorelementen Figuur 2.1 geeft een schematisch overzicht van de opbouw van de Cell . Een afbeelding van de fysieke realisatie is weergegeven in Figuur 2.2. Deze bestaat uit een enkele PPE en acht SPE’s, die met elkaar, het hoofdgeheugen en randapparaten zijn verbonden door middel van de Element Interconnect Bus (EIB). Het hoofdgeheugen bestaat uit Extreme Data Rate (XDR) RAM die wordt benaderd vanuit de Memory Interface Controller (MIC). Communicatie met andere Cell -processors is mogelijk via de Cell Broadband Engine Interface (BEI). De PPE bevat een 64-bits PowerPC RISC2 rekenkern die twee draden tegelijk kan uitvoeren. Er zijn standaard caches aanwezig, namelijk 32KiB L1 data- en instructiecaches en 512 KiB L2 geünificeerde cache (zowel voor data als instructies). De instructieset is een uitbreiding van de PowerPC instructieset, en de PPE is in staat bestaande code voor de PowerPC architectuur uit te voeren. Het vooropgestelde doel van de PPE is om het besturingssysteem te draaien, de controle op hoog niveau te verzorgen en de verschillende SPE-draden te beheren. 2 RISC : Reduced Instruction Set Computer. 9 Figuur 2.2: Fysieke implementatie van de Cell De acht SPE’s zijn onderling identieke vectorprocessors. Door het gebruik van Single Instruction, Multiple Data (SIMD) instructies zijn ze uitermate geschikt voor operaties die dataintensief zijn. De rekenkern van de SPE is een RISC kern, met 256 KiB lokaal geheugen en 128 registers van 128 bits breedte. Naast een SIMD instructieset zijn er gespecialiseerde instructies voor DMA-transfers en communicatie met de andere processorelementen. De DMA-instructies worden vanuit de rekenkern van de SPE aangeleverd aan een Memory Flow Controller (MFC) die aanwezig is in elke SPE. Deze communicatie gebeurt via unidirectionele kanalen. De status van deze kanalen kan door andere SPE’s of de PPE worden benaderd door Memory Mapped Input Output(MMIO) registers. 2.2.2 Element Interconnect Bus De Element Interconnect Bus is een communicatienetwerk die de verschillende componenten van de Cell met elkaar verbindt. Over dit netwerk wordt data en instructies uitgewisseld tussen de processorelementen onderling en met het hoofdgeheugen. De EIB is opgebouwd uit vier dataringen van 16 bytes breed, die werken op de helft van de kloksnelheid van de processors3 . Twee lopen in wijzerzin, de andere twee in tegenwijzerzin. Op elke ring kunnen er tot drie gelijktijdige datatransfers worden toegelaten, zolang hun pad op de ring niet overlapt. Om toegang tot de bus te krijgen, wordt vanuit de elementen verbonden met de bus een aanvraag gedaan. Een arbiter die deel uitmaakt van de EIB zal de aanvragen toewijzen aan een ring die de afstand tussen bron en bestem3 Standaard komt dit neer op 1.6 GHz. 10 ming minimaliseert. Er wordt verder op toegezien dat er geen aanvragen worden gestart die interfereren met transfers die reeds in uitvoering zijn. Belangrijk hierbij is om op te merken dat de volgorde waarin aanvragen worden gedaan niet noodzakelijk de volgorde is waarin ze worden uitgevoerd. Er kan wel een relatieve volgorde worden vastgelegd aan de hand van fences en barriers. Aangezien de vernieuwende geheugenstructuur aan de basis staat van de prestatieverhoging van de Cell , kan dan ook worden gesteld dat de EIB het hart van de Cell is. De theoretische piekbandbreedte is 204.8 GB/s4 , waaruit blijkt dat de prestatielimiterende factor van het geheugen op een doeltreffende manier is aangepakt in de Cell . Om multiprocessorsystemen met meerdere Cell processors te ondersteunen, is er op elke chip een I/O interface waarmee twee processors op een coherente manier met elkaar kunnen worden verbonden door gebruik te maken van het broadband interface (BIF) protocol. 2.2.3 Memory Interface Controller De geı̈ntegreerde geheugencontroller biedt een interface tussen de EIB en het fysieke hoofdgeheugen. Het biedt toegang tot een maximum van twee Rambus Extreme Data Rate (XDR) geheugeninterfaces, voor een maximum van 64 GiB XDR RAM. In totaal kunnen er 64 lees- en 64 schrijftoegangen in een wachtrij worden geplaatst. Er bestaan verder verschillende werkingsmodi, die onder andere voorrang kunnen geven aan de SPE’s of een tragere werking forceren om vermogen te besparen. 2.3 PowerPC Processor Element Het PowerPC Processor Element (PPE) is een 64-bits RISC-processor die voldoet aan de specificaties van de PowerPC architectuur. Hij biedt ondersteuning voor het uitvoeren van twee draden in parallel door middel van Simultaneous multithreading (SMT). Daarnaast is de standaard PowerPC instructieset uitgebreid met SIMD multimedia extensies. De PPE heeft als hoofdtaak de algemene controle van de volledige Cell te verzorgen. Het besturingssysteem wordt uitgevoerd door de PPE, zodat oproepen naar het besturingssysteem afkomstig van de SPE’s zullen gebieren via de PPE om. De PPE bestaat uit twee grote onderdelen, de PowerPC Processor Unit (PPU) en het PowerPC Processor Storage Subsystem (PPSS), zoals te zien op Figuur 2.3. 4 De maximale bandbreedte is beperkt door de snooping van adressen, wat slechts 1 adres per cyclus bedraagt. Elk gesnooped adres kan potentieel 128 bytes transfereren, dus is de theoretische piekbandreedte 128bytes × 1.6GHz = 204.8GB/s 11 Figuur 2.3: Overzicht van de opbouw van de PPE De instructies worden uit een L1 instructiecache gehaald, en een totaal van zes functionele eenheden werkt in op data uit de L1 datacache. De PPSS behandelt geheugenaanvragen van de PPU en externe aanvragen naar de PPE vanuit de SPE’s. Het bevat ook een geünificeerde L2 data- en instructiecache. Een gedetailleerd overzicht van de PPU en PPSS wordt in Figuur 2.4 weergegeven, en hierna meer in detail toegelicht. 2.3.1 PowerPC Processor Unit De PPU bestaat uit de volgende functionele eenheden: • Instruction Unit (IU): De IU bevat de front-end van de pijplijn (instruction-fetch, decode, dispatch, issue en branch) en de completion fase. Het bevat daarom de L1 instructiecache, een 32 KiB grote 2-wegs set-associatieve cache met cachelijnen van 128 bytes en foutbescherming door pariteitsbits. • Load and Store Unit (LSU): De load and store unit voert de datatoegangen vanuit de processor uit. Hiertoe behoren voornamelijk de load en store instructies. De LSU bevat de L1 data cache die dezelfde eigenschappen heeft als de L1 instructiecache, met dat verschil dat ze 4-wegs set-associatief en write-through is. • Vector/Scalar Unit (VSU): De VSU is nog eens in twee onderverdeeld. De FloatingPoint Unit (FPU) en de 128-bit brede Vector/SIMD Multimedia Extension Unit (VXU) staan in voor het uitvoeren van floating-point operaties en vectorinstructies respectievelijk. 12 Figuur 2.4: Gedetailleerd overzicht van de opbouw van de PPE • Fixed-Point Unit (FXU): De FXU voert de instructies op fixed-point (gehele) getallen, zoals optellen, vermenigvuldigen, delen, vergelijken en logische instructies. • Memory Management Unit (MMU): De adresvertaling voor alle geheugentoegangen gebeurt in de MMU. Naast een Segment Lookaside Buffer (SLB) van 64 elementen bevat ze een Translation Lookaside Buffer (TLB) met 1024 elementen. De TLB ondersteunt drie verschillende paginagroottes tegelijkertijd: de standaard 4 KiB, en twee vrije keuzes uit 64 KiB, 1 MiB en 16 MiB. Om parallelle uitvoer van fixed-point en floating-point operaties te vergemakkelijken, genereert de SIMD instructieset van de PPE geen excepties en ondersteunt ze geen complexe functies. Er worden verder slechts weinig resources of communicatiepaden gedeeld met de andere uitvoeringseenheden van de PPE. De adresvertaling in de MMU voldoet aan de eisen van de 64-bits PowerPC architectuur, en ondersteunt volgende groottes van de verschillende adresruimtes: • Werkelijke adresruimte: 242 bytes. Dit zijn de adressen die zich in het werkelijke geheugen bevinden. Dit kan zowel het lokaal geheugen van de SPE’s op de chip zijn als RAM-geheugen en I/O-apparaten. 13 • Effectieve adresruimte: 264 bytes. Deze adressen worden gegenereerd door programma’s. • Virtuele adresruimte: 265 bytes. Dit zijn de adressen die gebruikt worden tussen de MMU van de PPE en de MFC van elke SPE onderling, om te vertalen tussen de werkelijke en relatieve adresruimte. 2.3.2 PowerPC Processor Storage Subsystem De PPSS is het subsysteem dat alle geheugentoegangen van de PPU afhandelt en geheugenconsistentie met de EIB bewaart door snooping operaties. Er is een geünificeerde L2-cache met volgende eigenschappen: 512 KiB groot, 8-wegs set-associatief, write-back en error-correction code (ECC). Ook hier is de cachelijn 128 bytes. Er is slechts een enkele lees/schrijf-poort naar het hoofdgeheugen, maar er is ondersteuning tot acht data-prefetch streams. De data van de L1 datacache is gedupliceerd, en er is ondersteuning voor coherente multiprocessorsystemen. Data wordt uitgewisseld tussen de PPU en de PPSS via een 32-bytes brede leespoort en een 16-bytes brede schrijfpoort. De interface tussen de PPSS en de EIB is zowel voor lezen als schrijven 16 bytes breed. Er is slechts één datatoegang tegelijk, en alle gebeuren in programmavolgorde. In dat opzicht verschilt de PPE met de SPE, waar datatoegangen niet noodzakelijk in programmavolgorde op de EIB worden geplaatst. Voor de volledigheid volgt een kort overzicht van de belangrijkste registers in de PPE. Een uitgebreidere bespreking is te vinden in [23]. Bewerkingen gebeuren steeds op registers, en nooit rechtstreeks op het hoofdgeheugen. De belangrijkste PPE registers zijn: • General-Purpose Registers: In totaal zijn er 32 registers van 64 bits breedte die gebruikt worden voor fixed-point operaties. • Floating-Point Registers: Ook hiervan zijn er 32 registers van 64 bits. Het interne formaat van floating-point getallen is het IEEE 754 dubbele-precisie formaat [25]. Enkele-precisie resultaten worden intern in dubbele-precisie formaat opgeslagen. • Vectorregisters: Er zijn in totaal 32 vectorregisters, maar deze zijn 128 bits breed om efficiënte SIMD-instructies toe te laten. Omdat de PPE hardwarematige ondersteuning biedt voor de gelijktijdige uitvoering van twee uitvoeringsdraden, moeten alle architecturale registers en registers die een speciale functie hebben worden gedupliceerd. De meeste niet-architecturele werkmiddelen zoals de caches worden gedeeld. 14 Tabel 2.1: Overzicht van de belangrijkste PPU instructies. Omschrijving Aantal instructies Latentie(cycli) Uitvoeringseenheid Arithmetisch 59 1 of 2 FXU Spronginstructie 12 1 of 2 BRU Vergelijking 5 1 FXU Data-cache controle 5 N/A5 LSU Deling 8 10 tot 70 FXU Logisch 12 2 FXU Vermenigvuldiging 9 6 tot 15 FXU Load 18 2 LSU Store 17 N/A LSU,FXU FP vergelijking 2 1 FXU FP deling 2 74 FPU FP arithmetisch 20 10 of 11 FPU FP vermenigvuldiging 16 10 of 11 FPU FP afronden of converteren 12 10 of 11 FPU FP worteltrekking 2 84 FPU 2.3.3 PowerPC instructies Tabel 2.1 geeft een overzicht van de belangrijkste instructies in de PowerPC instructieset van de PPU. Voor een gedetailleerde uitleg wordt verwezen naar Appendix A in [19]. 2.4 Synergistic Processor Element De acht Synergistic Processor Elements zijn 128-bit RISC-processors die geoptimaliseerd zijn om rekenintensieve programma’s met een grote databehoefte uit te voeren. Omdat de SPE’s een volledig nieuw ontwerp zijn, voeren ze een volledig nieuwe SIMD instructieset uit. De SPE bestaat uit twee subsystemen, de Synergistic Processor Unit (SPU) en de Memory Flow Controller (MFC). 5 N/A geeft aan dat de instructie geen registers aanpast of enkel kan verklaard worden indien een breder beeld van de processor in acht wordt genomen. 15 Figuur 2.5: Overzicht van de opbouw van de SPE 2.4.1 Synergistic Processor Unit De SPU is de rekenkern die deel uitmaakt van de SPE. Alle instructies die worden uitgevoerd op de SPU worden uit het lokaal geheugen (local store, LS) gehaald. Dit lokaal geheugen is 256 KiB groot, en bevat zowel data als instructies. Alle loads en stores van data, ongeacht het datatype, gebeuren tussen het lokaal geheugen en een enkel registerbestand. Het registerbestand bevat 128 registers van 128 bits breed. De reden voor dit grote aantal is dat compileroptimalisaties zoals lusontvouwing veel efficiënter kunnen worden uitgevoerd indien het aantal beschikbare registers groot is. Verder laat de breedte van 128 bits SIMD-instructies toe die op een grote dataset tegelijk kunnen worden uitgevoerd (vb. 4 integers van 32 bit of 2 floating-point getallen van dubbele precisie). In plaats van rechtstreekse toegang tot het hoofdgeheugen, kan elke SPU maar werken op data die zich in zijn eigen beperkt lokaal geheugen bevindt. Data wordt van en naar het hoofdgeheugen geschreven door DMA-instructies, die door de DMA-controller in de MFC worden afgehandeld. Er zijn communicatiekanalen aanwezig vanuit de SPU naar de MFC, de PPE en andere apparaten zoals de andere SPE’s. De belangrijkste componenten van de SPU zijn weergegeven in Figuur 2.6. Deze bevatten de Synergistic Execution Unit (SXU), het lokaal geheugen (LS), en het SPU registerbestand (SRF). De SXU bevat zes uitvoeringseenheden, die hierna in meer detail zullen 16 Figuur 2.6: Gedetailleerd overzicht van de opbouw van de SPU worden besproken. Uitvoeringseenheden De SPU heeft twee functioneel verschillende pijplijnen, die de even en oneven pijplijn worden genoemd (zie fig. 2.6). Een overzicht van de verdeling van de instructies over de verschillende pijplijnen wordt gegeven in [22]. De SPU is in staat om per klokcyclus een instructie te starten voor de even én de oneven pijplijn, en kan tegelijk voor elke pijplijn de resultaten van een uitgevoerde instructie wegschrijven. Alle instructies worden in programmavolgorde uitgevoerd. De verschillende uitvoeringseenheden zijn: • SPU Odd Fixed-Point Unit (SFS): Deze eenheid voert shift-,roteer- en maskoperaties toe op bits, bytes, halfwoorden en woorden6 , en shuffleoperaties op bytes. • SPU Even Fixed-Point Unit (SFX): Voert aritmetische en logische instructies uit, verschuivingen en rotaties op SIMD woorden, vergelijkingen van floating-point getallen, en benadering van de inverse en inverse wortel van floating-point getallen. • SPU Floating-Point Unit (SFP): De SFP voert instructies op enkele en dubbele precisie floating-point getallen uit, net als integer vermenigvuldigingen en conversies. 6 In deze scriptie wordt een woord steeds als 32 bits lang beschouwd. 17 De SPU ondersteunt echter maar vermenigvuldigingen van 16-bit, zodat 32-bit vermenigvuldigingen in software worden geı̈mplementeerd door 16-bit operaties: drie 16-bit vermenigvuldigingen en twee optellingen om de tussenproducten samen te tellen. • SPU Load And Store Unit (SLS): De SLS voert de load en store instructies tussen het lokaal geheugen en het registerbestand uit. Het handelt ook DMA-aanvragen naar het lokaal geheugen af. • SPU Control Unit (SCN): Deze uitvoeringseenheid is verantwoordelijk voor het ophalen van instructies en het toewijzen ervan aan de twee pijplijnen. Spronginstructies worden hier uitgevoerd, met de belangrijke opmerking dat er geen sprongvoorspeller aanwezig is in de SPU. Het is wel mogelijk om branch hints aan de SPU te leveren. Indien de sprong niet correct kan worden voorspeld door de branch hints of er geen branch hints aanwezig zijn, bedraagt de branch penalty 18 tot 19 cyvli • SPU Channel and DMA Unit (SSC): Verzorgt de communicatie, datatransfer en controle van en naar de SPU. De SSC is nauw verbonden in werking met de DMAcontroller, die is beschreven in 2.4.2. Lokaal geheugen Het lokaal geheugen is 256 KiB groot en bevat zowel de data als de instructies voor de SPU. Er is foutbescherming in de vorm van ECC aanwezig. Gegevens worden nergens gecached, zodat de programmeur volledige controle heeft over wat er zich op elk moment in het lokaal geheugen bevindt. De latentie van het geheugen is steeds 6 cycli. Omdat het lokaal geheugen wordt gedeeld voor loads en stores, DMA lees- en schrijfoperaties en het ophalen van instructies, worden volgende prioriteiten toegekend voor toegang tot het lokaal geheugen: 1. DMA lees- en schrijfopdrachten krijgen voorrang. DMA-instructies kunnen maar om de acht cycli toegang proberen te krijgen tot het lokaal geheugen (ondertussen worden ze gebufferd), zodat de impact op gewone loads en stores beperkt blijft. 2. SPU loads en stores. 3. Instructies worden geprefetched door tenminste 17 instructies sequentieel op te halen vanaf het sprongdoel. 18 Floating-point ondersteuning De SPU ondersteunt bewerkingen op zowel enkele als dubbele precisie floating-point getallen. Bewerkingen op enkele-precisie floating-point getallen kunnen 4-wegs geparallelliseerd worden door SIMD-instructies, en kunnen volledig gepijplijnd uitgevoerd worden. Operaties op floating-point getallen met dubbele precisie zijn slechts gedeeltelijk parallelliseerbaar (gedurende de laatste 7 van in totaal 13 klokcycli), en laten geen instructies in de andere pijplijn toe. Het dataformaat ondersteund door de SPU is de IEEE 754 standaard, met de volgende uitzonderingen voor enkele precisie: • Er kan enkel naar beneden worden afgerond. • Operandi die niet genormaliseerd zijn worden als nul beschouwd, en resultaten die niet genormaliseerd zijn worden op nul geforceerd • Getallen met een exponent die enkel 1’en bevat worden beschouwd als genormaliseerde getallen en niet als oneindig of not-a-number (NaN). 2.4.2 Memory Flow Controller Op elke SPU is een aparte Memory Flow Controller aanwezig (MFC) die de verbinding vormt tussen de SPU en de EIB, het hoofdgeheugen en andere processorelementen. De hoofdtaak bestaat erin om data tussen het lokaal geheugen en het hoofdgeheugen uit te wisselen. Voor communicatiedoeleinden zijn er aparte structuren zoals mailboxen. Figuur 2.7 geeft een gedetailleerd overzicht van de MFC. De verschillende verbindingen zijn: 1. LS Read en Write Interface: Dit verbindt het lokaal geheugen met de EIB, en wordt gebruikt voor alle DMA transfers. 2. LS DMA List Element Read Interface : De DMA aanvragen worden in een wachtlijst geplaatst om ze op een efficiënte manier te ordenen. Een speciaal geval van een DMA instructie is de DMA-lijst, waarbij een transfer wordt gestart naar een aangesloten blok geheugen in het lokaal geheugen, maar waarvan de data niet in een continu stuk van het hoofdgeheugen hoeft te liggen. De maximale grootte van een DMA-lijst is 16 KiB, met een maximum van 2048 elementen. De LS DMA List Element Read Interface vormt de verbinding tussen het lokaal geheugen en de wachtlijst met DMA commando’s om DMA-lijsten doeltreffend te kunnen implementeren. 19 Figuur 2.7: Overzicht van de opbouw van de MFC 3. Channel Interface : Over dit kanaal worden DMA commando’s tussen de SPU en de MFC uitgewisseld. 4. EIB Command and Data Interfaces : Dit is de verbinding tussen de MFC en de EIB. De MFC kan tot 16 uitstaande DMA-aanvragen tegelijk hebben. 5. SPU Load and Store : Hoewel deze verbinding geen deel uitmaakt van de MFC, wordt ze hier voor de volledigheid vermeld. Via deze interface worden load- en store-instructies tussen de SPU en het lokaal geheugen afgewerkt. Mailboxen Elke SPE heeft twee mailboxen, één om boodschappen naar de PPE te sturen en één om boodschappen van de PPE te ontvangen. Deze mailboxen worden voornamelijk gebruikt voor de controle van de applicatie vanuit de PPE. Door de SPE in de mailboxen signaalboodschappen te laten schrijven, kan de PPE de werklast over de SPE’s verdelen en beheren. De uitgaande mailbox van de SPE kan ook gebruikt worden om aan te geven wanneer de SPE zijn taak heeft uitgevoerd. Direct Memory Acces Controller De DMAC implementeert de DMA transfers van data en instructies tussen het lokaal geheugen en het hoofdgeheugen. De DMAC voert autonoom alle instructies uit, waardoor 20 de SPU in parallel instructies kan uitvoeren terwijl een DMA-transfer wordt afgehandeld. Het verloop van een DMA-transfer wordt geı̈llustreerd in Figuur 2.8 en ziet er als volgt uit: 1. De SPU gebruikt de channel interface om het DMA commando in de wachtlijn van de MFC te plaatsen. 2. De DMAC selecteert uit de lijst een commando om uit te voeren. Vereenvoudigd zijn de prioriteitsregels dat er afgewisseld wordt tussen data opvragen of plaatsen, en dat het commando klaar moet zijn om uit te voeren (de adresresolutie is compleet en er zijn geen afhankelijkheden met andere instructies). 3. Indien het commando een DMA-lijst bevat moet de lijst van elementen worden opgevraagd. De DMAC plaatst hiervoor een aanvraag in een wachtlijn naar het lokaal geheugen. Ondertussen wordt het DMA-commando terug in de wachtlijst van de MFC geplaatst. Op het moment dat de lijst van elementen door het lokaal geheugen wordt teruggeven moet het oorspronkelijk DMA commando terug worden geselecteerd om verder te gaan met uitvoering. 4. Indien er adresvertaling nodig is, wordt deze vertaling aan de Memory Management Unit (MMU) aangevraagd. Ook hier wordt het DMA-commando terug in de lijst geplaatst. Na de adresvertaling moet het commando terug uit de lijst worden geselecteerd. 5. Vervolgens wordt een busaanvraag aangemaakt voor het volgende blok data van het commando. Een aanvraag is maximaal 128 bytes, maar kan ook minder zijn. De busaanvraag wordt aan de bus interface unit (BIU) aangeboden. 6. De BIU selecteert de aanvraag uit zijn lijst en plaatst het commando op de EIB. Het commando wordt door de EIB geordend, rekening houdend met de commando’s van de andere elementen. Indien de transfer het hoofdgeheugen nodig heeft, zal de memory interface controller (MIC) het commando bevestigen aan de EIB, die op zijn beurt aan de BIU aangeeft dat het commando werd aanvaard. Hierna kan de datatransfer beginnen. 7. De BIU in de MFC voert de nodige leesoperaties op het lokaal geheugen uit. De EIB transfereert de data tussen de BIU en de MIC, die de data van of naar het hoofdgeheugen verplaatst. 8. Vervolgens wordt er voor elk nieuw blok data een nieuwe busaanvraag uitgestuurd. Het DMA commando blijft in de lijst met uitvoerende commando’s van de MFC tot 21 Figuur 2.8: Schematische voorstelling verloop DMA transfer alle busaanvragen zijn verwerkt. Ondertussen kunnen wel andere DMA commando’s worden verwerkt. Hoewel dit proces omslachtig lijkt, valt de latentie voor de SPU heel goed mee. Kistler et al. [26] meten in hun werk het aantal klokcycli in elke stap. De resultaten zijn te zien in Tabel 2.2. Hoewel de totale bijkomende latentie 290 klokcycli bedraagt, is dit Tabel 2.2: Aandeel latentie DMA-fases bij kloksnelheid van 3.2 GHz. Fase Latentie(cycli) DMA issue 10 DMA naar EIB 30 Lijstelementen ophalen 10 Coherentieprotocol 100 Datatransfer naar LS 140 Totaal 290 22 Tabel 2.3: Overzicht van de belangrijkste SPU instructies. Omschrijving Aantal instructies Latentie(cycli) Pijplijn Uitvoeringseenheid Load en store 12 6 oneven SLS Branch hints 4 15 oneven SLS Branch resolutie7 36 4 oneven SCN Shuffle 35 4 oneven SFS Enkele precisie FP 6 6 even SFP Dubbele precisie FP 10 13 even SFP Byte operaties 3 4 even SFP Rotatie en shift 17 4 even SFX niet de latentie die door de SPU wordt geobserveerd. De SPU kan immers doorgaan met uitvoering nadat een DMA commando in de wachtlijst van de MFC is geplaatst. Dit vergt een totaal van slechts 10 klokcycli, waaruit blijkt dat de geheugenlatentie in de Cell uitermate laag is. Dit werkt uiteraard maar in het geval er relevante instructies zijn die kunnen worden uitgevoerd op de SPU terwijl het DMA commando wordt verwerkt. 2.4.3 SPU instructieset Tabel 2.3 geeft een overzicht van de belangrijkste instructies in de instructieset van de SPU. Voor een gedetailleerde uitleg wordt verwezen naar Appendix B in [19]. 2.5 Programmeeromgeving Bij het ontwikkelen van code voor de Cell werd steeds gebruik gemaakt van de door IBM aangeboden Cell SDK versie 2.0. De code voor de PPU werd gecompileerd met behulp van gcc 4.1.1 met een aangepaste back-end voor de PPU en de SPU’s. Programma’s werden getest en hun uitvoeringstijd gemeten op een DD3.1 dual Cell blade met 1 GiB XDR RAM ter beschikking gesteld door het Barcelona Supercomputing Center. 7 Sprongen die correct werden voorspeld door branch hints hebben geen delay. Verkeerd voorspelde branches kosten 18 tot 19 cycli 23 Hoofdstuk 3 Matrixvermenigvuldiging 3.1 3.1.1 Inleiding Keuze algoritme Matrixvermenigvuldiging is een welbekend en vaak bestudeerd probleem. De elementen van het product van twee matrices A en B worden als volgt berekend. (AB)ij = n X Air Brj (3.1) r=1 Er is reeds veel onderzoek gebeurd, zowel naar algemene methodes om matrixvermenigvuldiging te versnellen, als naar specifieke methodes op gedistribueerde en parallelle computersystemen. In dit hoofdstuk is het dan ook niet de bedoeling om deze algoritmes te bestuderen of zelf een nieuw te ontwikkelen, maar eerder om vanuit de naı̈eve implementatie te vertrekken en incrementele verbeteringen toe te passen, gebruik makend van de kennis van de cellarchitectuur. De basis van waaruit wordt vertrokken, is Programma 1 op pagina 25. De basis van dit algoritme zal steeds behouden blijven, maar optimalisaties zullen de prestatie verbeteren door specifieke aanpassingen die de onderliggende architectuur uitbuiten. Het doel van dit hoofdstuk is dan ook om de verschillende noodzakelijke tussenstappen en pijnpunten aan te duiden die nodig zijn om een bestaand algoritme zo efficiënt mogelijk te implementeren op de Cell . 3.1.2 Gelijkaardig werk Er is reeds veel onderzoek verricht over matrixvermenigvuldiging, zowel op puur wiskundig gebied als op het gebied van de computerwetenschappen. Hoewel er in dit hoofdstuk 24 Programma 1 Basisalgoritme voor matrixvermenigvuldiging. void matrix mul ( int A[N ] [ N] , int B [N ] [ N] , int C [N ] [ N ] ) { f o r ( i = 0 ; i < N; i ++) { f o r ( j = 0 ; j < N; j ++) { sum = 0 ; f o r ( k = 0 ; k < N; k++) { sum += A[ i ] [ k ] ∗ B [ k ] [ j ] ; } C [ i ] [ j ] = sum ; } } } niet op deze verwezenlijkingen wordt verder gebouwd, is het toch interessant om enkele resultaten te bespreken. De naı̈eve implementatie van het algoritme voor matrixvermenigvuldiging is O(n3 ) met n de dimensie van de matrix. Het onderzoek op wiskundig gebied streeft ernaar om algoritmes te ontwikkelen die een betere complexiteit hebben. De twee belangrijkste zijn de algoritmes van Strassen [33] en CoppersmithWinograd [10], met een complexiteit van O(n2.807 ) en O(n2.376 ) respectievelijk. Hoewel deze algoritmes dus asymptotisch sneller zijn, zijn ze vaak moeilijker te implementeren of bieden ze pas significante verbeteringen voor matrices met een grote dimensie. Omdat matrixvermenigvuldiging in veel wetenschappelijke en andere software voorkomt, is er reeds gedurende vele jaren intensief onderzoek gedaan naar matrixvermenigvuldiging op gedistribueerde systemen. Enkele veelgebruikte algoritmes zijn Parallel Universal Matrix Multiplication Algorithms on Distributed Memory Concurrent Computers (PUMMA)[9] en de implementatie van matrixvermenigvuldiging in Parallel Basic Linear Algebra Subroutines (PBLAS)[8]. Aangezien er reeds vele decennia onderzoek is gebeurd naar matrixvermenigvuldiging, is het niet de bedoeling om hier een incrementele bijdrage te leveren. De bedoeling is eerder om stapsgewijs het efficiënt implementeren van een algoritme op de Cell te illustreren. Er bestaat reeds een volledig geoptimaliseerde implementatie van matrixvermenigvuldiging voor de Cell, die de theoretische piekprestatie van 200 GFLOPS zeer dicht benadert [14]. 25 3.2 Programmeermodel Omdat programmeren voor de Cell heel wat complexer is in vergelijking met traditionele architecturen, is het aangewezen om stapsgewijs te werk te gaan. In elke stap wordt het programma incrementeel geoptimaliseerd, waarbij steeds meer mogelijkheden van de Cell worden gebruikt. Het vooropgestelde schema dat in dit hoofdstuk wordt gehanteerd, is als volgt: 1. Schrijf scalaire code voor de PPU. Omdat de PPU architecturaal veel overeenkomsten vertoont met normale microprocessors, is dit een vrij eenvoudige en pijnloze stap. Programma’s die werken op een PowerPC processor, zullen zonder aanpassingen werken op de PPU indien de gebruikte bibliotheken beschikbaar zijn op de Cell. 2. Vorm de scalaire code om naar code voor de SPU. In de eerste stap worden alle berekeningen nog gedaan op de PPU, terwijl in een optimaal programma de SPU’s het meeste werk voor hun rekening nemen. Daarom bestaat de volgende stap erin om de originele code van de PPU te laten draaien op de SPU. In deze stap moet vooral aandacht worden besteed aan de geheugenstructuur. Eerst en vooral moet alle nodige data van het hoofdgeheugen naar het lokaal geheugen worden getransfereerd via een DMA-transfer. De uiteindelijke resultaten moeten terug naar het hoofdgeheugen zijn geschreven wanneer de SPU klaar is met uitvoeren. Hierbij komt onmiddellijk het beperkt lokaal geheugen naar voor. Traditionele algoritmen gaan vaak uit van een groot hoofdgeheugen, en gebruiken dan ook grote datastructuren. De SPU heeft echter maar 256 KiB voor zowel code als data, zodat een voorzichtiger gebruik noodzakelijk is. Dit kan door niet het volledige programma in een enkele SPU-draad uit te voeren, maar door een fijnere granulariteit te kiezen, vb. een nieuwe draad aanmaken per nieuwe functieoproep. Een middenweg is vaak de oplossing, waarbij een aantal functies worden gegroepeerd tot een enkele SPUdraad om het creëeren van nieuwe draden te beperken. Om het geheugengebruik te beperken wordt dan enkel die data in het lokaal geheugen bewaard die op het moment van de berekeningen noodzakelijk is. 3. Partitioneer over verschillende SPUs . De vorige stap levert code op die functioneel correct is, maar niet optimaal gebruik maakt van alle acht de SPU’s. In deze stap wordt het oorspronkelijk algoritme geparallelliseerd over de SPU’s. Naast de normale problemen zoals synchronisatie en communicatie, speelt de granulariteit van de opsplitsing en het aantal dynamisch aangemaakte draden een belangrijke rol. 26 Indien teveel code per draad wordt uitgevoerd, is de kans groot dat het geheugen te klein is. Langs de andere kant is het aanmaken van een groot aantal draden vaak niet optimaal vanwege de tijd die nodig is om nieuwe draden aan te maken. Niet alleen het detecteren van parallellisme in het programma, maar het optimaal opsplitsen is dan ook van groot belang in deze stap. 4. Implementeer specifieke optimalisaties. De voorgaande stappen laten vaak een significante versnelling zien ten opzichte van de oorspronkelijke implementatie. Om echter alle mogelijkheden van de Cell te benutten, zijn een aantal specifieke optimalisaties noodzakelijk. Hoewel deze quasi eindeloos zijn, worden hier slechts enkele opgesomd: • Balanceren berekeningen en geheugengebruik. De SPE’s hebben een MFC en een SPU die in parallel DMA-transfers kan verwerken en kan rekenen op data die reeds aanwezig is. Om de latentie van het geheugen te verbergen, moeten datatransfers dan ook overlapt worden met nuttige berekeningen. DMA-transfers worden dan ook best zo vroeg mogelijk begonnen. • Vermijden ingewikkeld controleverloop. Omdat de SPU geen sprongvoorspeller heeft, is de impact van een complex controleverloop vaak groot. Indien mogelijk moet dit dus vermeden worden, eventueel door gebruik te maken van branch hints of het verwijderen van sprongen daar gebruik te maken van vectorinstructies. • Lusontvouwing en andere lusoptimalisaties. Vanwege het groot aantal registers, zijn optimalisaties zoals lusontvouwing en software pipelining vaak heel effectief. • Vectorinstructies. De belangrijkste versnelling in de vierde stap komt echter door gebruik te maken van vectorinstructies. Hoewel niet alle algoritmes zich hiertoe lenen, is de potentiële versnelling aanzienlijk. Zo kunnen operaties op enkele-precisie floating-point getallen met een factor vier worden versneld, indien de code zich hiertoe leent. • Gebruik dubbele pijplijn. De SPU heeft twee gediversifieerde pijplijnen, die het aantal instructies per klokcyclus flink kunnen opdrijven indien hier rekening mee wordt gehouden. Dit kan samenwerken met andere technieken zoals lusontvouwing, waarbij de oneven pijplijn de resultaten van de vorige opslaat terwijl de even pijplijn de resultaten van de volgende iteratie berekent. Wanneer dit programmeermodel wordt gevolgd, wordt de bijkomende complexiteit in elke stap beperkt, terwijl het uiteindelijke programma toch zo optimaal mogelijk wordt ge- 27 houden. Een opmerking die hier moet worden gemaakt is dat er nog weinig ondersteuning is voor profilering van een volledig programma op de Cell. In de eerste stappen kan echter nog worden afgegaan op kennis opgedaan op een ander platform met betere ondersteuning. 3.3 3.3.1 Matrixvermenigvuldiging op de PPU Aanpassingen Aangezien de PPU gebaseerd is op de PowerPC architectuur, gebeurt de initiële implementatie zonder aanpassingen ten opzichte van de implementatie voor traditionele microprocessors. Tegenover deze grote eenvoud staat wel een slechte prestatie, aangezien slechts een heel beperkt deel van de mogelijkheden van de Cell worden gebruikt. Om toch een implementatie te bekomen die relevanter is als referentie, wordt gebruik gemaakt van lustegeling[37, 35]. Lustegeling is een techniek die het datagebruik aanpast zodat optimaal gebruik wordt gemaakt van caches. Bij de naı̈eve implementatie wordt elk element uit matrix B N keer gelezen. De afstand tussen dit hergebruik is N 2 , waardoor een cachehit1 enkel voorkomt indien de cache groot genoeg is om N 2 waarden van de matrix op te slaan. Lustegeling zorgt voor een kleinere hergebruiksafstand van de data, zodat er meer cachetreffers optreden. De code van ons algoritme ziet er dan als in Programma 2. Indien de waarde van block voldoende klein wordt gekozen, kunnen alle leestoegangen tot B (behalve de eerste van elk element) gebeuren naar de caches. Aangezien deze techniek veronderstelt dat data wordt opgeslagen in caches, is ze enkel voordelig op de PPU, en niet op de SPU. 3.3.2 Resultaten De uitvoeringstijd met en zonder lustegeling voor verschillende dimensies is weergegeven in Figuur 3.1. Voor voldoende kleine matrices is de prestatiewinst van lustegeling een factor 4. Wanneer de matrices echter te groot worden en er meer cachemissers als gevolg optreden, heeft lustegeling een versnelling van ongeveer 35. Het overgangspunt ligt bij een dimensie van 512x512. Hoewel deze techniek niet bruikbaar is op de SPU vanwege het ontbreken van caches, wordt voor verdere vergelijkingen tussen de PPU en de SPU steeds lustegeling verondersteld waar mogelijk. Hoewel dit op het eerste zicht de PPU bevoordeelt, is dit noodzakelijk om een oneerlijke vergelijking te voorkomen. Bij de SPU kunnen we immers zelf ten allen 1 Er wordt hierbij een Last Recently Used -vervangingschema verondersteld. 28 Programma 2 Matrixvermenigvuldiging met lustegeling. void matrix mul ( int A[N ] [ N] , int B [N ] [ N] , int C [N ] [ N ] ) { f o r ( kk =0;kk<N; kk+=b l o c k ) { f o r ( j j =0; j j <N; j j+=b l o c k ) { f o r ( i =0; i <N; i ++){ f o r ( k=kk ; k<min ( kk+block ,N ) ; k++){ f o r ( j= j j ; j <min ( j j +block ,N ) ; j ++){ C[ i ] [ j ]+=A[ i ] [ k ] ∗B [ k ] [ j ] ; } } } } } } tijde bepalen wat er zich in het lokaal geheugen bevindt, zodat grote latenties voor leesof schrijfoperaties naar het geheugen niet zullen voorkomen. De schaling van de uitvoeringstijd voldoet aan de verwachtingen, namelijk kubisch in functie van de dimensie. Dit stemt overeen met de bevinding dat het basisalgoritme van orde O(N 3 ) is. Voor de gedetailleerde resultaten van de metingen wordt verwezen naar bijlage A. 3.4 3.4.1 Matrixvermenigvuldiging op een enkele SPU Aanpassingen Het grootste probleem bij het aanpassen van een algoritme voor uitvoering op de SPU is de keuze van de opdeling. In het algemeen is een volledig programma te groot om rechtstreeks op een enkele SPU uit te voeren, en dus moet het algoritme worden opgesplitst in kleinere afzonderlijke delen. Een goede keuze is om de hete code2 in een aparte SPU-draad uit te voeren. De keuze bij matrixvermenigvuldiging is echter triviaal, omdat het algoritme zeer beperkt is qua codegrootte, en de hete code zich uiteraard in de binnenste lus bevindt. Bij 2 Hete code is code die het grootste deel van de uitvoeringstijd in beslag neemt. 29 Figuur 3.1: Uitvoeringstijd matrixvermenigvuldiging op de PPU met en zonder lustegeling. meer complexe algoritmes is deze keuze echter minder eenvoudig, zoals in hoofdstuk 4 duidelijk zal worden. Hoewel de keuze van de uit te voeren code eenvoudig was, is het geheugengebruik minder eenvoudig op te lossen. Als we algoritme 1 rechtstreeks willen uitvoeren, moeten we ervoor zorgen dat er voldoende geheugen is gealloceerd voor matrix A, B én C. Voordat het algoritme kan worden uitgevoerd moeten de matrices A en B via DMA-transfers van het hoofdgeheugen naar het lokaal geheugen worden gekopieerd. Na uitvoering moet het resultaat van matrix C via DMA-transfers terug naar het hoofdgeheugen worden geschreven. Het probleem hierbij is de beperkte grootte van het lokaal geheugen. In de veronderstelling dat de matrices elementen van 32 bit bevatten, zoals integers of enkeleprecisie floating-point getallen, is het algoritme zonder aanpassingen slechts werkbaar voor matrices tot grootte 147x147. Dit is zelfs een overschatting, omdat niet de volledige 256 KiB beschikbaar is voor data. Uiteraard moet dit gebrek aan schaalbaarheid worden opgelost om een degelijk algoritme te bekomen. De oplossing voor het geheugengebruik is om enkel die data in het lokaal geheugen op te slaan die strikt noodzakelijk is. Om het geheugengebruik op een zo eenvoudig mogelijke manier te beperken, is er voor gekozen om steeds één element van de matrix C te berekenen. Dit houdt in dat er slechts een enkele rij van matrix A en een enkele kolom van matrix B nodig is. Hierdoor stijgt het geheugengebruik slechts lineair in plaats van kwadratisch met de dimensie van de matrices. De maximale grootte is nu 32768x32768. Om te voorkomen dat voor elk element telkens opnieuw zowel een rij als kolom moet worden getransfereerd, worden de nieuwe elementen van matrix C rij per rij berekend, waardoor de huidige rij 30 van matrix A kan worden hergebruikt. In totaal wordt voor een grootte van NxN matrix A dan 1 keer getransfereerd naar het lokaal geheugen, en matrix B N keer. De herhaling van DMA-transfers voor dezelfde data is de prestatie die moet worden ingeboet om het algoritme schaalbaar te maken. Uiteindelijk ziet het algoritme op de SPU in pseudocode er als volgt uit. Programma 3 Matrixvermenigvuldiging op de SPU. void matrix mul ( int A[N ] [ N] , int B [N ] [ N] , int C[N ] [ N ] ) { A array=m a l l o c (N∗ s i z e o f ( int ) ) ; B column=m a l l o c (N∗ s i z e o f ( int ) ) ; f o r ( i = 0 ; i < N; i ++) { DMA in ( A array ,A[ i ] [ ] ) ; f o r ( j = 0 ; j < N; j ++) { DMA in ( B column , B [ ] [ j ] ) ; f o r ( k = 0 ; k < N; k++) { C [ i ] [ j ] += A array [ k ] ∗ B column [ k ] ; } } } } 3.4.2 DMA-transfers Om de DMA-transfers tussen de SPU’s en het hoofdgeheugen correct te laten verlopen, moet er aan een aantal randvoorwaarden worden voldaan. In de eerste plaats moet het startadres van de data die wordt geDMA’ed bekend zijn bij uitvoering op de SPU. Concreet in dit voorbeeld komt dit er op neer dat de SPU het adres van matrices A,B en C nodig heeft tijdens de uitvoer. Om ervoor te zorgen dat een correct aantal bytes wordt getransfereerd, is ook de grootte van de matrices nodig. Al deze argumenten worden gegroepeerd in een contextstructuur. Bij het aanmaken van de SPU-draad wordt een verwijzing hiernaar meegegeven. Vóór het eigenlijke algoritme voor de matrixvermenigvuldiging wordt uitgevoerd, zal de SPU deze contextstructuur kopiëren naar zijn lokaal geheugen. Daarna zijn alle nodige variabelen bekend om de rest van de DMA-transfers correct te laten verlopen. 31 Omdat DMA-instructies expliciet door de programmeur moeten worden geschreven, moet de programmeur er ook zelf voor zorgen dat aan enkele bijkomende randvoorwaarden wordt voldaan. De belangrijkste zijn de alignatie van geheugenadressen en de grootte van getransfereerde data. De grootte van een transfer moet steeds 1, 2, 4, 8 of een veelvoud van 16 bytes lang zijn. De maximale grootte is 16 KiB. Het eerste probleem is op te lossen door de lengte van een DMA-transfer naar boven af te ronden tot een correct getal en indien nodig padding bytes toe te voegen. Transfer van data van meer dan 16 KiB kan eenvoudig worden opgesplitst in een aantal kleinere transfers. Dit probleem is dan ook eenvoudig te omzeilen. Een moeilijker probleem om op te lossen is de alignatie van geheugenadressen. Er wordt geëist dat een adres in het hoofdgeheugen, zowel voor bron-als doeladressen, steeds is afgerond op een veelvoud van 128 bytes. Wanneer geheugen wordt gereserveerd met de malloc-functie, is dit niet gegarandeerd het geval. Daarom moeten maatregelen worden getroffen om dynamische geheugenallocatie op een correcte manier te laten verlopen die DMA-transfers mogelijk maakt. Een eerste manier bestaat erin om het adres naar het gealloceerd geheugen dat wordt teruggeven, af te ronden naar een veelvoud van 128. De maximale verschuiving ten opzichte van het originele adres is dan 127, zodat er 127 bytes extra moeten worden gealloceerd om te voorkomen dat data wordt gelezen of geschreven in een adres dat niet toebehoort aan de gealloceerde structuur. Een efficı̈ente manier om dit te verwezenlijken is als volgt. Programma 4 Alignatie geheugenadres. r e t = ( int ∗ ) m a l l o c (127+ s i z e ∗ s i z e o f ( int ) ) ; r e t= (127+( unsigned ) r e t )& 0 x 7 f ; Hoewel deze manier voor een correct adres zal zorgen, is ze niet vrij van problemen. Wanneer namelijk het geheugen terug wordt vrijgegeven met behulp van de free-functie, zal dit voor een foutmelding zorgen. Indien immers de data vanaf het nieuwe adres wordt vrijgegeven, blijft het geheugen tussen het oude en het nieuwe geheugen gealloceerd. Dit geheugen kan nooit meer worden vrijgegeven, omdat het originele startadres is overschreven. Een eenvoudige manier om dit te omzeilen is het originele adres bij te houden, en het afgeronde adres in een andere variabele bij te houden. Er wordt dan verder gewerkt met het nieuwe adres, behalve wanneer het geheugen terug moet worden vrijgegeven. Dit wordt echter snel omslachtig wanneer er veel datastructuren zijn waarvoor dynamisch geheugen wordt gealloceerd. De kans op fouten is ook reëel, omdat het oude adres enkel kan en moet gebruikt worden bij het vrijgeven van data. 32 Een betere manier, en diegene die in de rest van alle geschreven code is toegepast, is om eigen functies te schrijven voor geheugenallocatie en -vrijgave. Het idee is om terug het oude adres verkregen met malloc af te ronden, maar het oude adres bij te houden in het gealloceerde deel geheugen zelf. Bij oproepen van de aangepaste free-functie wordt dit oud adres uitgelezen, waarna het geheugen startend op dit adres opnieuw kan worden vrijgegeven. De aangepaste functies zijn hierna weergegeven. Programma 5 Aangepaste functies voor gealigneerde geheugenallocatie void ∗ m a l l o c a l i g n ( int s i z e , unsigned long a l i g n ) { void ∗ r e t ; char ∗ r e a l ; unsigned long o f f s e t ; r e a l = ( char ∗ ) m a l l o c ( s i z e + s i z e o f ( void ∗ ) + ( a l i g n − 1 ) ) ; o f f s e t = ( a l i g n − ( unsigned long ) ( r e a l + s i z e o f ( void ∗ ) ) ) ; o f f s e t = o f f s e t & ( a l i g n −1); r e t = ( void ∗ ) ( r e a l + s i z e o f ( void ∗ ) ) + o f f s e t ; ∗ ( ( void ∗ ∗ ) ( r e t ) −1) = ( void ∗ ) ( r e a l ) ; return ( r e t ) ; } void f r e e a l i g n ( void ∗ p t r ) { void ∗ r e a l ; r e a l = ∗ ( ( void ∗ ∗ ) ( p t r ) −1); free ( real ); } 3.4.3 Resultaten In Figuur 3.2 wordt een vergelijking gegeven tussen de uitvoering van matrixvermenigvuldiging op de PPU en de SPU. Bij het algoritme op de PPU is er lustegeling gebruikt, 33 Figuur 3.2: Vergelijking uitvoering matrixvermenigvuldiging op de PPU en de SPU. de uitvoering op de SPU is de implementatie van algoritme 3. Er is een betere prestatie te zien voor de PPU dan voor de SPU. Dit prestatievoordeel neemt af bij toenemende dimensie. Er zijn hier een aantal verklaringen voor: • Het aanmaken van een SPU-draad vergt een zekere tijd. Uit uitgevoerde testen blijkt dit rond de 5ms te liggen. Bij korte uitvoeringstijden is deze vaste vertraging nadeliger dan bij lange uitvoeringstijden: bij dimensie 128 is dit aandeel ongeveer 13% , bij dimensie 1024 is dit nog slechts 0.04%. • Voordat de SPU zijn berekeningen kan uitvoeren, moet de data via DMA-transfers in het lokaal geheugen worden geplaatst. Dit vergt een vaste vertraging zoals weergegeven in Tabel 2.2 op pagina 22. Verder is er een vertraging evenredig met de grootte van de datastructuren. De vaste vertraging zal terug een groter effect hebben bij kleine dimensies, terwijl de veranderlijke component evenredig met de grootte belangrijker zal worden bij grote dimensies. De totale grootte van de matrices neemt echter kwadratisch toe met de dimensie, terwijl de berekeningen een kubisch verband hebben. In het aangepast algoritme op de SPU is het zelfs zo dat de DMA-transfers slechts een enkele rij of kolom transfereren, waardoor er slechts lineaire schaling is. Dit zorgt er voor dat voor toenemende dimensie het aandeel van de berekeningen belangrijker wordt voor de totale uitvoeringstijd. Hierbij is de SPU in het voordeel vanwege de geoptimaliseerde implementatie van de architectuur voor reken- en dataintensieve toepassingen. Tabel 3.1 geeft de opgemeten procentuele verhouding tussen de latentie van DMA-transfers en de berekeningen zelf. 34 Tabel 3.1: Opgemeten verhouding DMA-transfer en berekeningen bij matrixvermenigvuldiging op 1 SPU. 3.5 3.5.1 Dimensie DMA-transfer Berekeningen 128 45,66% 54,34% 256 35,54% 64,46% 512 29,28% 70,72% 1024 27,32% 72,68% Parallellisatie over meerdere SPU’s Keuze parallellisatieschema In de vorige stap werd de uitvoering van de PPU naar de SPU verplaatst. Het sequentiële karakter van het algoritme werd echter behouden, waardoor slechts één SPU kon worden gebruikt. Om optimale prestatie’s te bekomen moeten alle SPU’s worden betrokken bij de uitvoering op een manier die de overlast door parallellisatie beperkt en voor optimale schaalbaarheid zorgt. Matrixvermenigvuldiging is embarrassingly parallel [15], waardoor deze stap voor dit specifiek probleem heel eenvoudig wordt. We kiezen ervoor om data-partitionering [5] toe te passen, waarbij elke SPU inwerkt op een subset van de data. Concreet komt dit er op neer dat elke SPU slechts een beperkt deel van het resultaat berekent. De PPU berekent op voorhand welke rijen van het resultaat in matrix C moeten worden uitgerekend door welke SPU. Deze indices worden meegegeven in de contextstructuur voor de betreffende SPU. Bij voldoende grote matrices zorgt dit voor een ongeveer gelijke verdeling van de werklast, aangezien de bewerkingen voor de berekingen een deterministische uitvoeringstijd hebben maar de DMA-transfers niet. 3.5.2 Resultaten en schaalbaarheid Figuur 3.3 geeft een overzicht van de uitvoeringstijd voor een variërend aantal SPU-draden en voor verschillende dimensies. Bij een dimensie 512 en meer blijkt de uitvoeringstijd bijna perfect lineair mee te schalen met het aantal gebruikte SPU’s. De versnelling bij het gebruik van 8 SPU’s ten opzichte van 1 SPU is ongeveer 6,7. Dit is minder dan het 35 Figuur 3.3: Schaling matrixvermenigvuldiging in functie van aantal SPU’s en dimensie. theoretisch maximum van 8, maar deze bovengrens wordt niet bereikt omdat de DMAtransfers voor een enkele SPU nog steeds even lang duren. In realiteit zal het zelfs zo zijn dat DMA-transfers langer zullen duren omdat de toegang tot het hoofdgeheugen een flessenhals zal vormen indien meerdere SPU’s tegelijk hier toegang tot willen. De quasi lineaire schaling toont echter aan dat de keuze voor de parallellisatie goed meeschaalt, wat te verwachten was aangezien er geen communicatie nodig is tussen de SPU-draden onderling. Voor kleine dimensies, en dan vooral voor dimensie 128, zien we een negatief effect bij het toewijzen van meer SPU-draden aan het algoritme. Dit is te verklaren door de vertraging voor het aanmaken van meerdere SPU-draden, die de bekomen versnelling van de berekeningen teniet doen. 3.6 Optimalisaties De vorige aanpassingen zorgden voor een evenwichtige verdeling van de werklast over de SPU’s. Hoewel er al een grote versnelling merkbaar was, is het algoritme nog niet geoptimaliseerd voor uitvoering op de SPU. Hierna volgen enkele optimalisaties die de prestatie verbeteren door de onderliggende architectuur maximaal te benutten. Tenzij expliciet anders vermeld, wordt er steeds van alle 8 SPU’s gebruik gemaakt. 3.6.1 Dubbele buffering Matrixvermenigvuldiging is een dataintensief algoritme. Samen met de aanpassing voor een beperkter geheugengebruik zorgt dit voor een groot aantal DMA-transfers. In de 36 Figuur 3.4: Conceptuele werking dubbele buffering. laatste versie van het algoritme worden deze DMA-transfers nog inefficiënt verstuurd: wanneer data nodig is wordt een aanvraag verstuurd, en wordt er gewacht met uitvoering tot deze data beschikbaar is. Deze manier maakt geen gebruik van de parallelle werking van de SPU en de MFC. Een betere manier is om de aanvraag voor nieuwe data zo vroeg mogelijk te versturen, en ondertussen verder te werken met de data die reeds aanwezig is in het lokaal geheugen. Op die manier wordt de latentie geminimaliseerd. Een eenvoudige en doeltreffende manier om dit te realiseren is door gebruik te maken van dubbele buffering. Bij dubbele buffering wordt er voor de datastructuur die de latentie veroorzaakt in twee buffers voorzien. Tijdens elke iteratie is er één buffer waarop de berekingen worden uitgevoerd, en één buffer waarin nieuwe data wordt geschreven door DMA-transfers. Wanneer de berekeningen op de ene buffer zijn beëndigd en de volgende iteratie wordt gestart, wisselen de buffers qua functionaliteit. Indien er voldoende berekeningen worden uitgevoerd in parallel met de datatransfer, kan zo de latentie van de DMA-transfer bijna volledig worden verborgen. Het concept van dubbele buffering is grafisch voorgesteld in Figuur 3.4. Toegepast op het probleem van matrixvermenigvuldiging, is het aangewezen om de kolommen van de B-matrix te bufferen. Aangezien in de binnenste lus van het algoritme er een nieuwe DMA-transfer voor deze structuur wordt gestart, zal dit in het niet gebufferde geval voor een grote latentie zorgen. Omdat het vermenigvuldigen van een groot aantal elementen veel tijd in beslag neemt, kan dubbele buffering deze latentie verbergen. In pseudocode krijgen we het algoritme in Programma 6. 37 Programma 6 Matrixvermenigvuldiging met dubbele buffering void matrix mul ( int A[N ] [ N] , int B [N ] [ N] , int C [N ] [ N ] ) { A array=m a l l o c (N∗ s i z e o f ( int ) ) ; B column [ 0 ] = m a l l o c (N∗ s i z e o f ( int ) ) ; B column [ 1 ] = m a l l o c (N∗ s i z e o f ( int ) ) ; f o r ( i = 0 ; i < N; i ++) { DMA in ( A array ,A[ i ] [ ] ) ; DMA in ( B column [ 0 ] , B [ ] [ 0 ] ) ; f o r ( j = 0 ; j < N; j ++) { DMA in ( B column [ ( j +1)%2] ,B [ ] [ j + 1 ] ) ; f o r ( k = 0 ; k < N; k++) { C [ i ] [ j ] += A array [ k ] ∗ B column [ j % 2 ] [ k ] ; } } } } Om de impact van dubbele buffering op te meten, wordt bij de uitvoeringstijd van het algoritme een differentiatie gemaakt tussen de tijd die besteed wordt aan het wachten op het afronden van DMA-transfers, en het berekenen van een element uit de C-matrix. Het aandeel van DMA-instructies en berekeningen voor zowel het origineel als het algoritme met dubbele buffering is grafisch voorgesteld in Figuur 3.5. De verhouding werden steeds berekend ten opzichte van de totale uitvoeringstijd van het originele, niet-gebufferde algoritme. In het origineel algoritme is DMA-latentie verantwoordelijk voor ongeveer 40% van de totale uitvoeringstijd, terwijl dit door dubbele buffering is teruggebracht naar 3% voor dimensie 128, en minder dan 0,5% voor dimensie 1024. Dubbele buffering slaagt er dus in om de latentie van de DMA-transfer nagenoeg volledig te verbergen. Hoewel de DMA-latentie bijna verdwenen is, is de invloed van dubbele buffering op de berekeningen minder positief. Op Figuur 3.5 is eveneens de verhouding van de uitvoeringstijd voor berekeningen en DMA-transfers tussen het gebufferd en het origineel algoritme te zien. Hieruit blijkt duidelijk dat de latentie sterk is gereduceerd, maar dat de berekeningen ongeveer 20% meer in beslag nemen in verhouding met het originele algoritme. De reden hiervoor is de extra berekeningen die nodig zijn om na elke iteratie de buffers van 38 Figuur 3.5: Verhouding DMA-latentie en berekeningen tussen origineel en gebufferd algoritme. functie te doen veranderen. In algoritme 6 worden hiervoor modulo-operaties gebruikt, die zeer veel extra tijd zullen vragen. Deze implementatie is dan ook niet in de eigenlijke implementatie gebruikt. Er werd gekozen voor een enkele rijstructuur die beide buffers bevat. De buffer voor DMA-opslag is dan een verwijzing naar deze structuur, met een offset afhankelijk van de huidige iteratie. Analoog zullen de berekeningen dan inwerken op de andere helft van de rijstructuur. Op deze manier kan dubbele buffering worden bekomen door een simpele aanpassing van een enkele offset per iteratie. Omdat globaal gezien de versnelling van de DMA-transfers veel groter is dan de vertraging van de berekeningen, wordt de matrixvermenigvuldiging versneld met een factor 1,2 tot 1,5. Uit Figuur 3.6 blijkt dat de versnelling groter wordt naarmate de dimensie toeneemt. Dit is te verklaren door het feit dat de DMA-latentie bij het gebufferd algoritme slechts toeneemt met het aantal DMA-transfers, en niet met de grootte. Tijdens het transfereren van de data zijn er immers voldoende instructies om deze vertraging te verbergen, zodat enkel de vaste latentie voor het aanvragen van een DMA-transfer overblijft. Bij het origineel algoritme is de totale latentie van de DMA-transfers kubisch afhankelijk van de dimensie, net als de berekeningen. Dit is ook de verklaring voor de constante verhouding tussen DMA-latentie en berekeningen bij het basisalgoritme, terwijl bij dubbele buffering de DMA-latentie procentueel gezien steeds minder tijd in beslag neemt. Om maximale prestatie te bekomen, moet de vertraging van de berekeningen worden aangepakt. 39 Figuur 3.6: Uitvoeringstijd voor origineel, gebufferd en ontvouwd algoritme. 3.6.2 Lusontvouwing Dubbele buffering werkt de negatieve impact van DMA-transfers op de uitvoeringstijd nagenoeg volledig weg, maar heeft een negatieve invloed op de uitvoeringstijd van de berekeningen zelf. Om verdere prestatieverhogingen te bekomen, moet er dus gefocust worden op de binnenste lus van het algoritme. Een eerste eenvoudige techniek is gebruik maken van lusontvouwing. Lusontvouwing is een techniek die reeds lange tijd wordt toegepast, en een standaard optimalisatie is in de meeste compilers. Het idee is om in de binnenste lus meerdere iteraties van het origineel probleem uit te voeren. Indien er geen afhankelijkheden zijn tussen de verschillende iteraties, kan de compiler de instructies dan herordenen om zo een betere prestatie te bekomen. Deze techniek heeft echter als grootste nadeel dat er meer registers nodig zijn om alle data van de nieuwe lusiteraties te kunnen opslaan. Elke SPU heeft echter 128 registers, waardoor de beperkingen van lusontvouwing nagenoeg onbestaande zijn. Verder beschikt de SPU over een dubbele pijplijn, die nu beter benut kunnen worden omdat er meer instructies zijn om te verdelen over de pijplijnen. De aangepaste versie van het algoritme waarbij per iteratie 4 originele iteraties worden uitgevoerd is hierna weergegeven3 . Omdat lusontvouwing een versnelling van de binnenste lus tot gevolg heeft, verwachten we een aanzienlijke versnelling van de berekeningen. Figuur 3.7 geeft de uitvoeringstijd van de berekeningen voor het algoritme met dubbele buffering en lusontvouwing in verhouding tot het basisalgoritme. Hieruit blijkt duidelijk dat de berekeningen sterk versneld 3 Om de eenvoud te bewaren is de code voor dubbele buffering weggelaten. De resultaten veronderstellen echter dat dubbele buffering is geı̈mplementeerd. 40 Programma 7 Lusontvouwing voor matrixvermenigvuldiging. void matrix mul ( int A[N ] [ N] , int B [N ] [ N] , int C [N ] [ N ] ) { f o r ( i = 0 ; i < N; i ++) { f o r ( j = 0 ; j < N; j ++) { f o r ( k = 0 ; k < N/ 4 ; k++) { m1 = A array [ 4 ∗ k ] ∗ B column [ 4 ∗ k ] ; m2 = A array [ 4 ∗ k+1] ∗ B column [ 4 ∗ k + 1 ] ; m3 = A array [ 4 ∗ k+2] ∗ B column [ 4 ∗ k + 2 ] ; m4 = A array [ 4 ∗ k+3] ∗ B column [ 4 ∗ k + 3 ] ; C [ i ] [ j ] += m1+m2+m3+m4 ; } } } } zijn, namelijk tot slechts de helft van de tijd van het origineel. Op Figuur 3.6 is te zien dat de totale versnelling hier sterk door stijgt. Bij een dimensie van 128 is de uitvoeringstijd een factor 1,25 korter in vergelijking met het origineel, bij een dimensie van 1024 is dit reeds 3,89. De invloed van de dimensie is ook hier sterk aanwezig. De bovengrens op de versnelling is te berekenen door de wet van Amdahl. Uit de resultaten blijkt dat 40% van de originele uitvoeringstijd besteed wordt aan DMA-transfers. Deze fractie is in de limiet oneindig versneld door dubbele buffering. De overige 60% besteed aan berekeningen hebben een empirisch bepaalde maximale versnelling van ongeveer 2,35 door gebruik te maken van lusontvouwing. Uit de wet van Amdahl volgt dan de theoretische maximumversnelling: 1 0.6 + 2.35 = 3.91666 . . . Smax = 0.4 ∞ We benaderen deze maximumversnelling reeds zeer dicht voor een dimensie van 1024. 41 Figuur 3.7: Verhouding berekeningen gebufferd en ontvouwd algoritme op origineel. 42 Hoofdstuk 4 Clustal W 4.1 4.1.1 Algemene beschrijving Achtergrond Clustal W [34] is een programma voor de gelijktijdige alignatie van meerdere DNAsequenties. Het maakt deel uit van BioPerf [4], een benchmark suite die de belangrijkste applicaties uit de bioinformatica verzamelt. Bij het aligneren van sequenties wordt er gezocht naar gelijkaardige delen, wat kan wijzen op functioneel, structureel of evolutionair verwantschap. Op deze manier kunnen de eigenschappen van nieuwe DNA-sequenties snel achterhaald worden door vergelijking met reeds gekende sequenties. Wanneer twee sequenties een gelijke voorouder hebben, kunnen hun verschillen verklaard worden door ofwel puntmutaties ofwel het verwijderen van een aminozuur bij een van de sequenties. Bij het aligneren wordt er dan gezocht naar delen van de sequenties die overeenkomen met zo weinig mogelijk aanpassingen. Daarbij kan onderscheid gemaakt worden tussen globale alignatie en lokale alignatie. Globale alignatie zal de sequenties over hun volledige lengte zo goed mogelijk proberen af te stemmen op elkaar, terwijl lokale alignatie slechts afzonderlijke delen zal onderzoeken. Lokale alignatie is meestal interessanter, omdat zelfs als sequenties globaal gezien sterk van elkaar verschillen, de eventuele gelijkenissen toch zullen worden teruggevonden. Daar staat tegenover dat het vinden van gelijkaardige afzonderlijke delen een bijkomende moeilijkheid is. Voor het aligneren van twee sequenties wordt meestal gebruik gemaakt van dynamisch programmeren. Het Smith-Waterman algoritme [32] wordt hierbij het vaakst gebruikt. De belangrijkste elementen in het algoritme zijn het gebruik van een substitutiematrix en gap penalties. De substitutiematrix kent een score toe voor de overeenkomst of het verschil van twee aminozuren in de sequenties. Mutaties die weinig van elkaar verschillen 43 krijgen op die manier een grotere score toebedeeld. Wanneer er een gat in de sequenties wordt geı̈ntroduceerd, wordt de score negatief beı̈nvloed door een gap penalty. Er bestaan hier verschillende vormen van, gaande van een constante waarde tot een lineair afnemende waarde wanneer een bestaand gat wordt uitgebreid. Dynamisch programmeren garandeert een optimale oplossing gegeven een substitutiematrix en gap penalties. De moeilijkheid zit hem dan ook in het opstellen van deze scores, wat vaak empirisch gebeurd. Wanneer meerdere sequenties tegelijkertijd met elkaar moeten worden vergeleken, blijkt dynamisch programmeren al snel ontoereikend te zijn. De substitutiematrix moet immers van een twee-dimensionale structuur worden uitgebreid naar een n-dimensionale structuur, met n het aantal sequenties. Hierdoor neemt de zoekruimte kwadratisch toe met stijgende n. Er is zelfs aangetoond dat een globaal optimum vinden voor n sequenties een NP-compleet probleem is [24]. De zoekruimte wordt dan ook meestal beperkt door de sequenties eerst paarsgewijs met elkaar te vergelijken. Hieruit worden dan de twee sequenties die de meeste overeenkomsten met elkaar vertonen gekozen. Er wordt dan progressief verder gealigneerd door steeds de volgende sequentie die het best op de reeds gealigneerde lijkt, aan te passen aan de reeds bestaande alignatie [13]. Deze methode levert echter geen gegarandeerd optimale oplossing, en is dan ook een heuristiek. De grootste gevoeligheid zit in de initiële alignatie, die zeker bij sterk verschillende sequenties een grote fout kan introduceren. Er zijn dan ook veel uitbreidingen gebeurd op deze techniek om de foutgevoeligheid te verminderen. Clustal W is één van de meer populaire varianten op deze progressieve alignatie. 4.1.2 Beschrijving algoritme Het basisalgoritme voor het aligneren van meerdere sequenties bestaat uit drie fasen: 1. De sequenties worden paarsgewijs gealigneerd. 2. Uitgaande van de scores van de paarsgewijze alignatie wordt een boom opgebouwd. 3. De sequenties worden progressief gealigneerd volgens hun plaatsing in de boom. De verschillende fasen en hun implementatie in Clustal W worden hierna in meer detail besproken. Paarsgewijze alignatie (PW) In de eerste fase worden de sequenties per paar gealigneerd. Hierbij wordt de bestaande techniek van dynamisch programmeren gebruikt. Er bestaan twee gap penalties: één voor 44 het openen van een gat en één voor het uitbreiden van een bestaand gat. De score wordt berekend als het aantal identieke aminozuren in de beste alignatie gedeeld door het aantal vergeleken aminozuren. Alle scores worden bijgehouden in een scorematrix die de basis vormt van de volgende stap. Opstellen boomstructuur (GT) Uitgaande van de afstandsmatrix uit de eerste fase wordt een Neighbour-Joining algoritme [31] uitgevoerd, waarbij gelijkaardige sequenties zo dicht mogelijk bij elkaar worden geplaatst. Dit levert een aantal bomen zonder ouderknoop op, waarbij het gewicht van de takken evenredig is met het onderlinge verschil van de sequenties op de bladeren van de takken. De ouderknoop wordt dan zodanig gekozen dat de gemiddelde lengte van de takken aan beide kanten van de boom even lang zijn. Deze boomstructuur wordt dan verder gebruikt om een gewicht toe te kennen aan de sequenties. Hoe verder een sequentie zich van de ouderknoop bevindt, hoe groter de score. Het gewicht van takken die gedeeld worden door meerdere sequenties wordt evenredig verdeeld over deze takken. Het idee hierachter is dat sequenties die quasi identiek zijn een lage score krijgen, zodat er een beter onderscheid kan gemaakt worden tussen sequenties die sterk van elkaar verschillen. Progressieve alignatie (PA) In de laatste stap wordt een serie van paarsgewijze alignaties gebruikt om steeds grotere groepen van sequenties te aligneren. Er wordt van de bladeren van de boom vertrokken, waarbij de twee sequenties die het meest op elkaar lijken eerst worden samengenomen. Door stelselmatig naar de ouderknoop toe te werken, is het verschil tussen de nieuwe sequentie en de reeds gealigneerde groep sequenties zo klein mogelijk. Het aligneren in deze fase gebeurt opnieuw op basis van dynamisch programmeren. Reeds bestaande gaten in de alignatie worden niet aangepast, en nieuwe gaten krijgen de volledige gap penalty toegerekend. Om een score toe te kennen aan een positie in een sequentie en een groep gealigneerde sequenties, wordt een gewogen gemiddelde genomen op basis van de substitutiematrix en de scorematrix. Na deze stap is de alignatie van de sequenties compleet. Clustal W biedt nog enkele verbeteringen bovenop de bovenvermelde implementatie. Alle aanpassingen hebben betrekking op de laatste stap. De progressieve alignatie is namelijk verantwoordelijk voor de grootste fout in het uiteindelijke resultaat, aangezien er slechts sequentie per sequentie wordt bijgevoegd, in tegenstelling tot een alignatie waarbij rekening gehouden wordt met alle sequenties in één keer. Omdat Clustal W een groot aantal uitbreidingen biedt ten opzichte van het originele algoritme voor progressieve alignatie, 45 worden hier slechts de belangrijkste verbeteringen aangehaald. • Weging van sequenties. De gewichten die worden toegekend aan de sequenties worden rechtstreeks bekomen uit de boomstructuur. Gelijkaardige sequenties krijgen een verlaagde score omdat ze weinig extra informatie bevatten. Omgekeerd krijgen afwijkende sequenties een hoog gewicht. Deze gewichten worden gebruikt als gewichtscoëfficiënten bij het berekenen van de score van een alignatie in de laatste stap. Hiermee wordt voorkomen dat sequenties die goed op elkaar lijken een te grote invloed hebben op de alignatie, terwijl ze incrementeel minder meerwaarde hebben. • Initiële gap penalties. Bij aanvang van het algoritme worden er twee gap penalties berekend, die worden gebruikt bij het beginnen van een gat in de alignatie enerzijds (GOP), en het verder uitbreiden ervan anderzijds (GEP). Deze waarden worden aangepast bij elke alignatie, op basis van de volgende factoren. 1. Afhankelijk van de gebruikte substitutiematrix wordt een andere schalingsfactor gebruikt voor de GOP. 2. Wanneer sequenties grote gelijkenis vertonen, wordt de kost van het openen van een gat groter. Analoog wordt het openen van gaten bij sterk verschillende sequenties gestimuleerd door de GOP te verlagen. 3. Omdat bij langere sequenties er meer aminozuren gealigneerd zullen zijn, wordt de GOP geschaald met de logaritme van de lengte van de sequenties. Dit zal ervoor zorgen dat de score van een alignatie minder afhankelijk is van de lengte van de gebruikte sequenties. 4. Wanneer de lengte van de sequenties sterk verschillen, wordt de GEP verhoogd om te voorkomen dat er lange gaten ontstaan in de alignatie van de kortste sequentie. • Positie-specifieke gap penalties. Als er zich reeds een gat bevindt op een positie wordt zowel de GOP als de GEP verlaagd. Dit zorgt ervoor dat gaten makkelijker worden uitgebreid. Wanneer er echter geen gat is op de beschouwde positie, maar wel binnen een afstand van 8 aminozuren, dan wordt de gap opening penalty verhoogd. Dit voorkomt gaten die te dicht bij elkaar liggen. Verder kunnen de waarden nog worden aangepast indien er een bepaalde opeenvolging van aminozuren wordt waargenomen. • Keuze van de substitutiematrix. Er bestaan twee types substitutiematrices, namelijk de PAM [11] en de BLOSUM [17] series. Deze matrices hebben een verschillende prestatie wat de alignatie betreft afhankelijk van de gelijkenis van de 46 beschouwde sequenties. Doorheen het algoritme worden er vier verschillende versies van deze matrices gebruikt, waarbij gekozen wordt op basis van de score die in de boom wordt toegekend. 4.1.3 Aanverwant werk Omdat Clustal W een veel gebruikt algoritme is, en de databanken met sequenties steeds groter worden, is er al onderzoek gebeurd naar het versnellen van de originele implementatie. Een eerste studie uitgevoerd door SGI [27] onderzoekt de nodige aanpassingen om Clustal W te versnellen op een multiprocessorsysteem met gedeeld geheugen. Er blijkt ook een variatie te zitten in de uitvoeringstijd van de alignatie van dezelfde sequenties, afhankelijk van de ordening van de sequenties in het inputbestand. Om de beperkingen van een systeem met gedeeld geheugen te omzeilen, werd een aangepaste versie gemaakt van Clustal W die draait op een cluster van PC’s die communiceren via een eenvoudig netwerk [7]. Hierbij wordt gebruik gemaakt van de MPI communicatiebibliotheek [1]. Hoewel deze studies een goede basis vormen bij het zoeken naar parallellisme in het origineel algoritme, zijn ze niet altijd even bruikbaar voor uitvoering op de Cell . Vaak worden er bibliotheken gebruikt die nog niet beschikbaar zijn op de Cell , of wordt een volledig andere architectuur verondersteld. De gebruikte parallellisatie houdt uiteraard geen rekening met het beperkte geheugen van de SPU’s, waardoor de opdeling niet rechtstreeks bruikbaar is. Er is ook nog veel prestatiewinst mogelijk door te optimaliseren naar de specifieke architectuur van de Cell , door bv. gebruik te maken van vectorinstructies, lusontrolling toe te passen of de geheugenlatentie verbergen door gebruik te maken van dubbele buffering. 4.2 4.2.1 Analyse Theoretische analyse Om een idee te krijgen van de meest tijdrovende functies bij de uitvoering van Clustal W, wordt er in eerste instantie een theoretische analyse uitgevoerd. Hierbij wordt enkel uitgegaan van de broncode om een idee te vormen van de tijdscomplexiteit van de verschillende fasen van het programma, en de invloed van de input op de uitvoeringstijden. Indien mogelijk wordt deze analyse verduidelijkt aan de hand van pseudocode van de beschouwde functies. Omdat sommige delen omwille van de complexiteit niet altijd eenvoudig te verduidelijken zijn, wordt voor een beter begrip verwezen naar de broncode van Clustal W. Tabel 4.1 geeft een overzicht van de tijdscomplexiteit van de verschillende deelfasen, 47 Tabel 4.1: Tijdscomplexiteit van het originele Clustal W-algoritme. Fase Tijdscomplexiteit Paarsgewijze alignatie O(N 2 L2 ) Boomstructuur opbouwen O(N 4 ) Progressieve alignatie O(N 3 + N L2 ) Totaal O(N 4 + N L2 ) waarbij N het aantal sequenties en L de lengte van de sequenties voorstelt. Paarsgewijze alignatie Zoals reeds eerder vermeld, wordt in de eerste fase de sequenties paarsgewijs met elkaar vergeleken. Aangezien geldt dat alignatie(seq[i],seq[j])=alignatie(seq[j],seq[i]) moeten er in totaal N(N-1)/2 alignaties worden uitgevoerd. In pseudocode ziet de eerste fase uit zoals in Programma 8: Programma 8 Paarsgewijze alignatie in Clustal W. f o r ( i =0; i <N; i ++){ f or ( j =0; j <i ; j ++){ s c o r e m a t r i x [ i ] [ j ]= p a i r w i s e a l i g n m e n t ( s e q [ i ] , s e q [ j ] ) ; } } Het aligneren van de sequenties zelf is uiteraard afhankelijk van de lengte van de sequenties, en dit volgens een kwadratisch verband. De uiteindelijke complexiteit bedraagt O(N 2 L2 ). Hieruit blijkt dat het aantal en de lengte van de sequenties eenzelfde invloed hebben op de tijdsduur van de paarsgewijze alignatie. Boomstructuur opstellen In deze fase neemt het zoeken van het kleinste matrixelement overeenstemmend met de volgende tak in de boom, het meest tijd in beslag. Het opzoeken van dit element gebeurt aan de hand van het (vereenvoudigd) algoritme in Programma 9. De variabele SeqEnd is afhankelijk van het aantal sequenties, en geeft de grens aan tussen de sequenties die wel en niet zijn opgenomen in de boom. Aangezien de grootte 48 Programma 9 Opstellen boomstructuur in Clustal W. f o r ( nc =1; nc<=SeqEnd ; nc++){ f or ( j j =2; j j <=SeqEnd ; j j ++){ f or ( i i =1; i i < j j ; i i ++){ f or ( i =1; i <SeqEnd ; i ++){ sumi+=s c o r e m a t r i x [ i ] [ i i ] ; sumj+=s c o r e m a t r i x [ i ] [ j j ] ; } t o t a l=r e s c a l e ( sumi , sumj ) ; i f ( t o t a l <tmin ) { tmin=t o t a l ; mini= i i ; minj= j j ; } } } } van de scorematrix enkel afhankelijk is van het aantal sequenties, is deze fase dus niet afhankelijk van de lengte van de sequenties. De uiteindelijke orde is O(N 4 ). Deze fase zal dus snel in uitvoeringstijd stijgen als het aantal sequenties toeneemt, maar geen invloed ondervinden van langere sequenties. Deze fase zal dan ook enkel belangrijk zijn wat de uitvoeringstijd betreft, als de verhouding tussen het aantal sequenties en de lengte ervan zeer groot is. Progressieve alignatie De laatste stap in het algoritme zal de sequenties incrementeel samennemen en aligneren. De analyse van deze deelstap wordt bemoeilijkt door een recursieve functieoproep in het belangrijkste deel van de code. De schaling van de uitvoeringstijd in functie van de parameters van de sequenties is bijgevolg intuı̈tief moeilijker te verklaren. Onderstaande pseudocode bevat dan ook slechts een deel van het totale algoritme. Aangezien deze code in een later stadium van de optimalisaties voor de Cell zal worden aangepast én ze het grootste deel van de uitvoeringstijd voor de progressieve alignatie in beslag neemt, wordt ze hier toch opgenomen. 49 Programma 10 Progressieve alignatie in Clustal W. f o r ( i =1; i <Iend ; i ++){ f or ( j =1; j<=N; j ++){ hh=s+s c o r e ( i , j ) ; } } Deze (sterk verkorte) code maakt deel uit van een recursieve functie. De diepte van de recursie is afhankelijk van het aantal sequenties in de input. Dit deel van de code schaalt dan ook kubisch met het aantal sequenties. Aangezien andere delen van de progressieve alignatie die hier niet zijn opgenomen, wel afhankelijk zijn van de lengte van de sequenties, is de uiteindelijke orde van het algoritme complexer. Uiteindelijk wordt bekomen dat de progressieve alignatie van de orde O(N 3 + N L2 ) is. De invloed van het aantal sequenties is dus groter dan de lengte van de sequenties. Er is echter nog steeds een kwadratische afhankelijkheid van de lengte, zodat deze geen te verwaarlozen effect hebben op de uitvoeringstijd van deze fase. De uiteindelijke tijdscomplexiteiten van de deelfasen zijn weergegeven in Tabel 4.1 en stemmen overeen met de bevindingen uit andere studies [6]. Om een beter beeld te krijgen van de uiteindelijke verhouding van de uitvoeringstijden van de verschillende fasen, is een meting van de uitvoeringstijd nodig over een breed aantal inputs met variërende parameters. 4.2.2 Empirische analyse Uit de theoretische analyse volgt een beeld van de structuur van het algoritme. Alhoewel de tijdscomplexiteit van de fasen een aanduiding geven voor hun aandeel in de uitvoeringstijd, is er slechts een correct besluit te nemen nadat verschillende inputsets effectief zijn uitgevoerd en hun uitvoeringstijden opgemeten. Indien de aangelegde inputs voldoende van elkaar verschillen wat de lengte en het aantal sequenties betreft, zal dit een duidelijk beeld geven van een breed gamma aan mogelijke situaties. Het probleem bij deze analyse is het aantal beschikbare inputs. De twee inputsets die worden meegeleverd bij de broncode van Clustal W hebben gelijkaardige eigenschappen. Het aantal sequenties is relatief beperkt (50 voor input A en 66 voor input B) terwijl de lengte van de sequenties sterk toeneemt (150 voor input A en tot 6000 voor input B). Dit geeft dus een onvolledig beeld van het schalingsgedrag van de verschillende fasen. 50 Daarbij komt nog dat statistieken van databanken met echte sequenties [2] aantonen dat de gemiddelde lengte van sequenties veel lager is dan bij de meegeleverde inputsets. De gemiddelde lengte van de sequenties is 366 aminozuren, en er zijn heel weinig sequenties met een lengte van meer dan 2000 aminozuren. Hoewel het dus mogelijk is dat er zeer lange sequenties optreden, is het interessanter om inputsets te hebben met sequenties die in lengte niet veel van elkaar verschillen en een gemiddelde lengte rond het realistische gemiddelde van 366. Op die manier kan de schaling van de uitvoeringstijd beter in beeld worden gebracht zonder rekening te moeten houden met enkele eventuele sterk afwijkende sequenties. Om een meer realistisch beeld te kunnen vormen van het tijdsgedrag van het algoritme, zijn er dus meer inputs nodig met een grotere verscheidenheid. Om volledige controle te hebben op de parameters van de input (lengte en aantal) is ervoor gekozen om de inputs random te genereren op basis van de gevraagde parameters. Het uiteindelijke resultaat van het algoritme, namelijk de gezamenlijke alignatie van de sequenties, heeft dan ook geen zinnige informatie, maar dat is hier niet relevant aangezien enkel de uitvoeringstijd in deze stap van belang is. De informatie die wordt bekomen uit het uitvoeren van de zelf gegenereerde inputs heeft dan ook enkel betrekking op de verhouding van de uitvoeringstijden van de deelfasen van Clustal W. Ter controle worden inputs aangemaakt die analoge eigenschappen vertonen met de meegeleverde inputs. Hieruit blijkt dat de uitvoeringstijden volledig gelijklopend zijn tussen random gegenereerde inputs en werkelijke inputs. Figuur 4.1 geeft de uitvoeringstijd weer van het volledige programma in functie van de lengte van de sequenties (Figuur 4.1(a)) en het aantal sequenties (Figuur 4.1(b)). Wanneer de lengte variëert zien we dat de uitvoeringstijd vergroot volgens een kubisch verband met de lengte. Deze schaling komt voor bij alle inputs, en is dus niet afhankelijk van de absolute grootte van het aantal inputs. Een analoog beeld duikt op wanneer de uitvoeringstijd in functie van het aantal sequenties wordt uitgezet. De uitvoeringstijd schaalt op eenzelfde manier mee als bij variërende lengte van de sequenties. Opnieuw is deze schaling onafhankelijk van de lengte van de sequenties indien deze vast wordt gehouden. Dit komt overeen met de bevinding dat de tijdscomplexiteit van het volledige algoritme O(N 4 + N L2 ) is. Deze grafieken helpen echter niet in het bepalen van de deelfasen die verantwoordelijk zijn voor het grootste deel van de uitvoeringstijd en geven ook geen inzicht in de verschuivingen in het aandeel van de uitvoeringstijden wanneer de inputparameters veranderen. Om een beeld te kunnen vormen van de uitvoeringstijd van de verschillende deelfasen, wordt in Figuur 4.2 de uitvoeringstijden van de verschillende fasen uitgezet. In Figuur 4.2(a) wordt het aantal sequenties vast op 100 gehouden om de invloed van de lengte van 51 (a) Uitvoeringstijd ifv lengte (b) Uitvoeringstijd ifv aantal sequenties Figuur 4.1: Uitvoeringstijd van Clustal W bij variërende lengte (boven) en aantal sequenties (onder). de sequenties op de verschillende fasen te kunnen nagaan. De uitvoeringstijden van de paarsgewijze en progressieve alignatie schalen zoals verwacht sterker dan lineair mee. In 52 (a) Uitvoeringstijd ifv lengte (N=100) (b) Uitvoeringstijd ifv aantal sequenties (L=100) Figuur 4.2: Uitvoeringstijd deelfasen bij vaste lengte en aantal sequenties. absolute waarde is de uitvoeringstijd van de paarsgewijze alignatie in het algemeen het grootst. Deze bevindingen stemmen overeen met de resultaten van de theoretische analyse. De uitvoeringstijd voor het opbouwen van de boomstructuur blijft steeds gelijk, omdat deze in uitvoeringstijd niet afhankelijk is van de lengte van de sequenties. Wanneer de lengte van de sequenties constant wordt gehouden op 100, wordt de grafiek uit Figuur 4.2(b) bekomen. Een eerste observatie is dat de paarsgewijze alignatie met 53 afstand het meeste tijd in beslag neemt, en op ongeveer dezelfde manier meeschaalt als wanneer het aantal sequenties werd vastgehouden. Het schalingsgedrag van de progressieve alignatie is moeilijker te verifiëren omdat de tijdscomplexiteit twee termen bevat die afhankelijk zijn van het aantal sequenties. In ieder geval kan worden besloten dat er sneller dan lineair wordt meegeschaald met het aantal sequenties. Het opbouwen van de boom stijgt duidelijk snel in uitvoeringstijd wanneer het aantal sequenties groter wordt. Dit ligt in de lijn van de verwachting, omdat de tijdscomplexiteit van deze fase O(N 4 ) is. Een eerste conclusie die we hier kunnen trekken in verband met de meest tijdrovende functie is dat de paarsgewijze alignatie het meest tijd in beslag lijkt te nemen. Om een beter gefundeerde beslissing te kunnen nemen moeten er echter metingen worden genomen over een breder gamma aan inputs, met extremere waarden voor de inputparameters om randgevallen beter te kunnen onderscheiden. Figuur 4.3 illustreert de procentuele uitvoeringstijd van de verschillende fasen. Per deelgrafiek wordt de uitvoeringstijd in functie van de lengte van de sequenties uitgezet. De deelgrafieken verschillen van elkaar in het aantal sequenties van de verwerkte input. Deze grafieken leveren heel wat interessante informatie op. • Wanneer het aantal sequenties een redelijke waarde aanneemt, is de paarsgewijze alignatie met voorsprong de fase die het meest tijd in beslag neemt. Behoudens enkele uitzonderingsgevallen is het aandeel van deze fase groter dan 50% bij 50 sequenties of meer. Een stijgende lengte van de sequenties vergroot het aandeel nog meer. • Wanneer het aantal sequenties relatief beperkt blijft, is de progressieve alignatie verantwoordelijk voor bijna de volledige overige uitvoeringstijd die niet door de paarsgewijze alignatie in beslag wordt genomen. Een stijgende lengte blijkt hier bijna geen invloed op te hebben. Wanneer het aantal sequenties echter zeer groot wordt (500 en meer), daalt dit aandeel sterk. • Het opbouwen van de boomstructuur neemt slechts in uitzonderlijke gevallen relatief gezien veel tijd in beslag. Wanneer het aantal sequenties beperkt blijft, is het aandeel van deze fase slechts marginaal. Enkel bij een groot aantal sequenties (500 en meer) en zeer korte sequenties (tot 10 aminozuren lang) stijgt de uitvoeringstijd boven de 10% van het totaal. Hoewel deze situaties over het geheel gezien weinig voorkomen, is de uitvoeringstijd veel groter dan de uitvoeringstijd van de paarsgewijze alignatie en de progressieve alignatie. De gegevens van de metingen maken het mogelijk om een goed onderbouwde keuze te maken van de fasen die eerst moeten worden geoptimaliseerd om een zo groot mogelijke 54 (a) Aantal sequenties = 10 (b) Aantal sequenties = 50 (c) Aantal sequenties = 100 (d) Aantal sequenties = 500 (e) Aantal sequenties = 1000 Figuur 4.3: Procentueel aandeel van de verschillende deelfasen op de uitvoeringstijd. versnelling te bekomen. Dit blijkt duidelijk de paarsgewijze alignatie te zijn, die in het merendeel van de situaties meer dan 50% van de totale uitvoeringstijd bedraagt. Aangezien bij de meegeleverde inputs het aantal sequenties vrij beperkt is, is de progressieve alignatie zeker niet te verwaarlozen. De tweede fase, waarin de boom wordt opgebouwd, is echter enkel in de randgevallen waar de verhouding tussen het aantal en de lengte van de sequenties zeer groot is belangrijk. Er wordt dan ook verder geen inspanning geleverd om deze fase te versnellen. Voor mogelijke manieren om deze fase te parallelliseren, wordt verwezen naar aanverwant werk [6]. 55 4.3 Initiële aanpassingen Clustal W is geschreven in C, waardoor het na hercompilatie zonder aanpassingen uitvoerbaar zou moeten zijn op de Cell . Dit is echter niet het geval, enkele kleine aanpassingen zijn noodzakelijk. • De math-bibliotheek van de PPU ondersteunt standaard geen bewerkingen op floatingpoint getallen met dubbele precisie [21]. Om te voorkomen dat overal in de originele code variabelen van dubbele naar enkele precisie moeten worden veranderd, wordt er een wrapper geschreven. Deze zal oproepen naar functies uit de math-bibliotheek met argumenten van dubbele precisie omzetten naar functieoproepen met argumenten van enkele precisie. Deze functieoproepen worden wel ondersteund op de PPU. Hierbij gaat uiteraard wel een deel precisie verloren, zodat na uitvoering van het programma er moet worden op gelet dat het resultaat nog steeds correct is. • Hoewel in de documentatie staat dat de math-bibliotheek alle functies uit de ANSI C99 standaard bevat, blijkt dit niet het geval te zijn. Zo is de functie lgamma niet geı̈mplementeerd, terwijl deze wordt opgeroepen in Clustal W. Deze functie berekent de natuurlijke logaritme van de absolute waarde van gamma(x), waarbij de gammafunctie als volgt is gedefiniëerd. Z Γ(x) = ∞ t(x−1) e−t dt (4.1) 0 Deze functie kan echter geı̈mplementeerd worden aan de hand van een look-up table en oproepen naar log en exp [30]. Na deze relatief eenvoudige aanpassingen kan de code gecompileerd worden voor uitvoering op de PPU. De output stemt overeen met de referentie-output van Clustal W. Het verlies aan precisie door gebruik te maken van enkele precisie en de alternatieve implementatie van lgamma heeft dus geen negatieve invloed op de resultaten van de alignatie. Vanuit deze aangepaste code wordt verder gewerkt naar een geoptimaliseerde versie die gebruik maakt van de SPU’s, rekening houdend met de opgedane kennis van de empirische analyse en de ervaring opgedaan bij het optimaliseren van matrixvermenigvuldiging. 56 4.4 4.4.1 Paarsgewijze alignatie Uitvoering op de SPU Om een programma ten volle te laten profiteren van de Cell-architectuur, moeten de SPU’s zoveel mogelijk aan het werk worden gezet. Een eerste stap hierbij is de keuze van de code die door de SPU’s zal worden uitgevoerd. Bij het aanpassen van het algoritme voor matrixvermenigvuldiging was de keuze eenvoudig omdat de code zeer beperkt in grootte is. Voor een realistisch programma zoals Clustal W is de keuze minder triviaal, omdat de code vaak te groot is om in één keer op een SPU uit te voeren. Een eerste poging om Clustal W in zijn totaliteit op een SPU uit te voeren bleek dan ook niet te slagen. Uit het opmeten van de uitvoeringstijden blijkt dat de eerste fase, de paarsgewijze alignatie, ruimschoots de meerderheid van de tijd in beslag neemt. De meeste aandacht bij het overbrengen van code van de PPU naar de SPU gaat dan ook uit naar deze fase van het algoritme. Hierbij wordt het programmeermodel beschreven in 3.2 opnieuw gehanteerd om de aanpassingen in incrementele en relatief eenvoudige stappen te laten verlopen. Bij de keuze van de code die in een aparte SPU-draad zal worden uitgevoerd moet een evenwicht gezocht worden tussen code die voldoende berekeningen bevat om de creatie van een nieuwe draad te rechtvaardigen, maar toch klein genoeg is om zonder problemen in het beperkt lokaal geheugen te passen. Een eerste logische keuze was om de alignatie van de sequenties in een nieuwe draad uit te voeren. De alignatie zelf is de enige vorm van berekeningen die wordt uitgevoerd tijdens de eerste fase van Clustal W. De nodige communicatie van en naar de SPU is zeer beperkt omdat eens alle nodige data aanwezig is in het lokaal geheugen van de SPU, de berekeningen volledig kunnen worden uitgevoerd. Het resultaat dat moet worden teruggeschreven naar het hoofdgeheugen is de optimale alignatie en de bijhorende score. Het probleem is echter dat het aantal uit te voeren alignaties N(N-1)/2 bedraagt, en dus kwadratisch afhankelijk is van het aantal sequenties. Een enkele alignatie neemt dus relatief weinig tijd in beslag, waardoor de overhead van het creëeren van een draad voor elke nieuwe alignatie te groot is. In de plaats daarvan wordt er bij het begin van de paarsgewijze alignatie één SPU-draad aangemaakt. Door communicatie met de PPU weet de SPU welke sequenties met elkaar moeten worden gealigneerd, en wordt het resultaat van deze alignatie teruggegeven. Wanneer alle sequenties paarsgewijs met elkaar zijn gealigneerd, geeft de PPU het signaal aan de SPU om de draad te termineren. Het controlealgoritme in de PPU is hieronder weergegeven. Merk op dat het in principe mogelijk is om zonder enige vorm van communicatie de SPU-draad de sequenties te laten aligneren. Op het einde van de eerste fase is immers enkel 57 een matrix nodig waarin de scores van de paarsgewijze alignaties worden weergegeven. Deze aanpak is vermeden omdat het moeilijker is om op deze manier uitbreiding te doen naar de controle van de SPU-draden. Indien immers de PPU beslist wat de SPU uitvoert, is het ook eenvoudiger om de werklast te verdelen met het oog op parallellisatie. Programma 11 Controle-algoritme voor paarsgewijze alignatie met één SPU-draad s p e i d=s p e c r e a t e t h r e a d (& p a i r w i s e ,& c o n t e x t ) ; f o r ( i =0; i <N; i ++){ f o r ( j =0; j <i ; j ++){ mailbox write ( spe id , i ) ; mailbox write ( spe id , j ) ; mailbox read ( ) ; } } //SPU−draad t e r m i n e e r t b i j l e z e n i n d e x −1 mailbox write ( spe id , −1); De functie spe_create_thread maakt een nieuwe draad, en krijgt als argumenten een verwijzing naar de uit te voeren code (hier de code voor de paarsgewijze alignatie) en naar een contextobject dat de nodige data voor de uitvoering bevat. Als terugkeerwaarde wordt een identificatienummer gegeven waarmee communicatie met de SPU waarop de draad wordt uitgevoerd mogelijk is. Bij het uitvoeren van de code op de SPU wordt gewacht op een signaal van de PPU. Dit zal aangeven welke sequenties met elkaar moeten worden vergeleken. De communicatie gebeurt via het mailboxsysteem. De PPU schrijft de indices van de sequenties naar de mailbox van de SPU met mailbox_write. Daarna wordt gewacht tot de SPU een bericht terugschrijft in de mailbox van de PPU, wat aangeeft dat de alignatie klaar is. Dit bericht wordt uitgelezen met mailbox_read, een blokkerende functie. De waarde van het bericht speelt hier geen rol. Wanneer alle sequenties met elkaar zijn vergeleken, schrijft de PPU een negatieve waarde in de mailbox van de SPU. Dit is het signaal voor de SPU om de draad af te sluiten. Om de alignatie op de SPU mogelijk te maken, moeten alle daartoe benodigde variabelen en datastructuren in het lokaal geheugen van de SPU worden geplaatst. Om te voorkomen dat er overbodige data wordt meegegeven, is een liveness analyse van de beschouwde code nodig. Dit wordt aanzienlijk bemoeilijkt door het overvloedig gebruik van globaal gedefinieerde variabelen in de code. De declaratie en initialisatie van de no- 58 dige data gebeurt met andere woorden verspreid over een aantal codebestanden. Na het nodige speurwerk kan een contextstructuur worden opgebouwd die de nodige variabelen of referenties naar de datastructuren bevat. Het adres van deze contextstructuur wordt meegegeven bij de creatie van de SPU-draad. Net als bij de matrixvermenigvuldiging, moet ook hier erop gelet worden dat geheugenadressen zijn afgerond op een veelvoud van 128. Hiertoe worden terug de aangepaste functies malloc_align en free_align gebruikt. De allocatie van het geheugen is eveneens verspreid over een ruim aantal bestanden, waardoor grote zorg moet worden besteed dat alle datastructuren die nodig zijn voor de paarsgewijze alignatie correct worden gealigneerd en vrijgegeven. Pas na deze stap kan de paarsgewijze alignatie zonder problemen worden uitgevoerd op de SPU. De uitvoeringstijd zou naar verwachting iets hoger moeten liggen wanneer dezelfde code zonder aanpassingen aan het algoritme zelf wordt verplaatst van de PPU naar de SPU. In het geval van de paarsgewijze alignatie blijkt de uitvoering echter een stuk trager te zijn, tot een factor 100 toe. Dit valt uiteraard niet alleen te verklaren door de overhead van het creëren van de SPU-draad, het DMA-en van de data of de communicatie met de PPU. Deze elementen waren immers ook aanwezig bij de uitvoering van matrixvermenigvuldiging, en daar was de uitvoeringstijd op de PPU en de SPU van dezelfde grootte-orde. Bovendien is het vinden van de oorzaak van de vertraging moeilijk aangezien er weinig tools voorhanden zijn die profilering of uitgebreide analyse van de uitvoering op de SPU mogelijk maken. Na heel wat zoekwerk met behulp van de tools FDPR-pro en Visual Performance Analyzer, die geen profilering uitvoeren maar het aantal functieoproepen meten en voorstellen, werd opgemerkt dat functie fflush zeer veel werd opgeroepen. Dit is uiteraard geen referentie wat de uitvoeringstijd betreft, maar indien het probleem bij deze functie zit kan het wel de grote vertraging verklaren. Uiteindelijk blijkt dat na uitcommentariëren van alle oproepen naar deze functie de uitvoeringstijd terug in de lijn van de verwachtingen ligt. De oorzaak ligt in het feit dat alle systeemoproepen vanaf de SPU gebeuren door de oproep door te sturen naar de PPU, aangezien daar het besturingssysteem wordt op uitgevoerd. De fflush-functie zorgt er voor dat alle data die naar de standaardoutput moet wordt geschreven en zich nog in de buffers bevinden, verplicht worden weggeschreven (in dit geval naar het scherm). Omdat hiervoor het besturingssysteem moet worden aangesproken, is er een call naar de PPU noodzakelijk. Wanneer de functie vanop de PPU zelf wordt opgeroepen zal dit geen bijkomende problemen veroorzaken, maar op de SPU moeten alle oproepen naar het besturingssysteem worden doorgegeven naar de PPU. Deze zal wachten tot de PPU de aanvraag heeft verwerkt. Aangezien dit aanzienlijk hogere wachttijden zal opleveren, zal een groot aantal oproepen in totaal leiden 59 tot een enorme vertraging. Wanneer functies zoals fflush en printf worden vermeden, wat het geval is in de matrixvermenigvuldiging, is de uitvoering op de PPU en de SPU ongeveer gelijklopend. De resultaten voor verschillende inputsets zijn te zien in Figuur 4.4, waarbij de uitvoeringstijd van de fasen zijn genormaliseerd op de uitvoeringstijd van die fase bij uitvoering op de PPU. Hierop is terug te zien dat bij overgang van de PPU naar de SPU zonder aanpassingen van de code, de uitvoeringstijd iets toeneemt. De andere fasen draaien op de PPU, waardoor hun uitvoeringstijd niet verandert. (a) Inputset A (b) Inputset B Figuur 4.4: Verhouding van de uitvoeringstijden bij enkeldradige uitvoering. 4.4.2 Parallellisatie Om uiteindelijk toch een versnelling van de uitvoeringstijd te verkrijgen, moet de code van alle SPU’s gebruik maken. Voor de paarsgewijze alignatie is dit eenvoudig omdat het berekenen van de elementen in de scorematrix door twee sequenties te aligneren, onafhankelijk is van elkaar. Het komt er dus enkel op neer om het berekenen van de scorematrix te verdelen over de SPU’s. Er zijn in het extreme geval twee mogelijkheden om de werklast over de SPU’s te verdelen. In het eerste geval wordt vóór het starten van de SPU-draden berekend welke elementen door welke SPU zullen worden berekend. Het toewijzen van de werklast gebeurt dus volledig statisch. Het voordeel van deze techniek is dat de communicatie met de PPU tot een minimum wordt herleid, waardoor er geen vertraging ten gevolge van communicatie en synchronisatie zullen optreden. Het grote nadeel is echter dat er op voorhand niet met zekerheid kan worden bepaald of de werklast evenredig is verdeeld. De scorematrix is immers symmetrisch, waardoor enkel de elementen van de bovendriehoeksmatrix moeten worden berekend. Dit maakt het gelijk verdelen van de werklast aanzienlijk moeilijker, 60 omdat een driehoeksstructuur minder eenvoudig is op te delen in gelijke delen dan een volledige matrix. Zeker wanneer het aantal sequenties beperkt is, zal door benaderingen in het berekenen van de stop- en startindices een significante fout optreden, met een onevenwichtige verdeling tot gevolg. Het andere uiterste is om de toewijzing van een te berekenen element aan een SPU volledig dynamisch te laten gebeuren. Hierbij zal de PPU op zoek gaan naar een beschikbare SPU om een nieuw element te berekenen. Wanneer een SPU klaar is met uitvoeren, wordt een bericht teruggestuurd naar de PPU om aan te geven dat de SPU terug beschikbaar is en nieuwe elementen kan uitvoeren. Deze techniek garandeert een zo goed mogelijke werkverdeling, en een minimale wachttijd voor de SPU’s. Er zijn echter in totaal N(N-1) communicatieboodschappen nodig, twee per te berekenen matrixelement, waardoor er zeer veel overhead door communicatie optreedt. Dit kan eventueel nog worden beperkt door groepen van elementen toe te wijzen, wat de kans op een onevenwichtige verdeling echter groter maakt. Enkel indien het communicatienetwerk voldoende snel is, zal deze techniek geen onaanvaardbaar prestatieverlies met zich meebrengen. Uiteindelijk wordt voor een tussenoplossing gekozen. Een eerste groot deel van de matrix wordt statisch toegewezen over de SPU’s. In plaats van een enkel element per keer toe te wijzen, wordt een volledige rij van de scorematrix aan een SPU toegewezen. Dit vereenvoudigt de berekeningen die de start- en stopindices per SPU bepalen, maar hebben ook een grotere afwijking tot gevolg. Daarom wordt een laatste deel van de matrix volledig dynamisch toegewezen. Wanneer een SPU klaar is met zijn eerste statisch toegewezen deel te berekenen, wijst de PPU de volgende te berekenen rij toe aan die SPU. Omdat het aantal te berekenen elementen per rij lineair afneemt, zal dit uiteindelijk tot een evenwichtige werkverdeling leiden, met een beperkt aantal communicatieberichten. De gekozen opdeling van het statisch en dynamisch te berekenen deel is geı̈llustreerd in Figuur 4.5. Het statische deel moet nu worden opgesplitst in een aantal delen die evenveel elementen bevat. Die grenzen worden steeds gelegd op het einde van een rij, zodat eenzelfde rij steeds door dezelfde SPU wordt berekend. We kiezen ervoor om 75% statisch toe te wijzen, en de overige 25% dynamisch. Dit zal ervoor zorgen dat de initiële werklast voldoende groot is, maar er toch genoeg elementen zijn die dynamisch worden toegewezen om onevenwichtigheden uit te vlakken. Het bepalen van de grenzen gebeurt volledig analytisch in de PPU. Bij de berekeningen van de grenzen worden de volgende variabelen gebruikt. 61 Figuur 4.5: Keuze statisch en dynamisch verdeelde werklast. n: Totaal aantal rijen in de scorematrix ji : Startindex voor SPU i ki : Stopindex voor SPU i T : Beschikbare SPU’s De grootste index hoort bij de rij met het meeste elementen die moeten berekend worden, met andere woorden rij n bevat n − 1 te berekenen elementen. Algemeen geldt dus dat rij j in totaal j − 1 relevante elementen bevat. Het totaal aantal elementen vervat tussen twee willekeurige rijen j en k met j > k is dan: Sj→k = j−1 X i− 0 = k−1 X i 0 j(j − 1) − k(k − 1) 2 Om ervoor te zorgen dat het statische deel van de werklast 75% van de totale werklast bedraagt, moet een index n0 worden gevonden waarvoor geldt dat eenvoudig in te zien dat n0 = n 2. 62 Sn→n0 Sn→0 = 3/4. Het is Sn→ n2 = = Sn→0 = ⇒ lim n→∞ Sn→ n2 Sn→0 = = n(n − 1) − n2 ( n2 − 1) 2 n 3n − 2 ( ) 2 4 n(n − 1) 2 lim n→∞ 3n−2 4 n−1 3 4 Uit de eis dat de volledige werklast evenredig verdeeld moet zijn over T SPU’s, krijgen we volgende formule voor het bepalen van de start- en stopindices van de SPU. ⇔ Sji →ki Sn→ n2 = 1 T ji (ji − 1) − ki (ki − 1) 4 2 n(3n − 2) = 1 T Dit geeft echter slechts één vergelijking voor twee onbekenden ji en ki . De verschillende onderverdelingen van de werklast moeten echter op elkaar aansluiten, zodat er nog andere vergelijkingen kunnen worden opgesteld die het mogelijk maken om de start- en stopindex voor elke SPU te berekenen. j0 = n ji = ki−1 n kT = 2 Deze vergelijkingen maken het mogelijk om de indices iteratief te berekenen. Het controle-algoritme voor de PPU is weergegeven in Programma 12 op pagina 64. 63 Programma 12 Controle-algoritme voor paarsgewijze alignatie met werklastverdeling over meerdere SPU-draden f o r ( i =0; i <T ; i ++){ s p e i d [ i ]= s p e c r e a t e t h r e a d (& p a i r w i s e ,& c o n t e x t [ i ] ) ; } // S t a t i s c h e w e r k v e r d e l i n g j=n ; f o r ( i =0; i <T ; i ++){ k=c a l c u l a t e s t o p i n d e x ( n , j ,T ) ; mailbox write ( spe id [ i ] , j ) ; mailbox write ( spe id [ i ] , k ) ; j=k ; } // V e r d e e l de o v e r i g e w e r k l a s t dynamisch r e m a i n i n g s e q u e n c e s=k ; while ( r e m a i n i n g s e q s >0){ f o r ( i =0; i <T ; i ++){ data = m a i l b o x r e a d ( s p e i d [ i ] ) ; i f ( data !=0xFFFFFFFF) { mailbox write ( spe id [ i ] , remaining seqs ) ; m a i l b o x w r i t e ( s p e i d [ i ] , r e m a i n i n g s e q s −1); r e m a i n i n g s e q s −−; } } } f o r ( i =0; i <T ; i ++){ mailbox write ( spe id [ i ] , −1); } 64 Door gebruik te maken van dit communicatieschema wordt de werklast efficiënt verdeeld over het aantal beschikbare SPU’s. Uit de grafiek in Figuur 4.6 is duidelijk op te maken dat de versnelling van de paarsgewijze alignatie meeschaalt met het aantal gebruikte SPU’s. De versnelling van de paarsgewijze alignatie bij gebruik van 8 SPU’s ten opzichte van 1 SPU is 6.93 voor input A en 7.7 voor input B. Theoretisch is de maximumversnelling bij perfecte versnelling 8, waaruit blijkt dat de uitvoeringstijd zeer goed meeschaalt met het aantal gebruikte SPU’s. De werklast wordt dus evenwichtig verdeeld zonder een grote vertraging te introduceren door de gebruikte communicatie met de PPU. De totale versnelling is 2.18 voor input A en 1.54 voor input B wanneer de paarsgewijze alignatie op de SPU’s in plaats van op de PPU wordt uitgevoerd. Om een grotere versnelling van het totale programma te bekomen, moet de progressieve alignatie worden versneld. Bij de geparallelliseerd versie van het programma is deze immers verantwoordelijk voor 87% van de totale uitvoeringstijd voor input B. (a) Inputset A (b) Inputset B Figuur 4.6: Genormaliseerde verhouding van de uitvoeringstijden bij meerdradige uitvoering. Om ervoor te zorgen dat de code op de SPU in staat is om grote inputsets te verwerken, moet er terug aandacht besteed worden aan het geheugengebruik. Het huidige algoritme kopiëert immers nog steeds alle data die live-in is bij het begin van de paarsgewijze alignatie. Dit heeft tot gevolg dat een kopie van alle sequenties zich in het lokaal geheugen van elke SPU bevindt. Eveneens wordt er voldoende geheugen gereserveerd voor alle te berekenen elementen, die aan het einde van het algoritme worden teruggeschreven naar het hoofdgeheugen. Dit heeft tot gevolg dat het geheugengebruik sterk toeneemt, niet alleen bij een stijgend aantal sequenties, maar ook als de sequenties langer worden. Daarom wordt eenzelfde techniek als bij de matrixvermenigvuldiging gehanteerd, waarbij data slechts aanwezig is in het lokaal geheugen zolang ze ook effectief nodig is voor de uitvoering. Wanneer data niet of gedurende een lange tijd niet nodig is, wordt het gebruikte geheugen terug vrijgegeven ten voordele van nieuwere data. Daarenboven worden 65 de berekende elementen gegroepeerd per rij en, van zodra een volledige rij is berekend, wordt deze teruggeschreven naar het hoofdgeheugen. De verkorte versie van het algoritme op de SPU is beschreven in Programma 131 . Programma 13 Algoritme voor paarsgewijze alignatie op de SPU met beperkt geheugengebruik f o r ( i=I s t a r t ; i <Iend ; i ++){ s e q [ i ]= m a l l o c ( s e q l e n g t h [ i ] ) ; DMA in ( s e q [ i ] ) ; mat [ i ]= m a l l o c ( i ) ; f o r ( j =0; j <i ; j ++){ s e q [ j ]= m a l l o c ( s e q l e n g t h [ j ] ) ; DMA in ( s e q [ j ] ) ; mat [ i ] [ j ]= p a i r w i s e a l i g n ( s e q [ i ] , s e q [ j ] ) ; f r e e ( seq [ j ] ) ; } DMA out ( mat [ i ] ) ; f r e e ( mat [ i ] ) ; f r e e ( seq [ i ] ) } 1 In het beschouwde algoritme is enkel de code relevant voor het beperken van het geheugengebruik opgenomen. Code voor onder andere de communicatie is voor de eenvoud weggelaten. 66 Door de grote datastructuren komt een andere beperking van DMA-transfers aan het licht: de maximumgrootte van een enkele DMA-transfer is 16 KiB. Zeker voor de standaardinputsets waar de sequenties al snel enkele honderden aminozuren lang zijn, wordt deze grootte regelmatig overschreden. De oplossing is eenvoudigweg om de DMA-transfers op te delen in verschillende blokken tot 16 KiB, zodat grote datastructuren met meerdere DMA-transfers in het lokaal geheugen worden gekopiëerd. Met deze aanpassingen is de aangepaste versie van Clustal W met geparallelliseerde uitvoering van de paarsgewijze alignatie in staat om een groot aantal inputsets correct te verwerken. 4.5 Progressieve alignatie Om een verdere versnelling van Clustal W te bekomen moet de laatste fase, de progressieve alignatie, worden versneld. Na versnelling van de paarsgewijze alignatie stijgt het procentuele aandeel op de uitvoeringstijd van deze fase namelijk tot 62% voor inputset A en 87% voor inputset B. Paarsgewijze alignatie is nog slechts verantwoordelijk voor 36% en 12% respectievelijk, waardoor het verder optimaliseren van deze fase slechts een marginaal effect zal hebben. Het vinden van parallellisme bij de progressieve alignatie is heel wat moeilijker, omdat het aantal afhankelijkheden tussen de recursief opgeroepen functies groot is. Dit staat in schril contrast met het parallelliseren van de paarsgewijze alignatie, die bijna triviaal is door het ontbreken van afhankelijkheden. Na een grondige studie van de broncode en relevante literatuur blijkt er toch mogelijkheid tot parallellisatie te zijn. Na profilering van de originele code van Clustal W wordt opgemerkt dat de pdiff-functie 80% van de uitvoeringstijd van de progressieve alignatie beslaat. Deze recursieve functie bestaat in hoofdzaak uit twee lussen die de sequenties in een voorwaartse en achterwaartste fase overlopen om zo een optimale alignatie te bekomen. Deze twee lussen zijn onafhankelijk van elkaar, en kunnen dus in parallel op de SPU’s worden uitgevoerd. Theoretisch gezien is er met andere woorden een versnelling met een factor twee mogelijk voor de hete code van de progressieve alignatie. In een eerste poging worden twee nieuwe SPU-draden aangemaakt voor elke oproep van pdiff waarin de twee lussen in parallel worden uitgevoerd. Dit leidt echter tot een grote vertraging omdat, onder andere wegens het recursieve karakter, deze functie zeer vaak wordt opgeroepen. De tijd die gewonnen wordt door de lussen in parallel uit te voeren wordt dus meer dan teniet gedaan door de tijd die nodig is om nieuwe draden aan te maken en de nodige data via DMA-transfers door te sturen. Een intelligentere aanpak dringt zich op, waarbij SPU-draden op een statische manier in het begin van de 67 progressieve alignatie worden gecreëerd. Een oproep van pdiff kan op die manier worden vervangen door een bericht naar de SPU’s te sturen die, na het transfereren van de nodige data, de nodige berekeningen in parallel uitvoeren. Om te voorkomen dat er redundante data wordt getransfereerd, gebeurt de controle in de SPU-draden door een eenvoudige toestandsmachine. Aan de hand van het signaal dat door de PPU wordt verstuurd, kan de SPU bepalen of het om een recursieve oproep gaat of niet. Bij recursieve oproepen veranderen namelijk enkel de argumenten van de functie, en blijft alle andere data dezelfde. Op deze manier wordt zowel de kost van het aanmaken van nieuwe draden als het aantal DMA-transfers beperkt. Het nadeel van het gebruik van statisch aangemaakte draden is dat er veel extra communicatie met de PPU nodig is. Dit zou kunnen voorkomen worden door een groter deel van de programmacode op de SPU’s uit te voeren, maar dit maakt het mogelijke parallelisme ongedaan en is niet mogelijk vanwege het beperkte lokaal geheugen. Uiteindelijk blijkt de uitvoeringstijd van de progressieve alignatie na het gebruik van statisch aangemaakt draden een aantal grote-ordes lager te liggen dan bij het gebruik van dynamisch aangemaakte draden, maar nog steeds hoger dan bij het origineel: 174 ms tegenover 140 ms origineel voor inputset A. Na het uitvoeren van enkele optimalisaties kan de uitvoeringstijd nog gereduceerd worden tot 150 ms, maar dit levert nog steeds geen versnelling op. Uit het opmeten van de tijd besteed aan de eigenlijke berekeningen en de DMA-transfers, blijkt dat de SPU-draden slechts een fractie van de tijd nuttige berekeningen doen. De vertraging is dus te wijten aan de overvloedige communicatie en het aanmaken van de contextstructuren die nodig zijn voor de DMA-transfers van de SPU’s. Desondanks de parallelisatie en de optimalisatie van de progressieve alignatie, kan er op deze manier dus geen versnelling worden bekomen. Het aanpassen van de laatste fase van Clustal W toont aan dat niet alle code even eenvoudig valt te versnellen bij uitvoering op de Cell. Indien de code niet parallelliseerbaar of vectoriseerbaar is, is het moeilijk om een versnelling te bekomen. Als er bijkomend veel communicatie nodig is of veel draden worden aangemaakt, is het gebruik van de SPU’s zelf contra-productief. 4.6 Mogelijke uitbreidingen Bij de aanpassingen van Clustal W in dit werk is voornamelijk gezocht naar parallellisme om versnelling te bekomen. Er blijven nog veel mogelijkheden over om de uitvoering op de Cell nog verder te optimaliseren. Er wordt dan hoofdzakelijk gedacht aan het gebruik van vectorinstructies, die niet alleen de berekeningen kunnen versnellen maar ook het 68 controleverloop kan vereenvoudigen. Wegens het beperkte karakter van deze scriptie is deze potentie niet benut. Meer aandacht kan ook nog worden besteed aan de progressieve alignatie, om zo alsnog een versnelling voor deze fase te bekomen. Niet alleen vectorinstructies kunnen deze fase versnellen, ook een ander communicatieschema kan de prestatie vergroten. Verder onderzoek naar meer parallellisme in deze fase kan er ook voor zorgen dat alle SPU’s worden benut, eventueel door resultaten op voorhand in parallel door de SPU’s te laten berekenen en op te slaan in het hoofdgeheugen, waar ze later terug kunnen worden opgevraagd. 69 Hoofdstuk 5 Besluit Om de beperkingen inzake frequentieschaling en vermogenverbruik te doorbreken, bevatten moderne processoren steeds vaker meerdere rekenkernen. Een ver doorgedreven implementatie van deze recente trend is de Cell BE architectuur. In eerste instantie werd deze architectuur nader onderzocht. Het betreft een heterogene multiprocessor met een innovatieve geheugenhiërarchie. De processor bestaat uit negen rekenkernen: één PPU die het sterkst lijkt op traditionele microprocessoren, en acht vectorprocessoren, de SPU’s. Deze processorelementen beschikken over hun eigen beperkt lokaal geheugen. Data wordt tussen alle processorelementen getransfereerd via DMA-instructies. Het geheel maakt een hoge piekprestatie mogelijk, maar is een grote uitdaging op het gebied van efficiënt programmeren. Om de architectuur en haar moeilijkheden toe te lichten, werd een eenvoudig voorbeeld uitgewerkt, namelijk matrixvermenigvuldiging. Dit probleem wordt systematisch aangepast om optimaal gebruik te maken van de mogelijkheden van de Cell. Van een initiële implementatie die enkel gebruik maakt van de PPU, werd via incrementele verbeteringen een implementatie uitgewerkt die geparallelliseerd werd over alle acht de SPU’s. Om de beperkingen van de geheugenstructuur te omzeilen, werd extra aandacht besteed aan het geheugengebruik en buffering van de DMA-transfers. De berekeningen zelf werden versneld door lusontvouwing. De versnelling tegenover de originele uitvoering op de PPU bedraagt 19 voor een dimensie van 1024. Aangezien matrixvermenigvuldiging een zeer eenvoudig probleem is, werd een complexer algoritme geoptimaliseerd, namelijk Clustal W. Clustal W is een programma voor de alignatie van meerdere DNA-sequenties, opgebouwd uit drie grote fasen: paarsgewijze alignatie, het opbouwen van een boomstructuur en progressieve alignatie. Na een beknopte uiteenzetting van de werking moest een analyse worden uitgevoerd om de tijdsverdeling van de verschillende fasen na te gaan. De meegeleverde inputsets bleken echter te weinig 70 verschillend, zodat er random inputsets moesten worden gecreëerd om de invloed van de verschillende parameters op de uitvoeringstijd na te gaan. Hieruit bleek vooral de paarsgewijze alignatie in het merendeel van de gevallen de meeste tijd in beslag te nemen, gevolgd door progressieve alignatie. Het opbouwen van de boomstructuur nam enkel in de gevallen waar het aantal sequenties groot is maar de lengte in verhouding zeer klein, de meeste tijd in beslag. De paarsgewijze alignatie werd versneld door deze deelfase te parallelliseren over alle SPU’s. Om dit mogelijk te maken moest ook hier het geheugengebruik sterk verminderd en geoptimaliseerd worden. De werklast werd op een doeltreffende manier verdeeld, zodat alle SPU’s met een minimum aan communicatie een gelijk deel van de totale werklast krijgen toegewezen. Hierdoor schaalt de versnelling van de paarsgewijze alignatie quasi perfect mee met het aantal gebruikte SPU’s, voor een totale versnelling tussen 1.5 en 2.2 voor de meegeleverde inputsets. Als laatste aanpassing van Clustal W werd de progressieve alignatie geoptimaliseerd. Deze laatste stap bleek moeilijker te parallelliseren wegens het recursieve karakter van de functies. Uiteindelijk kan de vaakst uitgevoerde functie van deze stap worden opgesplitst in twee afzonderlijke delen. Omdat de functie zo vaak wordt opgeroepen was het niet aangewezen om bij elke nieuwe functieoproep een nieuwe SPU-draad aan te maken. Er werd daarom gekozen voor twee statisch gecreëerde SPU-draden die door communicatie met de PPU de berekeningen uitvoeren op de juiste data. Ondanks een grote versnelling tegenover dynamische draadcreatie, zorgt de extra communicatie voor een vertraging. De progressieve alignatie kon op deze manier dus niet worden versneld. Als algemeen besluit kan men stellen dat de Cell -architectuur dankzij tal van innovatieve vernieuwingen een zeer grote prestatie kan bekomen. Om een programma te bekomen dat daadwerkelijk optimaal gebruik maakt van de Cell, zijn er veel bijkomende optimalisatiestappen nodig. Niet alle programma’s zijn echter even eenvoudig aan te passen. Tegenover de grote prestatiewinst staat dus een grote complexiteit. 71 Bijlage A Bijkomende resultaten A.1 Gedetailleerde resultaten matrixvermenigvuldiging Hierna volgen de gedetailleerde resultaten van de matrixvermenigvuldiging besproken in hoofdstuk 3. De versnelling wordt steeds berekend tegenover de overeenkomstige uitvoering op de PPU. 72 Tabel A.1: Resultaten matrixvermenigvuldiging origineel algoritme. Dimensie PPU/SPU Draden DMA[%] Berekeningen[%] Totaal[ms] Versnelling 128 PPU 1 N/A N/A 16 1 128 SPU 1 45,65 54,35 37 0,44 128 SPU 2 45,89 54,10 21 0,78 128 SPU 4 46,75 53,25 15 1,10 128 SPU 8 48,56 51,44 16 1,02 256 PPU 1 N/A N/A 142 1 256 SPU 1 35,54 64,46 235 0,61 256 SPU 2 35,67 64,33 120 1,19 256 SPU 4 36,67 63,33 65 2,19 256 SPU 8 37,92 62,08 42 3,41 512 PPU 1 N/A N/A 1168 1 512 SPU 1 29,28 70,72 1679 0,70 512 SPU 2 31,11 68,89 863 1,35 512 SPU 4 31,48 68,52 438 2,67 512 SPU 8 38,18 61,82 248 4,70 1024 PPU 1 N/A N/A 9587 1 1024 SPU 1 27,31 72,69 12962 0,74 1024 SPU 2 28,37 71,63 6573 1,46 1024 SPU 4 29,76 70,24 3350 2,86 1024 SPU 8 39,72 60,28 1941 4,94 73 Tabel A.2: Resultaten matrixvermenigvuldiging gebufferd algoritme. Dimensie PPU/SPU Draden DMA[%] Berekeningen[%] Totaal[ms] Versnelling 128 PPU 1 N/A N/A 16 1 128 SPU 1 3,01 96,99 24 0,67 128 SPU 2 3,00 97,00 15 1,12 128 SPU 4 3,04 96,96 12 1,41 128 SPU 8 3,07 96,93 14 1,21 256 PPU 1 N/A N/A 142 1 256 SPU 1 1,41 98,59 172 0,83 256 SPU 2 1,41 98,59 88 1,61 256 SPU 4 1,43 98,57 48 2,94 256 SPU 8 1,44 98,56 32 4,42 512 PPU 1 N/A N/A 1168 1 512 SPU 1 0,68 99,32 1330 0,88 512 SPU 2 0,68 99,32 667 1,75 512 SPU 4 0,68 99,32 338 3,45 512 SPU 8 0,69 99,31 178 6,58 1024 PPU 1 N/A N/A 9587 1 1024 SPU 1 0,34 99,66 10514 0,91 1024 SPU 2 0,34 99,66 5260 1,82 1024 SPU 4 0,34 99,66 2634 3,64 1024 SPU 8 0,34 99,66 1326 7,23 74 Tabel A.3: Resultaten matrixvermenigvuldiging algoritme met lusontvouwing. Dimensie PPU/SPU Draden DMA[%] Berekeningen[%] Totaal[ms] Versnelling 128 PPU 1 N/A N/A 16 1 128 SPU 1 7,84 92,16 11 1,43 128 SPU 2 7,81 92,19 8 2,00 128 SPU 4 8,14 91,86 9 1,81 128 SPU 8 8,20 91,80 13 1,28 256 PPU 1 N/A N/A 142 1 256 SPU 1 3,81 96,19 68 2,08 256 SPU 2 3,83 96,17 37 3,88 256 SPU 4 3,86 96,14 23 6,26 256 SPU 8 4,33 95,67 20 6,96 512 PPU 1 N/A N/A 1168 1 512 SPU 1 1,88 98,12 503 2,32 512 SPU 2 1,90 98,10 254 4,60 512 SPU 4 1,93 98,07 131 8,89 512 SPU 8 2,05 97,95 74 15,68 1024 PPU 1 N/A N/A 9587 1 1024 SPU 1 0,95 99,05 3895 2,46 1024 SPU 2 0,95 99,05 1950 4,92 1024 SPU 4 0,96 99,04 979 9,79 1024 SPU 8 0,99 99,01 499 19,23 75 A.2 Gedetailleerde resultaten empirische analyse Clustal W De resultaten van de empirische analyse van Clustal W met variërend aantal sequenties N en lengte van de sequenties L, zijn hieronder weergegeven. Hierbij is de procentuele uitvoeringstijd van de drie fasen weergegeven (PW: paarsgewijze alignatie, GT: boomstructuur opstellen, PA: paarsgewijze alignatie). 76 Tabel A.4: Resultaten empirische analyse Clustal W. N L PW[%] GT[%] PA[%] 10 10 7,72 12,00 80,28 10 50 18,58 2,34 79,09 10 100 20,83 0,81 78,36 10 500 23,60 0,04 76,35 10 1000 23,91 0,02 76,07 50 10 23,51 5,30 71,19 50 50 46,87 0,70 52,43 50 100 55,36 0,24 44,40 50 500 59,92 0,02 40,06 50 1000 60,33 0,00 39,67 100 10 31,04 7,17 61,79 100 50 61,25 1,02 37,72 100 100 67,47 0,33 32,20 100 500 73,58 0,02 26,40 100 1000 74,28 0,00 25,72 500 10 22,24 46,23 31,52 500 50 74,15 9,74 16,10 500 100 80,80 3,00 16,20 500 500 89,62 0,17 10,21 500 1000 91,45 0,05 8,50 1000 10 14,52 63,60 21,88 1000 50 66,83 21,40 11,77 1000 100 80,98 7,31 11,71 1000 500 91,42 0,37 8,21 1000 1000 94,06 0,10 5,84 77 Bibliografie [1] MPI Documents. “http://www.mpi-forum.org/docs/”. [2] Uniprotkb/swiss-prot protein knowledgebase release 52.5 statistics. “http://www. expasy.ch/sprot/relnotes/relstat.html”. [3] Sun microsystems introduces breakthrough ultrasparc t1 processor with coolthreads technology, setting a new industry standard for performance, innovation. “”http:// www.sun.com/smi/Press/sunflash/2005-11/sunflash.20051114.2.xml”’, 2005. [4] D.A. Bader and V. Sachdeva. An open benchmark suite for evaluating computer architecture on bioinformatics and life science applications. SPEC Benchmark Workshop, 2006. [5] R. Barua, D. Kranz, and A. Agarwal. Communication-minimal partitioning of parallel loops and data arrays for cache-coherent distributed-memory multiprocessors. In Languages and Compilers for Parallel Computing, pages 350–368, 1996. [6] K. Chaichoompu, S. Kittitornkun, and S. Tongsima. MT-ClustalW: multithreading multiple sequence alignment. In 20th International Parallel and Distributed Processing Symposium, 2006. [7] J. Cheetham, F. Dehne, S. Pitre, A. Rau-Chaplin, and P.J. Taillon. Parallel Clustal W for PC Clusters. [8] J. Choi, J. J. Dongarra, S. Ostrouchov, A. Petitet, D. Walker, and R. C. Whaley. Lapack working note 100: a proposal for a set of parallel basic linear algebra subprograms. Technical Report, Computer Science Department, University of Tennessee, Knoxville, 1995. [9] J. Choi, J. J. Dongarra, and D. Walker. Pumma: Parallel universal matrix multiplication algorithms on distributed memory concurrent computers. Concurrency: Practice and Experience, 6(7), 1994. 78 [10] D. Coppersmith and S. Winograd. Matrix multiplication via arithmetic progressions. Journal of Symbolic Computation, 9, 1990. [11] M.O. Dayhoff, R.M. Schwartz, and B.C. Orcutt. Atlas of Protein Sequence and Structure. NBRF, 1978. [12] R. C. Edgar. Muscle: a multiple sequence alignment method with reduced time and space complexity. BMC Bioinformatics, 5(1), August 2004. [13] D. Feng and R.F. Doolittle. Progressive sequence alignment as a prerequisite to correct phylogenetic trees. Journal of Molecular Biology, 60:351–360, 1987. [14] D. Hackenberg. Fast matrix multiplication on cell (smp) systems. “http: //tu-dresden.de/die_tu_dresden/zentrale_einrichtungen/zih/forschung/ architektur_und_leistungsanalyse_von_hochleistungsrechnern/cell/”, 2007. [15] A. Harwood. Parallel algorithms: Embarrassingly parallel. “http://www.cs.mu.oz. au/498/notes/node40.html”, 2003. [16] J. Held, J. Bautista, and S. Koehl. From a few cores to many: A tera-scale computing research overview. White paper, Intel, 2006. [17] S. Henikoff and J.G. Henikoff. Amino acid substitution matrices from protein blocks. In Proceedings of the National Academy of Sciences, number 89, pages 10915–10919, 1992. [18] J. Hennessy and D. Patterson. Computer architecture: a quantitative approach. Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 2002. [19] IBM. Cell Broadband Engine programming handbook version 1.0, april 2006. [20] IBM. Cell Broadband Engine programming tutorial version 1.1, june 2006. [21] IBM. Cell Broadband Engine SDK Libraries Overview and Users Guide version 1.1, 2006. [22] IBM. Synergistic Processor Unit Instruction Set Architecture version 1.1, jan 2006. [23] IBM. Cell Broadband Engine Registers version 1.5, april 2007. [24] W. Just. Computational complexity of multiple sequence alignment with sp-score. Journal of Computational Biology, 8(6):615–623, 2001. 79 [25] W. Kahan. IEEE Standard 754 for Binary Floating-Point Arithmetic. Lecture notes University of California, Berkeley, 1997. [26] M. Kistler, M. Perrone, and F. Petrini. Cell multiprocessor communication network: built for speed. IEEE Micro, may-june 2006. [27] D. Mikhailov, H. Cofer, and R. Gomperts. Performance optimizations of Clustal W: Parallel Clustal W, HT Clustal, and MULTICLUSTAL. Technical report, SGI Life and Chemical Sciences. Technical report, SGI, 1999. [28] G. Moore. Cramming more components onto integrated circuits. Electronics, 38(8):323–339, april 1965. [29] D. Patterson and K. Yelick. Bridiging the processor memory gap. 2002. [30] Mihai Preda. Implementing the lgamma() function in java. “http://blog.javia. org/?p=26”. [31] N. Saitou and M. Nei. The neighbor-joining method: A new method for reconstructing phylogenetic trees. Molecular Biology and Evolution, 4:406–425, 1987. [32] T.F. Smith and M.S. Waterman. Identification of common molecular subsequences. Journal of Molecular Biology, 147:195–197, 1981. [33] V. Strassen. Gaussian elimination is not optimal. Numerische Mathematik, 13, 1969. [34] J. D. Thompson, D. G. Higgins, and T. J. Gibson. CLUSTAL W: improving the sensitivity of progressive multiple sequence alignment through sequence weighting, position-specific gap penalties and weight matrix choice. Nucleic Acids Res, 22(22):4673–4680, November 1994. [35] M. Wolfe. More iteration space tiling. In Supercomputing ’89: Proceedings of the 1989 ACM/IEEE conference on Supercomputing, pages 655–664. ACM Press, 1989. [36] Wm. A. Wulf and Sally A. McKee. Hitting the memory wall: Implications of the obvious. Computer Architecture News, 23(1):20–24, 1995. [37] J. Xue. Loop tiling for parallelism. Kluwer Academic Publishers, 2000. 80