Große PHP-Arrays, SPL und Sessions

Folgende Problemstellung: eine große Datenmenge wird auf einmal abgefragt, soll aber nicht direkt komplett an den Client gesendet werden, also wird sie in der Session zwischengespeichert. Vielleicht im allgemeinen nicht die geschickteste Lösung, in meinem Fall fielen die Nachteile jedoch nicht ins Gewicht. “Groß” bedeutete dabei im Bereich von 10-50 MB in 50K-100K Datensätzen.

Das ist nun leider eine Menge, bei der PHP-Arrays nur noch mit Vorsicht einzusetzen sind. Der Flaschenhals war in diesem Fall array_shift(), womit Einträge aus dem in der Session befindlichen Arrays entnommen wurden. Was läge da näher, als auf eine der SPL-Datenstrukturen zurückzugreifen? Leider sind sowohl SplStack als auch SplFixedArray nicht serialisierbar und somit nicht ohne Weiteres mit Sessions zu gebrauchen.

Dies lässt sich nachrüsten, dabei muss allerdings doch wieder auf PHP-Arrays zurückgegriffen werden. Mit dem Performance-Verlust beim Serialisieren und Deserialisieren erkauft man sich allerdings eine deutlich effizientere Daten-Verarbeitung. In meinem Fall war SplStack bzw. SplDoublyLinkedList perfekt, da die Daten nur noch der Reihe nach abgeholt werden sollten. Die Erweiterung sieht wie folgt aus:

Serialisierbare SPL-Datenstruktur

class SerializableList extends SplDoublyLinkedList
{
    private $_data;
    
    public function __sleep()
    {
        $this->_data = array();
        
        $this->rewind();
        
        while ($this->valid()) {
            $this->_data[] = $this->current();
            $this->next();
        }
        
        return array('_data');
    }
    
    public function __wakeup()
    {
        foreach ($this->_data as $row) {
            $this->push($row);
        }
        
        $this->_data = array ();
    }
}

Kurz erkärt

Beim Serialisieren (__sleep()) wird die Datenstruktur in ein PHP-Array (im Attribut _data) konvertiert und mit return array('_data') festgelegt, dass genau dieses Attribut serialisiert werden soll. Beim Deserialisieren (__wakeup()) ist _data wiederhergestellt und kann zurück konvertiert werden. Anschließend wird mit $this->_data = array() der Speicher wieder freigegeben.

Vorsicht

Ob diese Lösung im konkreten Fall sinnvoll ist, kann nur durch eigene Messungen ermittelt werden. Dabei sollte vor allen Dingen darauf geachtet werden, die Anzahl der Serialisierungsvorgänge so gering wie möglich zu halten, denn wie schon gesagt sind die durch die zusätzliche Konvertierung teurer als zuvor. Als Beispiel: Bei 8 Abfragen a 10000 Datensätzen war meine Anwendung mindestens 20 mal schneller als bei 80 Abfragen a 1000 Datensätzen. Und beide Varianten schlagen die Implementierung nur mit PHP-Arrays um Längen.

Eine Herausforderung für weitere Optimierung wäre es noch, einen eigenen Serialisierer zu schreiben, der ohne PHP-Array auskommt. Das wäre allerdings eher etwas für die PECL, sprich direkt in C gehackt. In PHP selbst sehe ich da wenig Hoffnung in puncto Effizienz.

PHP Iterator, IteratorAggregate mit current(), next() etc. nutzen

Einige Anmerkungen zu PHP Traversables

Iteration mit Funktionen

Traversables können mit foreach iteriert werden, jedoch nicht im allgemeinen mit Array-Iterations Funktionen wie reset() und next().

Iterator Objekte können auch auch mit folgenden Funktionen verwendet werden:

  • current()
  • next()
  • prev()
  • reset()
  • end()

Achtung: Es ist zwar möglich, allen diesen Funktionen IteratorAggregate Objekte zu übergeben (wie auch jedes andere Objekt), die Iterator-Funktionalität wird dabei aber nicht genutzt, sondern es wird über die Attribute des Objekts iteriert.

Das selbe gilt für Iterator Objekte mit each()! Dieses sollte generell nicht für Objekte benutzt werden, da hilft weder ArrayAccess noch Iterator. Es funktioniert im allgemeinen nie wie erwünscht.

Überprüfung von unbekannter Variable auf Iterierbarkeit mit foreach

$a implements Traversable || is_array($a);
  • Traversable ist vor Iterator zu bevorzugen da auch IteratorAggregate und andere interne Iteratoren damit erkannt werden.
  • Arrays sind keine Traversables, da keine Objekte

Will man allerdings volle Flexibilität genügt:

is_object($a) || is_array($a)
  • PHP kann beliebige Objekte als Iterator benutzen und iteriert dabei über die öffentlichen Attribute!