Strings vor Zerlegen mit explode durchsuchen mit strpos

Lässt sich die Performance verbessern, wenn ein mit explode() zu zerlegender String zuvor mit strpos() untersucht wird, ob er den von explode() verwendeten Delimiter enthält? Wir haben mehrere kleine Scripte erstellt und sie einem Benchmark-Test unterzogen.

String enthält den Delimiter in der Mitte, ohne strpos()

Code:

<?php
$teilstring = "f";
$gesamtstring = "abcdefghijk";
for ($i = 0;$i < 10000;$i++) {
    $test = explode($teilstring, $gesamtstring);
}
?>

Erläuterung:
Der String abcdefghijk wird innerhalb einer for()-Schleife 10.000mal anhand des Delimiters f zerlegt.

String enthält den Delimiter am Ende, ohne strpos()

Code:

<?php
$teilstring = "k";
$gesamtstring = "abcdefghijk";
for ($i = 0;$i < 10000;$i++) {
    $test = explode($teilstring, $gesamtstring);
}
?>

Erläuterung:
Der String wird gegenüber dem ersten Beispiel-Code nun anhand eines anderen Delimiters zerlegt.

String enthält den Delimiter nicht, ohne strpos()

Code:

<?php
$teilstring = "m";
$gesamtstring = "abcdefghijk";
for ($i = 0;$i < 10000;$i++) {
    $test = explode($teilstring, $gesamtstring);
}
?>

Erläuterung:
In diesem dritten Beispiel-Script wird der String anhand eines Delimiters zerlegt, den er nicht enthält.

String enthält den Delimiter in der Mitte, mit strpos()

Code:

<?php
$teilstring = "f";
$gesamtstring = "abcdefghijk";
for ($i = 0;$i < 10000;$i++) {
    if (strpos($gesamtstring, $teilstring) !== "false") {
        $test = explode($teilstring, $gesamtstring);
    }
}
?>

Erläuterung:
Das vierte Beispiel-Script ist eine Abwandlung des ersten Beispiel-Codes. Vor Ausführen der explode()-Funktion wird mit strpos() geprüft, ob der an explode() übergebene Delimiter im String enthalten ist.

String enthält den Delimiter am Ende, mit strpos()

Code:

<?php
$teilstring = "k";
$gesamtstring = "abcdefghijk";
for ($i = 0;$i < 10000;$i++) {
    if (strpos($gesamtstring, $teilstring) !== "false") {
        $test = explode($teilstring, $gesamtstring);
    }
}
?>

Erläuterung:
Dieser fünfte Beispiel-Code enthält gegenüber dem vorangegangenen Script den Delimiter an einer anderen Stelle im String.

String enthält den Delimiter nicht, mit strpos()

Code:

<?php
$teilstring = "m";
$gesamtstring = "abcdefghijk";
for ($i = 0;$i < 10000;$i++) {
    if (strpos($gesamtstring, $teilstring) !== "false") {
        $test = explode($teilstring, $gesamtstring);
    } else {
        $test[0] = $gesamtstring;
    }
}
?>

Erläuterung:
In diesem sechsten Beispiel-Script enthält der zu zerlegende String den Delimiter nicht. Aufgrund der mit strpos() durchgeführten Prüfung auf Vorhandensein des Delimiters im String wird die explode()-Funktion nicht ausgeführt. Stattdessen wird der gesamte String an ein $test-Array übergeben, so dass der Code das gleiche bewirkt wie in unserem dritten Beispiel-Code.

Ergebnis

Requests per second (RPS)Time per Request (TPR)Vergleich
String enthält den Delimiter in der Mitte, ohne strpos()51.28 [#/sec] (mean)19.500 [ms] (mean)
30.6%
23.4%
String enthält den Delimiter am Ende, ohne strpos()50.75 [#/sec] (mean)19.703 [ms] (mean)
29.3%
22.6%
String enthält den Delimiter nicht, ohne strpos()63.05 [#/sec] (mean)15.859 [ms] (mean)
60.6%
37.7%
String enthält den Delimiter in der Mitte, mit strpos()39.26 [#/sec] (mean)25.469 [ms] (mean)
String enthält den Delimiter am Ende, mit strpos()39.31 [#/sec] (mean)25.438 [ms] (mean)
0.1%
0.1%
String enthält den Delimiter nicht, mit strpos()45.94 [#/sec] (mean)21.766 [ms] (mean)
17.0%
14.5%

Am schnellsten ausgeführt wird das dritte Beispiel-Script, bei dem ohne vorangegangen Überprüfung mit strpos() der String anhand eines nicht in selbigem enthaltenen Delimiters zerlegt wird. Etwas langsamer sind die beiden Scripte, in denen der String den Delimiter enthält ohne vorherige Suche via strpos() zerlegt wird. Die drei Scripte, bei denen der String mit strpos() durchsucht wurde, sind deutlich langsamer – selbst, wenn der Delimiter nicht im String enthalten ist.

Fazit

An der wievielten Stelle der Delimiter im String enthalten ist, scheint keine Auswirkungen auf die Ausführzeit zu haben. Bei einem String mit einer Länge von 10 Zeichen verschlechtert eine dem Zerlegen vorangestellte Suche mit strpos() deutlich. Doch wie verhält es sich bei längeren Strings? Wir führen einen Benchmark mit folgenden Scripten aus:

 

Lässt sich die Performance verbessern, wenn ein mit explode() zu zerlegender String zuvor mit strpos() untersucht wird, ob er den von explode() verwendeten Delimiter enthält? Wir haben mehrere kleine Scripte erstellt und sie einem Benchmark-Test unterzogen.

String (200 Zeichen) enthält den Delimiter, ohne strpos()

Code:

<?php
$teilstring = "f";
$string = "abcdefghijk";
$gesamtstring = "";
for ($i = 0;$i < 20;$i++) {
    $gesamtstring.= $string;
}
for ($i = 0;$i < 10000;$i++) {
    $test = explode($teilstring, $gesamtstring);
}
?>

Erläuterung:
Innerhalb einer for()-Schleife wird String mit der Länge 200 Zeichen wird anhand eines im String enthaltenen Delimiters 10.000mal mit explode() zerlegt. Dabei findet vor dem Zerlegen keine Überprüfung statt, ob der String den Delimiter enthält.

String (200 Zeichen) enthält Delimiter nicht, ohne strpos()

Code:

<?php
$teilstring = "m";
$string = "abcdefghijk";
$gesamtstring = "";
for ($i = 0;$i < 20;$i++) {
    $gesamtstring.= $string;
}
for ($i = 0;$i < 10000;$i++) {
    $test = explode($teilstring, $gesamtstring);
}
?>

Erläuterung:
Anders als im vorangegangenen Beispiel-Code enthält der mit explode() zu zerlegende String den Delimiter nicht.

String (2000 Zeichen) enthält den Delimiter, ohne strpos()

Code:

<?php
$teilstring = "f";
$string = "abcdefghijk";
$gesamtstring = "";
for ($i = 0;$i < 200;$i++) {
    $gesamtstring.= $string;
}
for ($i = 0;$i < 10000;$i++) {
    $test = explode($teilstring, $gesamtstring);
}
?>

Erläuterung:
Ein 2.000 Zeichen umfassender String wird anhand eines im String enthaltenen Delimiters 10.000mal mit explode() zerlegt.

String (2000 Zeichen) enthält Delimiter nicht, ohne strpos()

Code:

<?php
$teilstring = "m";
$string = "abcdefghijk";
$gesamtstring = "";
for ($i = 0;$i < 200;$i++) {
    $gesamtstring.= $string;
}
for ($i = 0;$i < 10000;$i++) {
    $test = explode($teilstring, $gesamtstring);
}
?>

Erläuterung:
Der gleiche String wie im vorangegangenen Beispiel wird anhand eines Delimiters zerlegt, der im String nicht vorkommt.

String (200 Zeichen) enthält den Delimiter, mit strpos()

Code:

<?php
$teilstring = "f";
$string = "abcdefghijk";
$gesamtstring = "";
for ($i = 0;$i < 20;$i++) {
    $gesamtstring.= $string;
}
for ($i = 0;$i < 10000;$i++) {
    if (strpos($gesamtstring, $teilstring) !== "false") {
        $test = explode($teilstring, $gesamtstring);
    }
}
?>

Erläuterung:
Ein String der Länge 200 wird innerhalb einer for()-Schleife 10.000mal via explode() zerlegt. Vor dem Zerlegen wird mittels strpos() geprüft, ob der Delimiter im String vorkommt. In diesem Beispiel ist das der Fall.

String (200 Zeichen) enthält Delimiter nicht, mit strpos()

Code:

<?php
$teilstring = "m";
$string = "abcdefghijk";
$gesamtstring = "";
for ($i = 0;$i < 20;$i++) {
    $gesamtstring.= $string;
}
for ($i = 0;$i < 10000;$i++) {
    if (strpos($gesamtstring, $teilstring) !== "false") {
        $test = explode($teilstring, $gesamtstring);
    } else {
        $test[0] = $gesamtstring;
    }
}
?>

Erläuterung:
Im Unterschied zum vorangegangenen Beispiel-Code enthält der String den Delimiter nicht. D.h., die Prüfung via strpos() wird durchgeführt, gibt false zurück und explode() wird nicht ausgeführt. Stattdessen wird – um das gleiche Ergebnis zu bewirken – der String $test[0] zugeordnet.

String (2000 Zeichen) enthält den Delimiter, mit strpos()

Code:

<?php
$teilstring = "f";
$string = "abcdefghijk";
$gesamtstring = "";
for ($i = 0;$i < 200;$i++) {
    $gesamtstring.= $string;
}
for ($i = 0;$i < 10000;$i++) {
    if (strpos($gesamtstring, $teilstring) !== "false") {
        $test = explode($teilstring, $gesamtstring);
    }
}
?>

Erläuterung:
Ein 2.000 Zeichen umfassender String wird 10.000mal mittels explode() zerlegt. Vor dem Zerlegen wird mit strpos() sichergestellt, dass der String den Delimiter enthält, anhand dessen er zerlegt werden soll.

String (2000 Zeichen) enthält Delimiter nicht, mit strpos()

<?php
$teilstring = "m";
$string = "abcdefghijk";
$gesamtstring = "";
for ($i = 0;$i < 200;$i++) {
    $gesamtstring.= $string;
}
for ($i = 0;$i < 10000;$i++) {
    if (strpos($gesamtstring, $teilstring) !== "false") {
        $test = explode($teilstring, $gesamtstring);
    } else {
        $test[0] = $gesamtstring;
    }
}
?>

Erläuterung:
Der Delimiter ist – anders als im vorangegangenen Beispiel – nicht im String enthalten.

Ergebnis

Requests per second (RPS)Time per Request (TPR)Vergleich
String (200 Zeichen) enthält den Delimiter, ohne strpos()11.42 [#/sec] (mean)87.547 [ms] (mean)
806.3%
89.0%
String (200 Zeichen) enthält Delimiter nicht, ohne strpos()59.48 [#/sec] (mean)16.813 [ms] (mean)
4620.6%
97.9%
String (2000 Zeichen) enthält den Delimiter, ohne strpos()1.30 [#/sec] (mean)769.016 [ms] (mean)
3.2%
3.3%
String (2000 Zeichen) enthält Delimiter nicht, ohne strpos()28.01 [#/sec] (mean)35.703 [ms] (mean)
2123.0%
95.5%
String (200 Zeichen) enthält den Delimiter, mit strpos()10.07 [#/sec] (mean)99.281 [ms] (mean)
699.2%
87.5%
String (200 Zeichen) enthält Delimiter nicht, mit strpos()39.02 [#/sec] (mean)25.625 [ms] (mean)
2996.8%
96.8%
String (2000 Zeichen) enthält den Delimiter, mit strpos()1.26 [#/sec] (mean)795.203 [ms] (mean)
String (2000 Zeichen) enthält Delimiter nicht, mit strpos()17.86 [#/sec] (mean)56.000 [ms] (mean)
1317.5%
93.0%

Die niedrigste Ausführzeit hat das Script, bei dem ein 200 Zeichen String mit explode() anhand eines Delimiters zerlegt wird, der nicht im String vorkommt. Am längsten dauerte bei dem durchgeführten Benchmark das Script, bei dem ein String der Länge 2.000 den Delimiter enthält und dies vor dem Zerlegen mittels strpos() überprüft wird.

Fazit

In sämtlichen Beispielen kostete die Überprüfung mit strpos() unnötig Ausführzeit. In der Praxis eher selten vorkommende Fälle, bei denen Strings die Größe von 100kB und mehr haben, wurden nicht geprüft.

 

Anmerkung:
Die Tests wurden mit dem Apache HTTP server benchmarking tool ab auf einem im Februar 2017 nicht mehr dem Stand der Technik entsprechenden Server durchgeführt. Die RPS und TPR der getesteten PHP-Scripte sollten auf einem im Produktiv-Einsatz befindlichen Webserver deutlich besser sein.

Nächster Tipp