String mit array-strings durchsuchen – Performance der Funktionen

Ich suche in einem String nach Teilstrings, die ich in einem Array habe. Die Funktion array_search macht leider genau das Gegenteil, deshalb brauche ich an dieser Stelle eine eigene Funktion.

Doch welche String-Suchfunktion verwende ich?
Zur Auswahl stehen folgende Funktionen, sollte ich was vergessen haben bitte ich um kurze Meldung:

Hier mal der Versuch einer Messung, wie schnell die Funktionen ihre Arbeit erledigen. Dazu lasse ich alle Funktionen nacheinander den String durchsuchen, das ganze mache ich je Funktion 9999 mal, um eine Tendenz sichtbar zu machen. Und ja: Quick and Dirty Code, ich bin mir dessen bewusst.


$arrHaystack = array( 'hallo',
'welt',
'foobar',
'php5',
'suchfunktion',
'bob',
'und',
'alice',
'suchen',
'strings',
'in',
'arrays',
'Lorem','ipsum','dolor','sit','amet','consetetur','sadipscing','elitr','sed','diam','nonumy','eirmod','tempor','invidunt','ut','labore','et','dolore','magna','aliquyam','erat','sed','diam','voluptua','At','vero','eos','et','accusam','et','justo','duo','dolores','et','ea','rebum','Stet','clita','kasd','gubergren','no','sea','takimata','sanctus','est','Lorem','ipsum','dolor','sit','amet','Lorem','ipsum','dolor','sit','amet','consetetur','sadipscing','elitr','sed','diam','nonumy','eirmod','tempor','invidunt','ut','labore','et','dolore','magna','aliquyam','erat','sed','diam','voluptua','At','vero','eos','et','accusam','et','justo','duo','dolores','et','ea','rebum','Stet','clita','kasd','gubergren','no','sea','takimata','sanctus','est','Lorem','ipsum','dolor','sit','amet','Lorem','ipsum','dolor','sit','amet','consetetur','sadipscing','elitr','sed','diam','nonumy','eirmod','tempor','invidunt','ut','labore','et','dolore','magna','aliquyam','erat','sed','diam','voluptua','At','vero','eos','et','accusam','et','justo','duo','dolores','et','ea','rebum','Stet','clita','kasd','gubergren','no','sea','takimata','sanctus','est','Lorem','ipsum','dolor','sit','amet','Duis','autem','vel','eum','iriure','dolor','in','hendrerit','in','vulputate','velit','esse','molestie','consequat','vel','illum','dolore','eu','feugiat','nulla','facilisis','at','vero','eros','et','accumsan','et','iusto','odio','dignissim','qui','blandit','praesent','luptatum','zzril','delenit','augue','duis','dolore','te','feugait','nulla','facilisi','Lorem','ipsum','dolor','sit','amet','consectetuer','adipiscing','elit','sed','diam','nonummy','nibh','euismod','tincidunt','ut','laoreet','dolore','magna','aliquam','erat','volutpat');

$iIterations = 100;
$suchsatz = array('meas magna','mia diam','im garten sind bob und alice','elixier des nichts');

$iMax = count($suchsatz);
$iStart = microtime(1);

for ($i=0;$i<$iIterations;$i++)
{
$iTake = mt_rand(0, $iMax);
foreach ($arrHaystack AS $key => $value)
{
if (mb_substr_count($suchsatz[$iTake], $value)>0)
{
break;
}
}
shuffle($arrHaystack);
}
$iEnd1 = microtime(1);

for ($i=0;$i<$iIterations;$i++)
{
$iTake = mt_rand(0, $iMax);
foreach ($arrHaystack AS $key => $value)
{
if (mb_stripos($suchsatz[$iTake], $value)!==FALSE)
{
break;
}
}
shuffle($arrHaystack);
}
$iEnd2 = microtime(1);

for ($i=0;$i<$iIterations;$i++)
{
$iTake = mt_rand(0, $iMax);
foreach ($arrHaystack AS $key => $value)
{
if (mb_stristr($suchsatz[$iTake], $value)!==FALSE)
{
break;
}
}
shuffle($arrHaystack);
}
$iEnd3 = microtime(1);

for ($i=0;$i<$iIterations;$i++)
{
$iTake = mt_rand(0, $iMax);
foreach ($arrHaystack AS $key => $value)
{
if (preg_match('/'.$value.'/i', $suchsatz[$iTake])>0)
{
break;
}
}
shuffle($arrHaystack);
}
$iEnd4 = microtime(1);

printf('--------------------------'."\n");
printf('mb_substr_count() -> %f'."\n",$iEnd1-$iStart);
printf('mb_stripos() -> %f'."\n",$iEnd2-$iEnd1);
printf('mb_stristr() -> %f'."\n",$iEnd3-$iEnd2);
printf('preg_match() -> %f'."\n",$iEnd4-$iEnd3);
printf('--------------------------'."\n");

Hier die Ergebnisse:
————————–
mb_substr_count() -> 15.756097
mb_stripos -> 16.490042
mb_stristr -> 16.938911
preg_match -> 14.681287
————————–

Schön zu sehen, dass meine intuitiv gewählte Funktion mb_substr_count() die Arbeit anscheinend am schnellsten erledigt. Überraschend für mich, dass der regex Platz 2 belegt und dass die anderen Funktionen einen so großen Abstand zu den beiden ersten Plätzen aufweisen.

Update 08.01.2011:
Habe den Code mal den Kommentaren angepasst. Das Array der Wörter ist nun 240 Felder groß, es wird nun auch zufällig einer von 4 Sätzen durchsucht. Nach jedem Treffer wird das Wort-Array zufällig neu ‚geschüttelt‘. Das sollte alle Caching-Sachen ausschalten.

Dabei bildet sich wirklich ein neues Bild (bei 100 durchläufen). Platz 1 geht nun an den regex, Platz 2 an substr_count, dicht gefolgt von den beiden stri-Funktionen. Insgesamt ist aber kein nennenswerter Abstand mehr vorhanden (im Gegensatz zum ersten Test), weshalb ich sagen kann: Nehmt, womit ihr am besten klarkommt. In High-Performance Umgebungen folgt dem Testergebnis.

5 Gedanken zu „String mit array-strings durchsuchen – Performance der Funktionen

  1. rr

    Deine Messung ist nicht repräsentativ, weil Du auf viel zu wenig Werten misst. Am besten misst man mit einer Reihe von 100, 1000 und 10000 Werten. Messergebnisse können sich da noch verschieben.

    Antworten
  2. Oliver @ AnonSphere

    Dein Code springt aus der Schleife, wenn etwas gefunden wurde. So werden aber u. U. unterschiedlich viele Werte verglichen (je nach Satz und Reihenfolge) und Du überspringst dann auch shuffle, was Dir das Ergebnis weiter verzerrt.

    Für aussagekräftige Ergebnisse müssen die breaks raus und das shuffle kann auch raus. Keine der Funktionen zieht Performancevorteile aus Caching. Die unterschiedliche Länge der Testsätze kann auch die Ergebnisse verändern, die müssten eigentlich auch gleich lang sein.

    Was mich aber am meisten wundert, warum benutzt Du Multibytefunktionen? stripos ist wesentlich schneller als alle getesten Funktionen und ist in den allermeisten Fällen völlig ausreichen. In diesem Test hier auf jeden Fall.

    Antworten

Schreibe einen Kommentar zu Fabian Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert