Das Performance-Handbuch: Implementierung mit Perl


Wer in der UNIX oder Linux Welt lebt hat sicher schon Kontakt mit Perl gehabt.

Perlversionen

Bearbeiten

Die hier dargestellten Tipps wurden mit folgenden Perlversionen getestet:

PerlBetriebssystem
5.6.0 MSWin32-x86-multi-threadWindows NT
5.6.0 MSWin32-x86-multi-threadWindows 2000
5.005_53cOS2

Profiler

Bearbeiten

Auch bei Perl gilt, dass es nützlich ist die Performancelecks zu kennen bevor Sie zu Optimieren anfangen. Damit Sie die Ausführungsgeschwindigkeiten der einzelnen Operationen komfortabel messen können steht Ihnen das Modul Benchmark.pm zur Verfügung. Durch dieses Modul wird Ihnen ermöglicht in komfortabler Art und Weise die Ausführungsgeschwindigkeit zu messen.

Nutzung des Benchmark.pm Modul

Bearbeiten

Für die Nutzung des Moduls Benchmark.pm ist zuerst die Einbindung dieses notwendig.

use Benchmark;

Routine timethis ()

Bearbeiten

Die einfachste Methode die Ausführungszeit eines Quelltextabschnittes zu messen ist diesen mit Hilfe der Routine timethis () aufzurufen. Der einfachste Aufruf erfolgt durch die Übergabe von zwei Parametern. Der erste ist die Anzahl der Durchläufe während Sie innerhalb des zweiten den auszuführende Quelltext einbetten.

timethis (1000000, 'my $home="http://de.wikibooks.org/wiki/Das_Performance_Handbuch";');

Optional können Sie einen dritten Parameter übergeben, der eine Überschrift für Ihre Benchmark darstellt.

timethis (1000000, 'my $home="http://de.wikibooks.org/wiki/Das_Performance_Handbuch";', "Meine Benchmark");

Sofern Sie den ersten Parameter negativ wählen wird der zu testende Quelltext mindestens diese Anzahl von Sekunden ausgeführt, bevor der Test beendet wird. Bei der Angabe von 0 wird der Quelltext 3 Sekunden lang ausgeführt.

 timethis (-3,'my $home="http://de.wikibooks.org/wiki/Das_Performance_Handbuch";',"Meine 3sek.-Benchmark");

Als fünften Parameter können Sie noch die Art der Aufzeichnungen des Benchmarktests angeben. Zur Auswahl stehen dabei die Parameter 'all', 'none', 'noc', 'nop' oder 'auto'.

timethis (1000000,'my $home="http://de.wikibooks.org/wiki/Das_Performance_Handbuch";',"Option nop",'nop');

Routine countit ()

Bearbeiten

Eine weitere Routine ist countit () mit welcher Sie die Anzahl der Durchläufe in einer bestimmten Zeit. Dabei wird als erster Parameter die Zeit und als zweiter Parameter der auszuführende Quelltext übergeben. Zurückgegeben wird ein Benchmark Objekt. Im Gegensatz zu timethis () wird das Ergebnis nicht sofort ausgegeben. Zu diesem Zweck kann die Routine timestr () verwendet werden.

my $count=Benchmark::countit (2,'my $author="Bastie - Sebastian Ritter";');
print timestr($count);

countit () wird durch use Benchmark; nicht importiert, so dass Sie dass Paket der Routine mit angeben müssen.

Das obige Beispiel ist somit identisch zu einem einfachen timethis () Aufruf mit einer positiven Zahl als ersten Parameter.

Routine timethese ()

Bearbeiten

Die Routine timethese () ist als Wrapperroutine zu timethis () zu interpretieren. Wie diese wird als erster Parameter die Anzahl der Durchläufe angegeben. Als zweiten Parameter wird ein Hash erwartet.

my $hash = {'Addition mit +' => 'my $num=2; $num=$num+$num;' ,'Addition mit +=' => 'my $num=2; $num+=$num;'};
timethese (1000000, $hash);

Direkte Zeitmessung

Bearbeiten

Das Modul Benchmark.pm stellt noch weitere Möglichkeiten der Zeitmessung bereit. Durch den new Operator können Sie ein Benchmark Objekt erzeugen, welcher üblicherweise die vergangene Zeit in Sekunden seit dem 01.01.1970 enthält. Die Routine timediff () ermöglicht das Berechnen des Zeitunterschiedes zweier Benchmark Objekte, während Sie mit timesum ()[1] die Summe ermitteln können. Eine einfache Zeitmessung kann daher wie folgt aussehen:

my $counter = 0;
my $timeLauf1 = new Benchmark;
while ($counter < 1000000){
  $counter ++;
}
$timeLauf1 = timediff (new Benchmark, $timeLauf1);
$counter = 0;
my $timeLauf2 = new Benchmark;
while ($counter < 1000000){
  $counter ++;
}
$timeLauf2 = timediff (new Benchmark, $timeLauf2);
print "\n\rZeit 1. Durchlauf\n\r".timestr($timeLauf1);
print "\n\rZeit 2. Durchlauf\n\r".timestr($timeLauf2);
print "\n\rSumme: ".
timestr (Benchmark::timesum($timeLauf1,$timeLauf2));

Frühes und Spätes Binden

Bearbeiten

Es gibt in Perl verschiedene Möglichkeiten Quelltext auszulagern und wiederzuverwenden. Wie in fast allen Programmiersprachen gibt es die Möglichkeit der Quelltextauslagerung in Funktionen. Hier bietet sich als eine Möglichkeit das Optimierungsmöglichkeit Inlining an.

Inlining

Bearbeiten

Das sogenannte Inlining von Quelltext ist eine wirksame Methode um die Ausführungsgeschwindigkeit Ihrer Perl Anwendung zu erhöhen. Dies erhöht zwar die Größe Ihres Skriptes in nicht unerheblicher Weise, ermöglicht jedoch enorme Geschwindigkeitssteigerungen.

Die Benchmark der folgenden Anwendung zeigt eine Geschwindigkeitssteigerung von etwa 300%.

timethis (1000000, '&printIt()', "\n\rSubaufruf");
sub printIt {
  print "";
}
timethis (1000000, 'print "";', "\n\rInlining");

Der andere wesentliche Nachteil ist die Verschlechterung der Wartbarkeit Ihrer Anwendung, da Quelltext mit identischen Aufgaben an verschiedenen Stellen stehen.

Externe Funktionen und Module

Bearbeiten

Eine weitere Möglichkeiten Quelltext auszulagern ist dessen Aufruf mittels do (). Mit Hilfe dieses Schlüsselwortes können Sie Ihren Quelltext auf mehrere Dateien verteilen und zur Laufzeit einbinden. Dies führt dazu, dass der Quelltext der externen Datei an dieser Stelle in Ihre Anwendung eingefügt. Der Performancenachteil von do () ist, dass bei jedem Aufruf die Datei geöffnet, gelesen, geparst und interpretiert wird. Dies führt innerhalb von Schleifen zu einem erheblichen Overhead.

Die zweite Möglichkeit ist mit Hilfe des Schlüsselwortes require (). Im Gegensatz zu do () wird jedoch die Datei nur einmal eingelesen, so dass der Interpreter diesen Quelltext beim nächsten Durchlauf bereits im Speicher vorfindet und require () dadurch schneller ist.

Die dritte Möglichkeit ist die Verwendung von use, wodurch ähnlich require () der externe Quelltext genutzt werden kann. Dies wird insbesondere in Zusammenhang mit Modulen verwendet. Zusätzlich wird jedoch noch ein import durchgeführt. Der wesentliche Unterschied ist jedoch, das use bereits zum Zeitpunkt des Kompilierens abgearbeitet wird im Gegensatz zu do () und require (). Mit use können Sie daher ein frühes Binden[2] des externen Quelltext mit dem einhergehenden Geschwindigkeitsvorteil erreichen.

Beispiel für do, require und use Datei für do (*.pl):

my $a = 0;
while ($a < 1000){
  $a++;
}

Die Datei für require (*.pl) und use (*.pm) enthält an der letzten Stelle lediglich noch eine 1. Starten können Sie diese Quelltexte z.B. mit folgendem Skript .

 use Benchmark;
 timethis (100000000, 'use r;', "Use");
 timethis (100000000, 'require "s.pl"', "Require");
 timethis (100000000, 'do "t.pl"', "Do");

Variablen, Datentypen und Operatoren

Bearbeiten

Verbundene Zuweisungen

Bearbeiten

Perl versteht verbundene Zuweisungen. Diese werden durch den Interpreter schneller umgesetzt als getrennte, so dass $i++; schneller als $i += 1; und $i += $i; schneller als $i = $i + $i; ist. Dies gilt analog für die anderen Rechenoperationen.

Schreibweise der logischen Ausdrücke

Bearbeiten

Sie können für die logischen Ausdrücke Nicht, Und bzw. Oder zwei verschiedene Schreibweisen verwenden:

Logischer Ausdruck
NichtNot!
UndAnd&&
OderOr||

Exklusives Oder - Xor

Bearbeiten

Die ausgeschriebenen logischen Ausdrücke, die mehrere Ausdrücke verbinden werden dabei anders interpretiert. So wird bei den ausgeschriebenen Ausdrücken nur dann der nächste Ausdruck ausgewertet, sofern dies nach Auswertung des aktuellen Ausdrucks noch nötig ist.

if (1 = 1 || 2 = 2){}
if (3 = 3 or 4 = 4){}

Der vorangegangene Quelltext wird in der || Schreibweise somit zu einer Prüfung beider Ausdrücke führen, bevor der Inhalt des if Blocks ausgeführt wird. Bei der or Schreibweise hingegen wird bereits nach der Prüfung des ersten Ausdrucks in den if Block verzweigt[3]. Für die and bzw && Schreibweise gilt dies analog.


  1. Beachten Sie, dass timesum im Gegensatz zu timediff nicht in Ihren Namensraum importiert wird.
  2. Spätes Binden also mittels do realisiert.
  3. Übrigens waren die Auswirkungen auf dem OS/2 System mit dem Perlinterpreter 5.005_53 wesentlich gravierender als die auf dem Windowssystemen – hat hier jemand mitgedacht?