MVCLite scheint wirklich leichtgewichtig zu sein

Saturday, 13 October 2007, 08:51 von Blackflash

Gestern war es mal wieder so weit und ich wollte ein wenig mehr Performance aus MVCLite rauskitzeln. Der Ansatz war banal wie auch effektiv: Ich speichere ein Objekt einfach im RAM. Dazu habe ich die APC-Funktionen apc_store zum Ablegen und apc_fetch zum Laden der Daten genutzt. Die Basis sah folgendermaßen aus:

abstract class MVCLite_Core { public function getIdentifier () { // gibt den Klassennamen zurück } public function __destruct () { apc_store($this->getIdentifier(), $this); } public static function find ($identifier) { $result = apc_fetch($identifier); if($result === false) { throw new Exception(); } return $result; } }

Damit wird das Objekt transparent via APC im RAM gespeichert. Zum Holen wird die statische find-Methode genutzt, die eine Exception wirft, wenn das Objekt nicht vorhanden ist. Somit ist es relativ einfach möglich, zwischengespeicherte Objekte zu benutzen.

class Foobar extends MVCLite_Core { } try { $foobar = MVCLite_Core::find('Foobar'); } catch (Exception $e) { $foobar = new Foobar(); }

Es macht also keinen Unterschied, mit welcher Version wir letztendlich arbeiten, außer, dass eine Version aus dem RAM kommt. Natürlich ist dieses Schreiben via APC im Ggs. zum Erstellen von Objekten sehr zeitaufwändig, aber bei großen Objekten lohnt es sich definitiv.
Um zu schauen, ob es sich überhaupt lohnt, habe ich ein kleines Benchmark-Skript angefertigt, das eine grobe Tendenz zeigen soll.

// die instanzierte Klasse (Foobar.php) class Foobar { public $array = array(); public function __construct () { for($i = 0; $i < 100; $i++) { $this->_array[] = md5($i); } } } // Benchmark Teil 1 define('INTERVAL', 10); require 'Foobar.php'; $start = microtime(true); $counter = 0; do { $tmp = apc_fetch('Foobar'); if($tmp === false) { $tmp = new Foobar(); } apc_store('Foobar', $tmp); $counter++; } while(microtime(true) - $start < INTERVAL); echo "Iterations: $counter/" . INTERVAL . "s
"; apc_delete('Foobar');

// Benchmark Teil 2 $start = microtime(true); $counter = 0; do { $tmp = new Foobar(); $counter++; } while(microtime(true) - $start < INTERVAL); echo "Iterations: $counter/" . INTERVAL . "s";

Ich generiere hierbei eine bestimmte Anzahl von Hashes um den Aufwand der Initialisierung zu simulieren. 10s lang wird überprüft, wie viele Iterationen in dem Zeitraum möglich sind. Natürlich ist es undenkbar, die genaue Anzahl von Iterationen in dem Zeitraum herauszufinden - zumindest mit einem gerechtfertigten Aufwand -, aber mein Ziel ist ja ganz klar die Tendenz.

  • 100 Hashes
    • APC: 58.378/10s
    • Instanzierung: 12.550/10s
  • 10 Hashes
    • APC: 142.968/10s
    • Instanzierung: 106.351/10s
  • 1 Hash
    • APC: 150.731/10s
    • Instanzierung: 429.303/10s

Bereits bei 10 Hashes lohnt sich die Nutzung von APC. Wie gesagt, es ist nur eine Tendenz. Als ich dann die bereits beschriebene Implementation in MVCLite integriert hatte, habe ich ApacheBench darüber laufen lassen und siehe da, es ist sogar langsamer als mit der direkten Instanzierung! Was heißt das? Es bedeutet, dass MVCLite sein Ziel, eine leichtgewichtige Implementation zu bieten, nicht verfehlt hat und darüber bin ich glücklich. Derzeit brauche ich also keinen internen Mechanismus zum Zwischenspeichern im RAM, zumindest nicht im Kern. Für das Zwischenspeichern außerhalb des Kerns werde ich wahrscheinlich Zend_Cache benutzen, da man damit komplexere Mechanismen sehr leicht umsetzen kann.

Allerdings gab es neben der Verringerung der Geschwindigkeit auch noch einige andere Probleme mit APC. Z.B. speichert APC normale Objekte nur in der Tiefe von 1, was heißt, dass komplexere Datenstrukturen nicht effektiv genug gespeichert werden können. Die vorgeschlagene Lösung, die Objekte zu serialisieren ist okay, aber nicht optimal. Denn dadurch verliere ich die Referenzen und ein Objekt existiert mehrfach. Darunter leidet natürlich die Robustheit von MVCLite, was ich tunlichst vermeiden will. Der Code müsste dafür umgeschrieben werden, worunter natürlich die Transparenz leiden würde. Eine Lösung könnte das Speichern des gesamten Kerns in einer Variable sein. Man könnte dann die gesamten auftretenden Referenzen suchen und eliminieren, sodass dieselben Objekte nur an einer Stelle auftauchen. Beim Laden der gesamten Applikation muss es dann natürlich möglich sein, diese Referenzen wieder herzustellen. Aber dieses Prozedere ist ja anscheinend nicht notwendig...

Fazit? MVCLite ist auf einem guten Weg, aber ich werde mich sicherlich nicht ausruhen, sondern versuchen, exzessiv auf das Konzept der Lazy Initialization setzen, um unnötige Instanzierungen zu vermeiden. Dieses Konzept wird bereits in MVCLite_Db_PDO eingesetzt, damit eine Verbindung nur dann erstellt wird, wenn sie wirklich benötigt wird. Auch in der View-Helper-Registry ist dieses Konzept zu finden.

Kommentare


Kommentiere!

Your Name:


Your Email:


Your URL:


Spam Prevention:
Enter the text above into the box below.
If you are unable to read it, refresh the page.


Your Comment: