Sonntag, 24. Oktober 2010

Reguläre Ausdrücke sind langsam - Hintergründe

Reguläre Ausdrücke gelten gemeinhin als überaus mächtige Werkzeuge um Textelemente zu finden oder Ersetzungen vorzunehmen. Gegen reguläre Ausdrücke spricht, dass sie komplex und langsam sind. Man sollte sie nur sparsam einsetzen wenn andere Funktionen nicht passend sind. Entsprechende Hinweise gibt es zu dutzenden im Web.

Sieht man sich das Verhalten von regulären Ausdrücken aber in der Praxis an und beschränkt sich nicht auf eine isolierte Betrachtung der einzelnen Funktionen, zeigt sich ein anderes Bild.

Hintergründe zu dem eigentlichen Beitrag.

Der Auftakt der Messungen wird mit einem sehr einfachen Test gemacht. Wir lesen eine XML-Datei ein und führen auf einfache Weise verschiedene Zeitmessungen durch, zum Vergleich auch Funktionen ohne Bezug zu der XML-Datei.

//Datei lesen
$Datei = './a99418b7.xml';

$DateiInhalt = file_get_contents($Datei);
$XML = simplexml_load_file($Datei);

//Zeitmessung strpos
$zwischenzeit = microtime(true);

    strpos($DateiInhalt, 'Suchwort');

echo (microtime(true) - $zwischenzeit) * 1000;
echo " ,strpos\n";

//Zeitmessung preg_match
$zwischenzeit = microtime(true);

    preg_match("/Suchmuster/", $DateiInhalt);

echo (microtime(true) - $zwischenzeit) * 1000;

//Zeitmessung SimpleXML
$zwischenzeit = microtime(true);

    $XML->Daten->wort[10]->attributes()->id;

echo (microtime(true) - $zwischenzeit) * 1000;
echo " ,simplexml/is\n";
Hier ist das Ergebnis für verschiedene Funktionen und Aufrufe, die im obigen Code aus Platzgründen nicht vollständig aufgeführt sind:




Bei einer solchen Zeitmessung zeigt sich beispielhaft wie vorsichtig man bei Benchmarktests sein muss:

PregMatch mit den Parametern "/i" oder "/is" ist wesentlich schneller als die reine Funktion ohne Parameter - könnte man zumindest aufgrund dieses Testergebnisses denken? Tatsächlich aber sind reguläre Ausdrücke schneller wenn sie mit einem Ausdruck bereits einmal aufgerufen worden sind. Ein ähnliches, eher zu erwartendes Zeitverhalten kann man feststellen wenn eine Klasse wiederholt instanziiert wird. Das obige Ergebnis stellt also nichts anderes als einen fataler Fehler in dem Testszenario dar - herzlichen Glückwunsch!

Auch darf man nicht außer acht lassen, welche Vorbedingungen für bestimmte Funktionen erfüllt sein müssen. Für SimpleXML muss beispielsweise das Dokument zeitaufwendig eingelesen und aufgereitet werden - "simplexml_load_file()" ist nicht gerade ein "Rennauto" was bei einer isolierten Betrachtungsweise gerne vergessen wird.
Wichtig ist auch immer die Frage auf welchem Betriebssystem, mit welcher Version von PHP oder welcher Prozessor- bzw. Systemgeschwindigkeit ein Test durchgeführt wird.

Kommen wir zu unserem eigentlichen Test, der unter Windows über das CLI (benchmark_win_cli.php) und unter Linux via Webserver (benchmark_linux_http.php) aufgerufen wird. Damit jede Funktion unabhängig von den anderen getestet werden kann, legen wir beim Aufruf über einen Parameter fest welche Funktion aktuell ausgeführt werden soll.
(Hinweis: Auf einigen Windowsrechnern führt die zweite Funktion von SimpleXML zu einem Fehler, ich werde dies noch untersuchen. Es hat jedoch keinen Einfluß auf das Testergebnis).

switch ($Aktion)
{
    case 'stristr':
        foreach (glob($Ordner.'/*.[xX][mM][lL]') as $Dateien)
        {
            $DateiInhalt = file_get_contents($Dateien);

            if(stristr($DateiInhalt,'Suchwort1')) $zaehler++;
            if(stristr($DateiInhalt,'Suchwort2')) $zaehler++;
            if(stristr($DateiInhalt,'Suchwort3')) $zaehler++;

        };
        break;

    //Weitere Funktionen...
};
Nur statische Suchmuster in der Art "tag wert /tag" können in allen Funktionen gleich realisiert werden, sind aber gerade deshalb künstlich. Insbesondere bei regulären Ausdrücken machen statische Suchmuster genaugenommen nur wenig Sinn. Andererseits ist es fast unmöglich ein komplexes Suchmuster mit einfachen Stringfunktionen abbilden zu wollen. Man ist so in dem Test quasi sein eigener Gefangener.

Zur Kontrolle und Vergleichbarkeit der Ergebnisse zählt jeder Treffer bei der Suche einen Counter hoch, das Ergebnis wird zusammen mit der gemessenen Zeit in einer Textdatei protokolliert. Problematisch ist es Muster zu finden, die von allen Stringfunktionen, PregMatch und SimpleXML gefunden werden können.

//Steuerinformationen und Zeitmessung
$zaehler = 0;
$anfang= microtime(true);

 //switch case (siehe oben)

$ende = microtime(true);
$zeitspanne = $ende-$anfang;
logge($zeitspanne.','.$Aktion.','.$zaehler);


function logge($Inhalt)
{
    $logdatei=fopen('./info.txt','a');
    fputs(
            $logdatei,
            $Inhalt ."\r\n"
    );
    fclose($logdatei);
}
Bleibt uns nur noch die einzelnen Funktionen mehrfach im Wechsel aufzurufen und die erzeugten Daten auszuwerten.

"i" steht für "evil"

Wer glaubt, dass das schlechte Ergebnis der "i"-Funktionen unter Windows ein Ausrutscher sei, der möge sich das Ergebnis des Tests auf einem Netbook unter Windows mit PHP 5.3 ansehen:




Seltsam? Aber so steht es geschrieben!

Keine Kommentare:

Kommentar veröffentlichen