Köln, den 30. August 2014 Studiengang Informationsverarbeitung Sommersemester 2014 Sprachliche Informationsverarbeitung Hauptseminar: „ Angewandte Linguistische Datenverarbeitung“ bei Prof. Dr. Jürgen Rolshoven Parallelisierung des Levenshtein Algorithmus vorgelegt von: Alena Tabea Geduldig Inhalt 1 Einleitung ................................................................................................................................................. 1 2 Die Editierdistanz .................................................................................................................................. 3 3 Der Levenshtein Algorithmus ............................................................................................................ 5 3.1 Das Prinzip der Dynamischen Programmierung .......................................................................... 5 3.1.1 Zerlegung des Problems in Teilprobleme………………………………………….5 3.1.2 Lösung der kleinsten Teilprobleme………………………………………………...6 3.1.3 Schrittweise Lösung des Gesamtproblems…………………………………………7 3 Datenparalleles Rechnen auf der GPU ............................................................................................ 9 3.1 OpenCL und Aparapi ..................................................................................................................... 10 4 Datenparallelisierung des Levenshtein Algorithmus................................................................. 11 4.1 Anwendungsvariante 1: Editierdistanz vollständiger Dokumente ........................................... 11 4.1.1 Parallelisierungsstrategie………………………………………………………….12 4.2 Anwendungsvariante 2: Editierdistanz auf Wortebene.............................................................. 13 4.2.1 Parallelisierungsstrategie………………………………………………………….14 5 Laufzeitvergleich .................................................................................................................................. 16 5.1 Laufzeitvergleich der dokumentbasierten Anwendungsvariante .............................................. 16 5.2 Laufzeitvergleich der wortbasierten Anwendungsvariante........................................................ 17 5 Fazit.......................................................................................................................................................... 21 6 Literaturverzeichnis ............................................................................................................................. 23 1 Einleitung Die vorliegende Hausarbeit baut auf das im Wintersemester 2011/2012 gehaltene Referat zum Thema Stringvergleich in der Computerlinguistik und Bioinformatik auf. Dort wurde ein in beiden Wissenschaftsbereichen angewendetes Maß zum Vergleich von Zeichenketten vorgestellt: Die Editierdistanz. Die Editierdistanz ist ein Maß für die Ähnlichkeit beziehungsweise Unähnlichkeit zweier Strings. Sie fällt damit in den Bereich des approximativen Matchens, also den Teilbereich der Stringverarbeitung, der die Ermittlung ähnlicher, aber nicht notwendigerweise identischer Zeichenketten zum Ziel hat. Das Ziel des approximativen Matchens lässt sich wie folgt charakterisieren: approximate matching problem Erkenne, finde und toleriere Zeichenketten, die einer gegebenen Zeichenkette weitgehend ähnlich sind und zwar unabhängig davon, worin die Abweichung von der Gleichheit besteht und wo sie im String lokalisiert ist. (vgl. Wegst 2006) Diese Zielsetzung setzt ein adäquates Maß für die Bewertung der Ähnlichkeit oder Unähnlichkeit von Strings voraus. In welchem Sinne adäquat in diesem Zusammenhang zu verstehen ist, wird zu Beginn dieser Arbeit erläutert und schließlich die Editierdistanz als ein geeignetes Maß für den Stringvergleich vorgestellt. Der Schwerpunkt dieser Arbeit liegt auf dem Algorithmus mit dem die Editierdistanz zweier Strings maschinell berechnet wird. Der Levenshtein Algorithmus liefert zu je zwei Zeichenketten die zugehörige Editierdistanz. In der Praxis wird der Algorithmus oft auf sehr großen Datenmengen angewendet. Beispielsweise in der Bioinformatik, um eine Gensequenz mit allen Gensequenzen einer Datenbank zu vergleichen. In der Computerlinguistik wird der Algorithmus unter anderem zur automatischen Rechtschreibkorrektur herangezogen. Bei einer manuellen Texteingabe, beispielsweise in eine Suchmaschine, kann die Editierdistanz dazu verwendet werden, dass neben identischen Treffern auch grammatisch oder phonetisch ähnliche Wörter berücksichtigt werden. In diesem Anwendungsfall muss der Levenshtein Algorithmus sehr häufig hintereinander ausgeführt werden, jeweils aber nur auf Wortebene, also auf relativ kurzen Zeichensequenzen. Neben dem Vergleich einzelner Wörter kann die Editierdistanz auch zum Vergleich vollständiger Dokumente, beispielsweise zur Plagiatserkennung, verwendet werden. In diesem Fall wird der Levenshtein Algorithmus nur einmal ausgeführt, jedoch auf erheblich längeren Zeichenketten. Beide Anwendungsvarianten werden mit wachsender Datenmenge sehr laufzeitintensiv. Im ersten Fall liegt dies weniger an der Laufzeit einer einzelnen 1 Algorithmusausführung, als an der Menge der Wiederholungen. Im zweiten Fall treibt die Länge der Dokumente die quadratische Laufzeit des Algorithmus in die Höhe. In dieser Hausarbeit wird ein Versuch unternommen, der starken Laufzeitentwicklung des Levenshtein Algorithmus durch die Anwendung eines GPU-gestützten Programmierverfahrens entgegenzuwirken. Dieses Programmierverfahren hat sich innerhalb der letzten Jahre unter dem Namen General Purpose Computation on Graphics Processing Units (GPGPU) etabliert. Neben der CPU, der zentralen Recheneinheit eines Computers, wird auch die GPU, die zentrale Recheneinheit der Grafikkarte, zur Ausführung von Befehlen verwendet. Durch eine geschickte Ausnutzung ihrer hardwarespezifischen Besonderheiten soll die Grafikkarte zu einer besseren Performance laufzeitintensiver Anwendungen beitragen. Das Ziel dieser Arbeit ist es, ein Programm zu implementieren, dass die Ausführung beider der oben skizzierte Anwendungsvarianten des Levenshtein-Algorithmus ermöglicht. Hierfür wird einmal eine ‚herkömmliche‘ Java-Applikation entwickelt, und einmal die GPGPU unterstützende Programmiersprache OpenCL verwendet. Durch einen Laufzeitvergleich dieser funktionsgleichen Programme, soll die zentrale Frage beantwortet werden, ob, und welche Anwendung des Levenshtein Algorithmus durch GPU-Programmierung beschleunigt werden kann. 2 2 Die Editierdistanz Das Ziel des approximativen Matchens ist die Erkennung zueinander ähnlicher Zeichenketten. Diese Aufgabe erfordert ein Maß, mit dem die Ähnlichkeit beziehungsweise Unähnlichkeit zweier Zeichenfolgen eindeutig bestimmt werden kann. Wie könnte also ein adäquates Maß für StringÄhnlichkeit aussehen? Zu je zwei Zeichenketten sollte ein Ähnlichkeitsmaß eine eindeutige Maßzahl liefern, die den Unterschiedlichkeitsgrad der beiden Strings ausdrückt. Gesucht ist folglich eine Abbildung, die ein Paar von Strings <S1, S2> auf eine Zahl abbildet. Im Falle eines Distanzmaßes sollten zueinander ähnlichere Strings auf eine niedrigere Zahl abgebildet werden, als deutlich verschiedenere Strings. (Bei einem Ähnlichkeitsmaß gilt die genau umgekehrte Anforderung). Eine weitere Anforderung an ein adäquates Distanzmaß ist, dass die Reihenfolge der beiden Strings keine Auswirkung auf den Distanzwert haben sollte. Es sollte also keine Rolle spielen, ob das Paar <S1, S2>, oder <S2, S1> bewertet wird, da die Ähnlichkeit von Strings unabhängig von ihrer Reihenfolge ist. Als letzte Anforderung sollte ein Distanzmaß identische Strings auf den Wert 0 abbilden. Die Editierdistanz ist ein Stringmaß, das genau diese Eigenschaften erfüllt. Sie misst die Operationen, die notwendig sind, um den ersten String in den zweiten String zu überführen. Zugelassen sind hierbei genau drei Operationen: 1. Das Löschen eines Zeichens aus S1 (delete) 2. Das Einfügen eines Zeichens in S1 (insert) 3. Der Austausch eines Zeichens von S1in ein Zeichen von S2 (replace) Eine Folge von Operationen, die einen Strings S1 in einen anderen String S2 überführt, nennt man Editskript von S1 und S2. Hieraus ergibt sich auch die exakte Definition der Editierdistanz: Definition Die Editierdistanz des Stringpaares <S1,S2> entspricht genau der Länge des kürzesten Editskriptes von S1 und S2. (vgl. Gusfield 1997: 216) Stimmen zwei Zeichen überein, so dass keine Operation notwendig ist, enthält das Editskript die Anweisung match (engl. Übereinstimmung), welche jedoch nicht als Operation gezählt wird. Zur Veranschaulichung ist nachfolgend ein Editskript für das Stringpaar <darling, airline> aufgeführt: Edarling,airline = d,m,i,m,m,m,r 3 Folgt man dieser Operationsfolge nacheinander angewendet auf die einzelnen Zeichen des Wortes darling, erhält man den neuen String airline. Edarling,airline ist folglich ein gültiges Editskript für dieses Stringpaar. Es hat die Länge 3, da es drei der oben genannten Operationen enthält. Da es kein noch kürzeres Editskript gibt, ist drei die Editierdistanz der beiden Strings. Der Vermerk auf ein kürzestes Editskript in der Definition der Editierdistanz weist darauf hin, dass gültige Editskripte nicht eindeutig und von unterschiedlicher Länge sein können. Mit der Länge des kürzesten Editskriptes ist die Editierdistanz jedoch eindeutig bestimmt und erfüllt somit die erste Bedingung (Eindeutigkeit) an ein adäquates Distanzmaß. Im Folgenden wird gezeigt, dass die Editierdistanz auch die weitere Anforderung (Unabhängigkeit der StringReigenfolge) erfüllt: Um S1 in S2 zu überführen ist sicherlich ein anderes Editskript nötig, als um S2 in S1 zu überführen. Beide sollten jedoch dieselbe Länge haben um die Anforderung zu erfüllen. Sei also E1,2 ein kürzestes Editskript für <S1,S2> der Länge n. Dann entspricht n der Editierdistanz von <S1,S2>. Nun erhält man durch den Austausch der Operationen delete und insert auch ein Editskript für das Paar <S2,S1> das ebenfalls die Länge n hat. Gäbe es ein noch kürzeres Editskript für das Paar <S2,S1>, etwa der Länge n-i, so ließe sich auch daraus, durch den Austausch der Operationen delete und insert ein Editskript für <S1,S2> erzeugen. Dieses hätte jedoch auch die Länge n-i, was im Widerspruch zur Voraussetzung steht, das n bereits die Länge des kürzesten Editskriptes war. Folglich muss n auch bereits die Länge des kürzesten Editskriptes von <S2,S1> sein, womit die Anforderung erfüllt ist. Dass auch die dritte Anforderung (Abbildung identischer Strings auf den Wert 0) erfüllt ist, geht unmittelbar aus der Definition hervor. Die Editierdistanz ist somit ein adäquates Distanzmaß für die Ähnlichkeit von Zeichenketten. Bisher wurde jedoch nur die Definition dieses Maßes erläutert, nicht aber wie die Editierdistanz maschinell berechnet werden kann. Mit dieser Frage befasst sich das folgende Kapitel. 4 3 Der Levenshtein Algorithmus Der Levenshtein Algorithmus ist benannt nach dem russischen Wissenschaftler Wladimir Lewenstein, der die Editierdistanz im Jahr 1965 einführte. Zu je zwei Zeichenketten liefert der Levenshtein-Algorithmus die zugehörige Editierdistanz. Er basiert auf dem Prinzip der dynamischen Programmierung und liefert bei Bedarf nicht nur die Editierdistanz, sondern auch alle möglichen Editskripte zweier Strings. 3.1 Das Prinzip der Dynamischen Programmierung Dynamische Programmierung ist eine algorithmische Methode zur Lösung von Optimierungsproblemen. Sie kann dann erfolgreich eingesetzt werden, wenn das zu lösende Problem in mehrere äquivalente Teilprobleme gegliedert werden kann. Löst man zunächst die kleinsten Teilprobleme optimal, so können diese zu den Lösungen der jeweils größeren Teilprobleme erweitert werden. Indem man alle Teillösungen in einer Tabelle speichert, wird dieses Prinzip fortgeführt, bis schließlich die Lösung des Gesamtproblems erreicht wird. (zur Dynamischen Programmierung siehe auch Cormen 2013: Kap. 15) Dynamische Programmierung lässt sich folglich in drei Schritte zerlegen: 1. Zerlegung des Gesamtproblems in Teilprobleme 2. Lösung der kleinsten Teilprobleme 3. Sukzessives Lösen der jeweils nächstgrößeren Teilprobleme Diese drei Schritte werden nun, angewendet auf die Editierdistanz, erläutert. 3.1.1 Zerlegung des Problems in Teilprobleme Der erste Schritt zur dynamischen Lösung eines Optimierungsproblems ist die Zerlegung in mehrere Teilprobleme. Im Fall der Editierdistanz bedeutet dies, dass die beidem Strings in alle ihre möglichen Präfixe {S1(0..i)}i=0-n und {S2(0…j)}j=0-m zerlegt werden. Als ein Teilproblem ist somit die Berechnung der Editierdistanz eines Präfixpaares zu verstehen. Des Weiteren wird eine Matrix D angelegt, in der die Zwischenlösungen, also die Editierdistanzen aller Präfixpaare gespeichert werden. Jede Teillösung hat dabei einen festen Platz in der Matrix. Abbildung 3.1 veranschaulicht diesen Vorgang für das Stringpaar <brot,not>. 5 Abbildung 3.1: Distanzmatrix trix zur Speicherung von Teillösungen zur Berechnun ung der Editierdistanz am Beispiel des Stringpaares <bro brot,not>. Jeder Matrixeintrag steht für die Editierdis distanz eines Präfixpaares. 3.1.2 Lösung der kleinsten T Teilprobleme Die kleinsten Teilproblemee ssind im Falle des Levenshtein-Algorithmus die Distanzberechnungen der Präfixpaare, bei denen mindestens m einer der beiden Präfixe dem leeren ren String entspricht. Die Lösungen sind trivial undd o ohne Rechenaufwand bestimmbar: Die Dista istanz eines Strings zum leeren String entspricht gen enau seiner Länge. Jedes Zeichen muss durch ch eine delete-Operation gelöscht werden. Abbildun ung 3.2 zeigt die Distanzmatrix nach der Ausführung A des ersten Teilschritts: Abbildung 3.2: 3. Distanzmatrix nach Lösung der kleinsten Teilpr lprobleme. 6 3.1.3 Schrittweise Lösung des Gesamtproblems Das Ziel dieses Schritts ist es, die Matrix nun sukzessive mit den Editierdistanzen der übrigen Präfixpaare zu füllen, so dass der Matrixeintrag D[i][j] der Distanz des Stringpaares <S1(1..i), S2(1..j)> entspricht. Dies erfolgt Zeilenweise (oder Spaltenweise) damit auf die Lösungen der jeweils kleineren Präfixpaare aufgebaut werden kann. Bei der Berechnung des Eintrages D[i][j] sind die folgenden Editskripte somit bereits bekannt: (a) Das Editskript Ei-1,j von <S1(1..i-1), S2(1…j)> (Eintrag D[i-1][j ]) (b) Das Editskript Ei,j-1 von <S1(1..i),S2(1..j-1)> (Eintrag D[i ][j-1]) (c) Das Editskript Ei-1,j-1 von <S1(1..i-1), S2(1..j-1)> (Eintrag D[i-1][j-1]) Wie nun gezeigt wird, benötigt das gesuchte Editskript Ei,j höchstens eine Operation mehr, als das kürzeste der oben aufgezählten Editskripte (a) – (c): (a) Wenn Ei-1,j eine Vorschrift ist, um das Präfix S1(1..i-1) in das Präfix S2(1..j) zu überführen, dann ist Ei-1,j+ delete auch eine Vorschrift um S1(1..i-1,i) in S2(1..j) zu überführen. delete sorgt dann genau für die Entfernung des überflüssigen i-ten Zeichens aus S1. (b) Äquivalent dazu erfüllt auch Ei,j-1+ insert die Anforderungen eines Editskripts für S1(1..i) in S2(1..j). Wenn Ei,j-1das Präfix S1(1..i) in das Präfix S2(1..j-1) überführt, dann erhält man durch ein anschließendes Hinzufügen des j-ten Zeichens von S2 aus dem Präfix S1(1..i) das Präfix S2(1…j-1,j) (c) Um Ei-1,j-1zu dem gesuchten Editskript zu erweitern, müssen 2 Fälle unterschieden werden: 1. Das i-te Zeichen von S1 und das j-te Zeichen von S2 sind identisch: In diesem Fall ist keine weitere Operation mehr nötig. Das gesuchte Editskript lautet Ei-1,j-1 + match 2. Das i-te Zeichen von S1 und das j-te Zeichen von S2 sind nicht identisch: Das gesuchte Editskript lautet in diesem Fall Ei-1,j-1+replace. Das i-te Zeichen von S1 wird durch das j-te Zeichen von S2 ersetzt. Aus diesen Überlegungen ergibt sich folgende Rekursionsgleichung zur zeilenweisen Berechnung der fehlenden Matrixeinträge: 7 Abbildung 3.3 zeigt die volls ollständig berechnete Distanzmatrix mit Hilfe de der Rekursionsgleichung für das Paar <brot, not>. Die Di gesuchte Editierdistanz befindet sich im letzt etzten Matrixfeld D[4][3]. Abbildung 3.3: Voll ollständig ausgefüllte Distanzmatrix für das Stringpaa paar <brot, not> d Matrix auch die zugehörigen Editskripte.. Hierfür H muss ein BackBei Bedarf erhält man auss der Trace durch die Matrix beginnend beg beim letzten Eintrag durchgeführtt w werden. (Vgl. Gusfield 1997: 221) Nachdem nun die Funktio tionalität des Levenshtein-Algorithmus erläute utert wurde, werden im folgenden Kapitel die Möglichkeiten M seiner Parallelisierung untersu rsucht. Sollte sich der Algorithmus, in parallel ausf usführbare Berechnungen zerlegen lassen, so kö können diese durch die Ausführung auf einem Graf rafikprozessor (GPU) umgesetzt werden. 8 3 Datenparalleles Rechnen auf der GPU Die besondere Architektur moderner Grafikkarten ermöglicht die datenparallele Ausführung von Befehlen. Dies bedeutet, dass auf einer GPU derselbe Befehlssatz zeitgleich auf unterschiedlichen Daten ausgeführt werden kann, statt nacheinander wie auf einem CPU-Kern. Verglichen mit einer sequentiellen Ausführung, kann durch daten-paralleles Rechnen also sehr viel Rechenzeit eingespart werden. Voraussetzung ist natürlich, dass ein Algorithmus auf die datenparallele Arbeitsweise übertragbar ist ohne in seiner Funktionalität verfälscht zu werden. Die Gründe für die parallele Rechenfähigkeit von Grafikprozessoren liegen in ihrer vielkernigen Architektur. Während eine herkömmliche CPU in etwa 4 – 8 Kerne besitzt, verfügt eine GPU über mehrere hundert Kerne. Im Unterschied zu den Kernen einer CPU, haben GPU-Kerne keinen eigenen Befehlssatz, sondern teilen sich einen Befehlssatz und Cache mit vielen weiteren Kernen. Dies hat die folgenden Konsequenzen: Auf Grund der sehr hohen Zahl an Kernen, ist auf der GPU hochgradig paralleles Rechnen möglich, denn jeder Kern kann zeitgleich auf unterschiedlichen Daten operieren. Da sich die Kerne ihren Befehlssatz jedoch mit anderen Kernen teilen müssen, können sie zeitgleich auch nur dieselben Befehle ausführen. Dies ist auch der Grund warum nur datenparalleles und kein taskparalleles1 Rechnen möglich ist. Zur Kommunikation mit der Grafikkarte eines Computers sind inzwischen Schnittstellen entwickelt worden, die den Austausch von Befehlen und Daten zwischen CPU und GPU ermöglichen. Zu den populärsten gehören die Schnittstellen CUDA (Compute Unified Device Language)2, welche allein der Programmierung von Nvidia-Grafikchips vorbehalten ist und die plattformunabhängige Schnittstelle OpenCL (Open Compute Language)3. OpenCL geht aus einer Zusammenarbeit der Firmen Apple, Nvidia, AMD, IBM und Intel hervor und wurde 2008 in einer ersten Version vorgestellt. Aus Gründen der besseren Austauschbarkeit und Reproduzierbarkeit, wurde für die Implementierungen zu dieser Arbeit die Schnittstelle OpenCL zu Grunde gelegt. Darüber hinaus wurde Aparapi4 verwendet. Dies ist eine offene Bibliothek, die Java-Bytecode während der Laufzeit in OpenCL-Code übersetzt. 3.1 OpenCL und Aparapi Beim taskparallelen Rechnen werden zeitgleich unterschiedliche, meist umfangreiche Aufgaben ausgeführt. http://www.nvidia.com/object/cuda_home_new.html, zuletzt aufgerufen am 30.08.2014. 3 http://khronos.org/opencl/, zuletzt aufgerufen am 30.08.2014. 4 http://code.google.com/p/aparapi/, zuletzt aufgerufen am 30. 08. 2014. 1 2 9 Eine OpenCL-Anwendung lässt sich unterteilen in ein Host-Programm und ein KernelProgramm. Das Host-Programm läuft auf der CPU und regelt von dort aus den Programmfluss. Von hier aus wird die Schnittstelle OpenCL initiiert und die Kommunikation mit der GPU durchgeführt. Ein Kernel ist der Teil der Anwendung, der für die Ausführung auf der GPU bestimmt ist. Wird ein Kernel zur parallelen Ausführung an die GPU gesendet, wird ein Indexraum erstellt. Jedem Punkt des Indexraumes wird dann eine Instanz des Kernels zugeordnet. Jede Instanz ist zur Ausführung auf einem anderen GPU Kern bestimmt. Durch den ihr zugewiesenen Punkt im Indexraum, ihre globale ID sind die einzelnen Kernelinstanzen eindeutig identifizierbar. Jede Kernelinstanz wird zeitgleich von einem der GPU Kerne ausgeführt. Alle enthalten dieselben Befehle, die jedoch durch eine Bezugnahme auf die globale ID variieren können. Eine Kernelinstanz ist somit vergleichbar mit einem Schleifendurchlauf einer for-Schleife. Auch hier werden in jedem Durchlauf dieselben Befehle ausgeführt, die durch eine Bezugnahme auf den Laufindex variieren. Der Unterschied liegt jedoch darin, dass die Kernelinstanzen parallel ausgeführt werden, während eine for-Schleife sequentiell abgearbeitet wird. Kernelprogramme müssen normalerweise in einer OpenCL-spezifischen Sprache geschrieben und als Textdatei oder in String-Form separat abgespeichert werden. Die Aparapi-Bibliothek stellt jedoch die Klasse Kernel bereit, welche direkt in das Programm eingebunden werden kann. Die Klasse Kernel erfordert die Implementation der Methode run(), welche aus dem HostProgramm aufgerufen werden kann. In dieser Methode können die auf der GPU auszuführenden Befehle in Java-Code definiert werden. Über die Befehle put() und get() kann das Host-Programm außerdem den Hin- und Rücktransfer von Daten zwischen CPU- und GPUSpeicher regeln. 10 4 Datenparallelisierung des Levenshtein Algorithmus Im Rahmen dieser Arbeit wurden zwei verschiedene Anwendungsmöglichkeiten des Levenshtein-Algorithmus implementiert. Die erste Variante berechnet die Editierdistanz von jeweils zwei vollständigen Dokumenten. Die zweite Variante berechnet die Editierdistanz eines einzelnen Wortes zu jedem Wort in einer gegebenen Datenbank. Beide Versionen wurden einmal auf herkömmliche sequentielle Weise implementiert und einmal als daten-parallele OpenCLAnwendung. 4.1 Anwendungsvariante 1: Editierdistanz vollständiger Dokumente Diese Levenshtein-Variante ist für Anwendungen konzipiert, bei denen der LevenshteinAlgorithmus nur einmalig und auf sehr langen Zeichenketten ausgeführt wird, also beispielsweise zur Plagiatsanalyse. Die jeweiligen Implementierungen befinden sich im Package documentBasedLevenshtein. Abbildung 4.1 zeigt das zugehörige Klassendiagramm. Die abstrakte Klasse AbstractDocBasedLevenshtein definiert die Methode setDocuments(), zur Übergabe der Dokumente und die abstrakte Methode getDistance(). Diese Methode wird in der KlasseSequentialDocBasedLevenshtein auf herkömmliche Weise, so wie in Kapitel 2 beschrieben, realisiert. ParallelDocBasedLevenshtein erfüllt dieselbe Funktion, nutzt hierfür jedoch die parallelen Strukturen der Grafikkarte. 11 Abbildung 4.1: Klassendia diagramm für die dokumentbasiere Anwendungsvaria ariante des Levenshtein Algorithmus. 4.1.1 Parallelisierungsstrategi egie Um die Vorteile der Berech chnung auf der GPU nutzen zu können ist zun unächst eine Analyse des Algorithmus hinsichtlich sei seiner Parallelisierbarkeit durchzuführen. Zentr ntrale Fragen ist hierbei, welche Berechnungsschritte itte voneinander unabhängig sind, denn nurr dann d ist eine parallele Ausführung möglich. Die ie meiste Zeit des Algorithmus wird fürr die Berechnung der Matrixeinträge benötigt. Die ie Berechnung eines einzelnen Eintrags geschie hieht zwar in konstanter Zeit, jedoch müssen insg nsgesamt n*m dieser Einträge berechnet werden, w was in einer herkömmlichen sequentielle llen Version eine verschachtelte for-Schleife üb über alle Zeichen beider Dokumente erfordert. Wie Wi im vorherigen Kapitel gezeigt wurde, de, sind die einzelnen Distanzberechnungen jedoch och nicht alle unabhängig voneinander. Bis auf uf die Startlösungen baut jede Teillösung auf den Er Ergebnissen dreier weiterer Teilprobleme auf. uf. Die Berechnung des Eintrags D[i][j] setzt die Lösu ösungen in den Einträgen D[i-1][j], D[i][j-1] und nd D[i-1][j-1] voraus. Alle Matrixeinträge können folgl lglich nicht zur selben Zeit bestimmt werden. en. Dennoch lassen sich einige Berechnungen, diee n nicht aufeinander aufbauen parallel ausführe ren, wie Abbildung 4.2 demonstriert. 12 Abbildung 4.2: Darstellungg dder parallel berechenbaren Matrixeinträge. Jeweils eils die Einträge auf einer Diagonalen sind unabhängig zu zueinander und eine zeitgleiche Berechnung somitt möglich. m Diese Parallelisierungsstrateg tegie wird im Programm wie folgt umgesetzt: ParallelDocBasedLevenshtein Par stellt die Host-Applikation tion dar, die ebenfalls eine Realisierung der d abstrakten Klasse AbstractDocBasedLevenshtein darstellt. da DocBasedLevenshteinKernel enthält das auf der GPU ausführbare Kernelprogramm. Es kann n aaus der Host-Applikation heraus aufgerufen werden. we Der erste Schritt zur Berech chnung der Editierdistanz ist der Transfer derr Dokumente D vom CPUSpeicher an den GPU-Speic eicher. Dort wird außerdem Speicherplatz für ür die nach und nach zu berechnende Distanzmatri atrix alloziert. Mit dem ersten Kernelaufruf au aus der Host-Methode getDistance() werden nun die ie ersten n+m+1 Matrixeinträge, also die Startb rtbedingungen bestimmt. (Die entsprechenden Matrix rixfelder sin in Abbildung 4.2 mit dem Wert 0 markiert.) m Jeder Eintrag wird parallel von einem der er GPU-Kerne ermittelt. Iterativ folgen weitere re Kernelaufrufe aus der Host-Applikation. Mit jedem em Kernelaufruf werden so viele Kernelinstanze nzen gebildet, wie bereits neue Einträge berechenbar bar sind. Ist die Matrix vollständig berechn hnet, befindet sich die Editierdistanz der Dokum umente im Matrixfeld D[n+1][m+1]. Nurr dieser Eintrag wird anschließend zurück an de den CPU-Speicher transferiert und von der er Methode getDistance() zurückgegeben. 4.2 Anwendungsvariantee 22: Editierdistanz auf Wortebene Die zweite Anwendungsvari ariante der Editierdistanz ist für Anwendungen en konzipiert, bei denen ein einzelnes Wort, beziehu ehungsweise eine kurze Zeichensequenz mitt aallen Wörtern in einer großen Datenbank vergliche chen werden soll. Die jeweiligen Implementierun rungen befinden sich im Package wordBasedLevenshtein ein. Abbildung 4.3 zeigt das zugehörige Klassendi ndiagramm. Die abstrakte Klasse abstractWordBasedLeven evenshtein definiert die Methode setDataBase() mitte ittels derer die zu Grunde gelegte Wort-Datenbank gesetzt g werden kann. Außerdem wird die abstrakte Methode getAllDistances() definiert, we welche zu einem gegebenen Wort, sämtliche Editierdistanzen E zu den 13 Wörtern der Datenbank zur urückgeben soll. In der Klasse sequentialWordBase BasedLevenshtein wird diese Methode auf sequentielle W Weise mittels einer Schleife über sämtliche W Wörter der Datenbank realisiert. In jedem Iteration ionsschritt der Schleife wird die Editierdistanzz ddes gegebenen Wortes zum entsprechenden Datenb enbankeintrag berechnet und im Ergebnis-Arrayy festgehalten. f Abbildung 4.3: Klassendiagram ramm für die wortbasierte Anwendungsvariante des es Levenshtein L Algorithmus 4.2.1 Parallelisierungsstrategi egie Was in der sequentiellen n Version nacheinander in den verschiedene nen Schleifeniterationen geschieht, wird in der paral arallelen Variante zeitgleich von einzelnen GPU PU-Kernen abgearbeitet. Jeder GPU-Kern übernimmt mt eine vollständige Ausführung des Levenshtei tein-Algorithmus, jeweils für ein anderes Wort auss dder Datenbank. Folglich müssen so viele Kernelinstanzen K initiiert werden, wie es Wörter in der d zu Grunde gelegten Datenbank gibt. Daa ddie Distanzberechnung zweier Wörter unabhängigg zzu den Distanzberechnungen anderer Wortpaa aare erfolgen kann, ist in dieser Variante nur ein n Kernelaufruf aus der Host-Applikation nötig der sämtliche Kernelinstanzen zeitgleich zur zu Ausführung frei gibt. Zu Beginn der Anwendungg wird w die vollständige Datenbank an den Speich eicher der GPU gesendet. Dieser Arbeitsschritt kostet et zwar z Transferzeit, die in der sequentiellen Ver ersion entfällt, jedoch ist dieser Datentransfer nur einmalig ein notwendig, und muss nicht für jedess einzelne ei Wort, dass mit der Datenbank verglichen en werden soll wiederholt werden. Die zu zusätzliche Zeit durch Datentransfer in der parallele llelen Variante ist somit konstant und kann möglic glicherweise nach einigen 14 Datenbankabfragen wieder ausgeglichen werden. Voraussetzung hierfür ist natürlich, dass die parallele Distanzberechnung tatsächlich schneller ausgeführt werden kann, als die sequentielle Version. Das folgende Kapitel soll diese Frage beantworten und somit herausstellen, ob sich der Einsatz von Grafikprozessoren für Levenshtein-basierte Anwendungen lohnen kann. 15 5 Laufzeitvergleich Für den Performancevergleich zwischen den GPU-basierten Levenshtein-Varianten und den sequentiellen Varianten wurde die Gesamtlaufzeit der Anwendungen unter jeweils gleichen Voraussetzungen gemessen. Da das primäre Interesse dieser Untersuchungen dem Laufzeitverhalten gilt und nicht den berechneten Ergebnissen, spielt der semantische Inhalt der verwendeten Textdokumente in diesem Zusammenhang keine Rolle. Einfluss auf die Laufzeit hat lediglich die Länge der verwendeten Texte beziehungsweise Wörter. 5.1 Laufzeitvergleich der dokumentbasierten Anwendungsvariante Zunächst wurden die beiden Implementationen der ersten Anwendungsvariante des Levenshtein Algorithmus miteinander verglichen. Die Laufzeitmessungen beziehen sich also jeweils auf die Berechnung der Editierdistanz zweier vollständiger Dokumente. Es wurden Messungen mit jeweils variierenden Dokumentlängen gemacht. Die Ergebnisse sind in Abbildung 4.3 time festgehalten. 10000 9000 8000 7000 6000 5000 4000 3000 2000 1000 0 CPU-Time GPU-Time 2024 4048 8096 16192 32384 total number of characters Abbildung 4.3: Laufzeitvergleich des datenparallelen und sequentiellen Levenshtein Algorithmus bei wachsender Dokumentlänge Auf den ersten Blick fallen die deutlich längeren Laufzeiten der GPU-basierten, datenparallelen Algorithmus-Version ins Auge. Sowohl bei insgesamt 2024 Zeichen, als auch bei deutlich längeren Dokumenten mit insgesamt 32384 ist die herkömmliche sequentielle Levenshtein16 Implementierung in der Laufzeit deutlich überlegen. Weitere Tests mit noch längeren Dokumenten waren auf Grund der begrenzten Speicherkapazität des Rechners nicht mehr möglich. Der Versuch, die Berechnung der Editierdistanz von vollständigen Dokumenten durch datenparalleles Rechnen zu beschleunigen muss somit als gescheitert bezeichnet werden. Betrachtet man jedoch statt der absoluten Laufzeiten die relativen Laufzeitunterschiede, so kann zumindest hier mit wachsender Dokumentlänge ein Fortschritt festgestellt werden. Abbildung 4.4 stellt den relativen Performancevorteil der GPU-basierten gegenüber der CPU basierten Implementierung dar. 0,45 0,4 0,35 speedup 0,3 0,25 0,2 0,15 0,1 0,05 0 2024 4048 8096 16192 32384 total number of characters Abbildung 4.4: relativer Performancevorteil der datenparallelen gegenüber der sequentiellen LevenshteinVersion Der Abbildung lässt sich entnehmen, dass die datenparallele Version zwar stets langsamer bleibt als die sequentielle, der relative Laufzeitunterschied nimmt jedoch bei wachsender Dokumentlänge stetig ab. Je länger die verwendeten Dokumente sind, desto näher kommt die datenparallele Laufzeit an die der sequentiellen Version heran. Diese Beobachtung schließt zumindest nicht aus, dass ein Ein- beziehungsweise Überholen der GPU-Laufzeit an die CPULaufzeit bei noch größeren Datenmengen möglich wäre. 5.2 Laufzeitvergleich der wortbasierten Anwendungsvariante Zum Vergleich der datenparallelen Version mit der sequentiellen Version der wortbasierten Levenshtein Anwendung, wurdedas Laufzeitverhalten in zwei unterschiedlichen Testszenarien gemessen. Im ersten Testszenario blieb die Größe der hinterlegten Wortdatenbank konstant bei 3280 Wörtern. Es variierte stattdessen die Zahl der Datenbankabfragen, also die 17 Ausführungshäufigkeit der Methode getAllDistances(). Das Ergebnis der Messungen ist in Abbildung 4.5 festgehalten. 14000 12000 time 10000 8000 6000 cpu 4000 gpu 2000 0 10 20 40 80 160 320 640 1280 Anzahl der Datenbankabfragen Abbildung 4.5: Laufzeitvergleich der datenparallelen und sequentiellen Version bei einer konstanten Datenbankgröße von 3280 Wörtern Die Grafik zeigt, dass die datenparallele Version bereits ab der vierzigsten Datenbankabfrage schneller arbeitet als die herkömmliche sequentielle Version der Levenshtein Anwendung. Offenbar sind vierzig Ausführungen notwendig, um die einmalig zu Beginn benötigte Datentransferzeit zwischen CPU- und GPU-Speicher aufzuholen. Während sich die Laufzeit der sequentiellen Variante bei einer Verdopplung der Datenbankabfragenfast eins zu eins mit verdoppelt, ist bei der datenparallelen Version ein wesentlich langsamerer Anstieg zu beobachten. Der Performancevorteil der datenparallelen gegenüber der sequentiellen Version bleibt somit nicht konstant, sondern wird mit jeder weiteren Anfrage an die Datenbank größer. Bei insgesamt 640 Datenbankabfragen rechnet die GPU-basierte Version etwa fünf Mal schneller als die herkömmliche Version auf der CPU. (vergleiche Abbildung 4.6) 18 6 5 speedup 4 3 2 1 0 10 20 40 80 160 320 640 1280 Anzahl der Datenbankabfragen Abbildung 4.6: relativer Performancevorteil der datenparallelen gegenüber der sequentiellen Version der Wortbasierten Levenshtein Anwendung In einem weiteren Testszenario wurde das Laufzeitverhalten beider Versionen bei gleichbleibend 40 bzw. 160 Datenbankabfragen gemessen. Es variierte stattdessen die Größe der hinterlegten Datenbank. Beginnend bei 205 Wörtern in der Datenbank, wurde die Zahl der Wörter in jedem neuen Durchgang verdoppelt, bis schließlich ein Umfang von 26240 Wörtern erreicht wurde. Die Ergebnisse der Laufzeitmessungen sind in Tabelle 4.7 festgehalten. 40 queries db-size CPU- GPU-time 160 queries speedup CPU-time GPU-time speedup time 820 55 302 0,18211921 157 424 0,37028302 1640 127 324 0,39197531 450 441 1,02040816 3280 389 376 1,03457447 1531 548 2,79379562 6560 1334 767 1,73924381 5308 1818 2,91969197 13120 4885 1256 3,88933121 20499 2439 8,40467405 26240 18879 3004 6,28462051 76598 4390 17,4482916 Tabelle 4.1: Ergebnisse der Laufzeitmessungen bei Veränderung der Datenbankgröße Bei 40 Datenbankabfragen pro Messung lässt sich bereits bei einer Datenbankgröße von 3280 Wörtern ein minimaler Vorsprung der datenparallelen Version ausmachen. Dieser kann mit jeder 19 Vergrößerung der Datenbank weiter ausgebaut werden. Bei 26240 Wörtern in der Datenbank ist die datenparallele Version mehr als sechs Mal schneller als die sequentielle Variante. Bei 160 Datenbankabfragen wird der Performancegewinn durch datenparalleles Rechnen (wie auf Grund der obigen Messungen zu erwarten) noch größer. Bis zu 17 Mal schneller als das sequentielle Programm berechnet der GPU-basierte Algorithmus die Editierdistanzen. 20 5 Fazit In dieser Arbeit wurde die Editierdistanz als Maß für den approximativen Stringvergleich vorgestellt. Sowohl in der Bioinformatik als auch in der Computerlinguistik finden sich mehrere Einsatzgebiete für die Editierdistanz und den zugehörigen Levenshtein Algorithmus.. Insbesondere zwei unterschiedliche Einsatzmöglichkeiten des Levenshtein-Algorithmus wurden in dieser Arbeit näher skizziert: Dies war zum einen die Berechnung der Editierdistanz sehr langer Zeichensequenzen. Als Anwendungsbeispiel diente hierfür der Vergleich längerer Textdokumente beispielsweise als Mittel zur Plagiatsanalyse. Weiter wurde ein Anwendungsszenario skizziert, bei dem die Editierdistanz sehr häufig hintereinander berechnet wird, jeweils aber nur auf sehr kurzen Zeichensequenzen. Als ein Beispiel aus der Bioinformatik wurde hierfür der Vergleich einer kurzen Gensequenz mit jeder Gensequenz aus einer großen Datenbank genannt. Das Hauptanliegen dieser Arbeit war es, ein Programm zu implementieren, dass die Ausführung beider der skizzierte Anwendungsvarianten des Levenshtein-Algorithmus ermöglicht. Auf Grund der sehr hohen (da quadratischen) Laufzeit des Levenshtein Algorithmus, spielte der Laufzeitfaktor hierbei eine entscheidende Rolle. Neben einer herkömmlichen JavaImplementation des Programms wurde daher auch ein funktionsgleiches Programm in der GPUbasierten Sprache OpenCL implementiert. Hierbei stand die zentrale Fragestellung im Vordergrund, ob die verschiedenen Anwendungen des Levenshtein Algorithmus, durch die zusätzliche Verwendung des Grafikprozessors, in ihrer Laufzeit beschleunigt werden können. Diese Fragestellung konnte in Kapitel 5 dieser Arbeit durch den direkten Laufzeitvergleich beider Programme beantwortet werden. Es konnte herausgestellt werden, dass insbesondere das zweite Anwendungsszenario, bei dem die Editierdistanz sehr häufig auf kurzen Zeichensequenzen berechnet wird, von der GPUProgrammierung profitieren kann. Je nach Größe der hinterlegten Datenbank und der Anzahl der Vergleiche, konnten mit dem GPU-basierten Programm bis zu 17 Mal schnellere Laufzeiten erzielt werden. Eine große Datenbank und hohe Anzahl von Vergleichen stelle sich hierbei als besonders begünstigend für den Performancevorsprung der GPU-basierten Version heraus. Bei kleineren Datensätzen blieb der Vorsprung gering, oder fiel hinter den des herkömmlichen Programms zurück. Zum Vergleich längerer Dokumente stellte sich der Einsatz der GPU-Programmierung als weniger geeignet heraus. Hier konnte keine Testumgebung geschaffen werden, bei der die GPU basierte Version des Levenshtein Algorithmus schneller arbeitete als die herkömmliche CPU21 basierte Variante. Auch bei längeren Dokumenten blieb die herkömmliche Version mehr als doppelt so schnell wie die OpenCL-Version. Insgesamt zeigte sich aber mit wachsendem Datenumfang eine Tendenz zur Annäherung, da das Laufzeitwachstum der GPU-basierten Version langsamer war, als das der CPU-basierten Version. Als universelle Methode für performanceoptimiertes Rechnen kann die GPU-Programmierung in dem hier untersuchten Kontext zwar nicht bezeichnet werden, unter bestimmten Voraussetzungen, sind jedoch erhebliche Laufzeitverbesserungen möglich und der zusätzliche Mehraufwand durchaus lohnenswert. 22 6 Literaturverzeichnis CORMEN, Thomas: 2013, Algorithmen – Eine Einführung. 4., durchges. und kommentierte Aufl. Oldenbourg Verlag, München. GUSFIELD, Dan: 1999, Algorithms on strings, trees and sequences.Computer science and computational biology. Cambridge University Press, Cambridge. WEGST, Tillmann: Stringähnlichkeit. http://www.tillmann-wegst.de/fuzzy/, n.d. Web. 30. Aug. 2014. 23