strncmp() ist schneller als substr()

In diversen Foren und Blogs wird schon seit 2007 geschrieben, dass strncmp() schneller als substr() sei. Und zwar, stolze 20%!!! Ist das denn so? Stimmt die Berechnung auch? Nun, wie wir alle es wissen, Codefragmente die viel mit string-Typen zu tun haben, sind nicht Ressourcen-schonend und können den gesamten Prozess sehr verlangsamen. Um das Ganze besser vergleichen zu können, habe ich diese einfache Klasse „Benchmark_Substr_vs_Strncmp“ geschrieben. Mittels der oben genannten Funktionen wird versucht aus einem Test-Array, mit zwanzig Einträgen, den Array-Key „GETME__“ zu ermitteln. Diese Suche wird mit beiden Funktionen je 10.000-mal ausgeführt. Bei jedem Testlauf wird die Dauer für die Suche in eine CSV Datei festgehalten.

< ?php
class Benchmark_Substr_vs_Strncmp
{
    const LOOP = 10000;

    protected $_resultSubstr = 0;
    protected $_resultStrncmp = 0;

    protected $_testData = array(
        'GETME__3' => 3,
        'GETME__4' => 4,
        'DONT_GETME__1' => 1,
        'DONT_GETME__2' => 2,
        'DONT_GETME__3' => 3,
        'DONT_GETME__4' => 4,
        'GETME__5' => 5,
        'GETME__6' => 6,
        'DONT_GETME__6' => 6,
        'DONT_GETME__7' => 7,
        'DONT_GETME__8' => 8,
        'GETME__7' => 7,
        'GETME__8' => 8,
        'GETME__9' => 9,
        'DONT_GETME__5' => 5,
        'DONT_GETME__9' => 9,
        'GETME__1' => 1,
        'GETME__2' => 2,
		'GETME__1234' => 1,
        'GETME__2234' => 2,
    );

    protected function _substr_data()
    {
        $out = array();

        foreach ($this->_testData as $key => $val)
        {
            if (substr($key, 0, 7) == 'GETME__')
            {
                $out[$key] = $val;
            }
        }
    }

    protected function _strncmp_data()
    {
        $out = array();

        foreach ($this->_testData as $key => $val)
        {
            if (!strncmp($key, 'GETME__', 7))
            {
                $out[$key] = $val;
            }
        }
    }

    public function runBenchmarkOfStrncmp()
    {
        $beginn = microtime(true);

        $i = 0;

        while($i < self::LOOP)
        {
            $this->_strncmp_data();

            ++$i;
        }

        $this->_resultStrncmp = sprintf('%.3f', microtime(true) - $beginn);
    }

    public function runBenchmarkOfSubstr()
    {
        $beginn = microtime(true);

        $i = 0;

        while($i < self::LOOP)
        {
            $this->_substr_data();

            ++$i;
        }

        $this->_resultSubstr = sprintf('%.3f', microtime(true) - $beginn);
    }

    public function saveData()
    {
        $fp = fopen(dirname(__FILE__).'benchmark_substr_vs_strncmp.csv', 'a');

        fputcsv($fp, array($this->_resultSubstr, $this->_resultStrncmp), ';');
        fclose($fp);

        $this->_resultSubstr = 0;
        $this->_resultStrncmp = 0;

        print '.';
    }
}

Hier ein Testszenario:

$benchmark = new Benchmark_Substr_vs_Strncmp();
$benchmark->runBenchmarkOfSubstr();
$benchmark->runBenchmarkOfStrncmp();
$benchmark->saveData();

Für den Test habe ich auf das System dafür gesorgt, dass dieser Test die einzige Klasse im System ist die was mit PHP macht. Hier das Ergebnis nach zwanzig Testszenarien.

Substr	Strncmp		Differenz in Sek.	Differenz in %
--------------------------------------------------------------
0,227	0,2		0,027				11,89
0,222	0,199		0,023				10,36
0,216	0,198		0,018				8,33
0,217	0,199		0,018				8,29
0,232	0,213		0,019				8,19
0,217	0,201		0,016				7,37
0,223	0,207		0,016				7,17
0,213	0,199		0,014				6,57
0,21	0,198		0,012				5,71
0,215	0,204		0,011				5,12
0,207	0,198		0,009				4,35
0,205	0,198		0,007				3,41
0,207	0,202		0,005				2,42
0,2	0,198		0,002				1,00
0,209	0,207		0,002				0,96
0,199	0,198		0,001				0,50
0,202	0,201		0,001				0,50
0,206	0,205		0,001				0,49
0,199	0,199		0				0,00

Der Aufruf von substr() ist zwar relativ schnell, aber die widerholende Ausführung summiert sich. Ich habe bei meiner Berechnung die besagten 20% nicht erreicht, jedoch stolze 5%. Liegt es eventuell daran dass PHP5 schneller als PHP4 ist? Wie auch immer, ich bin mir sicher, dass man gut behaupten darf, dass schlecht implementierte Zeichenkettenanalysen in umfassenden Anwendungen oder Frameworks einen großen Teil der Zeit auf diese Funktion verwenden. Die strncmp() ist nur dann ein natürlicher Ersatz für substr(), wenn es um Zeichenvergleich am Anfang von Zeichenketten, handelt.

Die substr() Funktion muss erst ihren Rückgabewert zuweisen und schreiben, und dann einen Vergleich durchführen, während strncmp() nur Zeichen für Zeichen vergleicht. Wie die Speicherverwaltung bei PHP im Einzelnen funktioniert, das bleibt uns verborgen, dennoch werden wir die Zeit für die Zuweisung immer deutlich spüren.

Frage: Im Zend-Framework wird substr() alleine 209-mal verwendet. Der Großteil davon wird verwendet um Zeichenketten am Anfang der Zeichenkette zu vergleichen. Und strncmp(), hingegen wird nur 2-mal verwendet. Was meint Ihr, würde ein Umbau auf strncmp(), eine Anwendung die komplett auf Zend-Framework aufbaut, beschleunigen?

5 thoughts on “strncmp() ist schneller als substr()

  1. Interessanter Versuch, ich habe bisher immer nur substr benutzt und kannte strncmp noch garnicht.
    Allerdings macht es wohl nur in wenigen Ausnahmesituationen Sinn, substr durch strncmp zu ersetzen.
    5% oder gar 20% klingt zwar erstmal viel, aber bei deinem Test macht das bei 200.000 Durchläufen ja nur ca. 10 Millisekunden aus – bei „normaler“ benutzung merkt man also keinen Unterschied ;)

  2. Ich würde das mal getrost unter Micro Optimization ablegen. Bei 5% muss man schon ein paar tausend Strings vergleichen, dass sich das wirklich rechnet. Da kann man auch dran gehen und alle Hochkommata in echo’s ersetzen. Trotzdem nett mal drüber gelesen zu haben.

    1. @David Müller
      Bei diesem Test habe ich einen Unterschied bis von 5% bis 11% erreichen können. Habe den selben Test auf einem Server ausgeführt, der sehr viel mit PHP zu tun hat, und habe dort unterscheide von 6% bis 32% festgestellt. Ja, es bleibt spannend :-)

Schreibe einen Kommentar

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