Wie einige von Euch ja schon wissen, beschäftige ich mich seit einiger Zeit mit der Optimierung kurzer Funktionen, die als "Arbeitspferde" für die Bearbeitung großer (Short-) String-Datensätze dienen. Also vor allem mit den Compare-Methoden, die man zum Sortieren braucht, und den Pos-Funktionen, mit denen man Texte durchsuchen kann. Irgendwann habe ich angefangen, Entwürfe hierfür in Assembler zu schreiben und deren Laufzeitverhalten zu testen.
Dabei bin ich darauf gestoßen, daß es unheimlich schwierig ist, vorherzusagen, welche Lösungen richtig schnell funktionieren und welche nicht. Die Zeiten, als man den Zeitbedarf von Assemblercode anhand von Handbuchtabellen einfach addieren konnte, sind längst vorbei: Moderne Prozessoren versuchen, den weiteren Programmablauf vorherzusagen und im Hintergrund Vorarbeit zu leisten, indem sie z.B. später erst anstehende Anweisungen schon mal vorsorglich durchführen, um fertige Ergebnisse parat zu haben, wenn die dann gebraucht werden. Das Ganze ist ein Spiel mit Wahrscheinlichkeiten, denn solange die Vorhersagen, welchem Entscheidungspfad das Programm demnächst wohl folgen würde, zutreffen, funktioniert das Ganze wunderbar – sobald diese Predictions aber in die falsche Richtung gehen, war der ganze Hintergrundprozeß für die Katz und muß neu aufgebaut werden. Das kostet dann wiederum richtig viel der zuvor mühsam eingesparten Laufzeit.
Um nun optimalen, schnellen Code zu schreiben, muß man nun seinerseits vorhersehen, wie der Prozessor an dieser oder jener Stelle wohl „ticken“ würde. Das hat sich als außerordentlich schwierig herausgestellt, und sehr viel von dem, was man darüber im Internet nachlesen kann, hat sich dabei leider auch als durchaus fragwürdig erwiesen. Manchmal nützt es z.B., sinnlos scheinende Verzögerungen (sogenannte NOPs, No-Operation-Anweisungen) mitten in den Code einzubauen. Im Internet wird seit einiger Zeit das seltsame Phänomen diskutiert, daß sogar wahllos eingestreute NOP's Programme beschleunigen können – bislang konnte m.W. aber noch niemand eine schlüssige Begründung für diesen Effekt vorbringen.
Ich selbst bin in meiner kleinen Welt der Compare- und Pos-Funktionen von einer Überraschung in die nächste gefallen, einen Teil davon konntet Ihr in diesem Thread nachlesen, wo es um das sogenannte Alignment ging, der Anordnung von Prozessoranweisungen und Sprungstellen relativ zu 16-, 32- und 64-Byte-Blöcken innerhalb des Speichers bzw. Prozessor-Caches. Man könnte das alles als Kleinkrämerei abtun, aber die Erfahrung zeigt, daß es hier um Laufzeitunterschiede bis zu einer Größenordnung von 1:3 geht – für eine Compare-Funktion, die beim Sortieren von Datensätzen millionenmal durchlaufen wird, sind das Welten, die der Anwender sehr wohl zu spüren bekommt. Sich mit diesem Thema zu beschäftigen lohnt sich also durchaus!
Und es gab noch mehr Überraschungen – die genauen technischen Details werde ich, um Eure Geduld nicht überzustrapazieren, in einem getrennten Posting darlegen. Hier nur soviel: Ich habe daher beschlossen, sozusagen eine weitere Vergrößerungslinse an mein Mikroskop zu schrauben und eine denkbar einfache Funktion (die einen ShortString nach der Position eines Chars durchsucht - also das, was System.Pos in der Variante (Char; ShortString) macht) in einer Reihe von Varianten durchzutesten. Dabei ist das hier präsentierte Testprogramm entstanden.
Es hat sich dann gezeigt, daß keineswegs alles, was auf meinem AMD-64-Desktop gut funktioniert, auch auf meinem 32-bit Laptop (mit einem Intel Celeron) richtig rennt. Manches dann aber schon. Damit bin ich nun an einem Punkt angelangt, an dem ich Euch um Eure (möglichst zahlreiche!) Mithilfe bitten möchte!
Die Frage ist nämlich, ob es genügend Gemeinsamkeiten im Mikro-Laufzeitverhalten unterschiedlichster x86-Prozessoren gibt, aus denen sich Erkenntnisse in Bezug auf Code-Optimierung gewinnen lassen, oder ob die Unterschiede überwiegen. Was es also als nächstes braucht, ist ein Vergleich der Ergebnisse des hier vorgestellten Testprogramms mit möglichst vielen verschiedenen Prozessoren. Da aber einzelne Prozessorfamilien und -typen ebenfalls ständig weiterentwickelt werden (in verschiedene „Modelle“ unterteilt und unterhalb der Modellebene in das sogenannte „Stepping“), interessieren hier also nicht nur CPU-Typen, sondern auch deren Baureihen und Revisionen.
Nun zum Testprogramm AlignTest:
(Edit, 19. 3. 2015: Revidierte Fassung mit MaxStrings-Begrenzung)
AlignTest vermißt und protokolliert die jeweilige Laufzeit einer sehr einfachen Pos(Char; Shortstring)-Funktion, deren Kern aus lediglich 6 Assembler-Zeilen besteht, in rund hundert winzigen Variationen. Als Datenbasis verwendet das Programm dabei das Lazarus-Verzeichnis (dieses muß ggfls. noch eingegeben werden) und lädt dann von dort eine gute Million Quelltextzeilen aus den Source-Dateien. Diese Strings werden dann jeweils nach 20 verschiedenen Buchstaben durchsucht. Wichtig ist dabei: Das Programm mißt den gesamten Durchlauf dieser >20.000.000 Aufrufe pro Funktion, es nimmt also keine (unsinnigen) Mikromessungen vor, die Streuung der Egebnisse ist dementsprechend gering (< ± 1% bei gleichmäßiger Belastung des Computers).
Der gesamte Test dauert ca. 12 Minuten (natürlich abhängig vom jeweiligen Rechner, wobei meine 12 Minuten auf eher betagten Kisten ermittelt wurden). Wem das zu lange dauert, der kann die Anzahl der Runs natürlich auch heruntersetzen – die habe ich auf 4 voreingestellt, um auch durchschnittliche Abweichungen, also gewissermaßen das „Betriebssystem-Rauschen“, sowie Ausreißer erfassen zu können. Gut wäre übrigens, dann eine Kaffeepause einzulegen und den Computer während des Tests unbelastet von anderen rechenintensiven Aktivitäten laufen zu lassen (wobei auch das Gegenteil dessen mal ganz interessant zu sehen ist, bloß daß das eben nicht Gegenstand des Tests ist).
Anschließend generiert das Programm ein Logfile namens "AlignTestResult(Prozessorname).txt", das im selben Verzeichnis wie die Quelltext-Dateien abgelegt wird (zwei meiner Ergebnisfiles sind im Zipfile enthalten).
Worum ich Euch nun bitten möchte, ist Folgendes:
- Die Programmdateien herunterzuladen und in einen Ordner zu entzippen.
- Das Programm (Datei AlignTest.lpr) in Lazarus zu laden, es (unverändert) zu kompilieren und zu starten. Voreingestellt ist das kompilieren ohne Debugger mit Optimization Level 2. Es spielt übrigens für die Messungen keine Rolle, ob man das Programm innerhalb der Lazarus-IDE laufen läßt oder außerhalb als Executable aufruft.
- Ggfls. muß jetzt noch das Lazarus-Verzeichnis eingegeben werden, falls die Vorgabe (/usr/share/lazarus/ für Linux bzw. c:\lazarus für Windows) bei Euch nicht zutrifft.
- Den Test durchlaufen zu lassen (das dauert wie gesagt etwa 12 Minuten).
- Und schließlich die Ergebnisdatei ("AlignTestResult(Prozessorname).txt") entweder hier zu posten oder mir per EMail zuzusenden (meine EMail-Adresse erscheint auf dem unteren Memofeld)..
Im besten Fall dagegen könnten wir aber schon einiges darüber lernen, wie man zeitkritische Hotspot-Routinen (für die ist das hier vorrangig von Interesse) erheblich verbessern kann. Das könnte – im allerbesten Fall – vielleicht sogar für die FPC-Kernentwickler interessant sein.
Jedenfalls bin ich bei meinen Internetrecherchen zu diesem Thema nirgendwo auf einen solchen Versuch gestoßen, das Mikro-Laufzeitverhalten prozessorübergreifend empirisch zu untersuchen. Klar: Hier wird nur ein winziger Ausschnitt des Prozessorverhaltens betrachtet. Aber allein dieser Ausschnitt kann Laufzeitunterschiede von 1:3, z.B. beim Sortieren, bewirken. Und man wird sehen, ob es sich lohnt, ggfls. später einen umfangreicheren Test zu enwickeln (dann aber besser im Team).
Ob ich hier Neuland betrete oder bloß im nebligen Morast herumstapfe – ganz ehrlich: Ich weiß es nicht. Aber ich denke, einen Versuch ist es wert. Selbstverständlich werde ich Euch über die Ergebnisse auf dem Laufenden halten.
Jedenfalls: Je mehr Testergebnisse ich auswerten kann, desto wertvoller und damit umso eher von allgemeinem Interesse könnten etwaige Erkenntnisse daraus sein.
Gruß Rüdiger
P.S.: AlignTest wurde unter 32- und 64-bit Linux entwickelt und ausgiebig getestet, ich konnte es aber nicht unter Windows testen. Ich hoffe, es läuft auch dort anstandslos (möglicherweise mit einigen optischen Abstrichen). Wenn es doch unter Windows ernste Schwierigkeiten gibt, sagt bitte Bescheid, dann müßte man nochmal einen Zwischenstop machen.