Assoziatives-Array vs. Objekt - Benchmark

Dieses Thema im Forum "Webentwicklung" wurde erstellt von Murdoc, 27. Mai 2014 .

Schlagworte:
  1. 27. Mai 2014
    Zuletzt bearbeitet: 27. Mai 2014
    Ich habe aus div. Gründen mal einen kleinen Benchmark erstellt um zu ermitteln wie groß der Unterschied zwischen Arrays und Objekten bei lesenden und schreibenden Zugriffen ist.

    Hierfür habe ich ein ein "kleines" Script erstellt: Pastebin.com

    Legende:
    • array -> normaler array
    • object -> eine Instanz der Klasse stdclass
    • object (fix) -> eine Instanz der Klasse stdclass
      Der Zugriff auf die Attribute ist hardcoded.
    • array-ref -> array wurde als Referenz an eine Funktion übergeben
    • array-arg -> array wurde als Wert (Value) an eine Funktion übergeben (copy-on-write wird provoziert)
    • wrapper 1 -> eine Instanz einer Klasse mit "magic" Methoden, welches intern einen array verwendet
    • wrapper 2 -> eine Instanz einer Klasse mit "magic" Methoden, welches nach und nach Attribute die gelesen/schrieben werden setzt
    • wrapper 3 -> eine Instanz einer Klasse die alle Attribute die gelesen/schrieben werden bereits vordefiniert hat
    • wrapper 3 (fix) -> eine Instanz einer Klasse die alle Attribute die gelesen/schrieben werden bereits vordefiniert hat.
      Der Zugriff auf die Attribute ist hardcoded.
    • wrapper 4 -> eine Instanz einer Klasse die alle Attribute die gelesen/schrieben werden bereits vordefiniert hat.
      Der Zugriff auf die Attribute erfolgt über getter und setter Methoden.

    Code der drei Wrapper-Klassen:
    Spoiler
    PHP:
    class  Wrapper1  implements  IteratorAggregate  {
      private 
    $mem  = [];
      
      function 
    __get ( $key ) {
        return 
    $this -> mem [ $key ];
      }
      
      function 
    __set ( $key $val ) {
        
    $this -> mem [ $key ] =  $val ;
      }
      
      function 
    __isset ( $key ) {
        return isset (
    $this -> mem [ $key ]);
      }
      
      public function 
    getIterator () {
        return new 
    ArrayIterator ( $this -> mem );
      }
    }

    class 
    Wrapper2  {
      function 
    __get ( $key ) {
        return 
    null ;
      }
      
      function 
    __set ( $key $val ) {
        
    $this -> $key  $val ;
      }
      
      function 
    __isset ( $key ) {
        return 
    false ;
      }
    }

    class 
    Wrapper3  {
      public 
    $a = 0 $b = 0 $c = 0 $d = 0 $e = 0 $f = 0 $g = 0 ;
      public 
    $h = 0 $i = 0 $j = 0 $k = 0 $l = 0 $m = 0 $n = 0 ;
      public 
    $o = 0 $p = 0 $q = 0 $r = 0 $s = 0 $t = 0 $u = 0 ;
      public 
    $v = 0 $w = 0 $x = 0 $y = 0 $z = 0 ;
    }

    class 
    Wrapper4  {
      public 
    $a = 0 $b = 0 $c = 0 $d = 0 $e = 0 $f = 0 $g = 0 ;
      public 
    $h = 0 $i = 0 $j = 0 $k = 0 $l = 0 $m = 0 $n = 0 ;
      public 
    $o = 0 $p = 0 $q = 0 $r = 0 $s = 0 $t = 0 $u = 0 ;
      public 
    $v = 0 $w = 0 $x = 0 $y = 0 $z = 0 ;
      
      public function 
    set ( $k $v ) {
        
    $this -> $k  $v ;
      }
      
      public function 
    get ( $k ) {
        return 
    $this -> $k ;
      }
      
      public function 
    has ( $k ) {
        return !empty (
    $this -> $k );
      }
    }

    Schema (1000 mal)
    1. Schlüssel "h" bis "t" schreiben
    2. Schlüssel "a" bis "z" lesen und schreiben falls diese leer sind
    3. Alle Schlüssel nochmal lesen


    Das Ergebnis:
    Code:
    using a new value for all calls
    array: 0.015790910720825s
    object: 0.019371101856232s
    object (fix): 0.011160640716553s
    wrapper 1: 0.055833201408386s
    wrapper 2: 0.041082346439362s
    wrapper 3: 0.019991140365601s
    wrapper 3 (fix): 0.01124064207077s
    wrapper 4: 0.036492087841034s
    
    using a new created reference as argument for all
    array-ref: 0.016140930652618s
    array-arg: 0.0164009308815s
    object : 0.020071148872375s
    object (fix): 0.011620669364929s
    wrapper 1: 0.058123321533203s
    wrapper 2: 0.042702448368073s
    wrapper 3: 0.020651180744171s
    wrapper 3 (fix): 0.011720671653748s
    wrapper 4: 0.037342131137848s
    
    using the same reference as argument for all calls
    array-ref: 0.012670722007751s
    array-arg: 0.015990920066833s
    object: 0.016700940132141s
    object (fix): 0.0083904719352722s
    wrapper 1: 0.04807275056839s
    wrapper 2: 0.016450939178467s
    wrapper 3: 0.015830910205841s
    wrapper 3 (fix): 0.0071604084968567s
    wrapper 4: 0.028211607933044s
    
    using the same reference for all calls (global)
    array: 0.012590732574463s
    object: 0.016200919151306s
    object (fix): 0.0080104613304138s
    wrapper 1: 0.050002858638763s
    wrapper 2: 0.016260931491852s
    wrapper 3: 0.01558089017868s
    wrapper 3 (fix): 0.0068503999710083s
    wrapper 4: 0.030301728248596s
    System: AMD Phenom 2 x4 @3.2 ghz
    PHP 5.5.9 (cli) (built: Feb 5 2014 13:02:39)


    Beobachtung:

    1. Array (lesen/schreiben in etwa gleich mit Objekten, Iteration um einiges schneller)
    2. Objekt mit vordefinierten Attributen (dynamischer Zugriff langsamer, hardcoded schneller als Array!)
    3. Leeres Objekt (dynamischer Zugriff langsamer, hardcoded schneller als Array!)
    4. Objekt das sich mittels "magic" Methoden selbst modifiziert (nach wiederholter Verwendung mit leeren Objekten identisch)
    5. Objekt mit getter/setter Methoden
    6. Objekt mit "magic" Methoden und internem Array zum speichern der Daten

    Bemerkenswertes:
    • Getter und setter Methoden sind schneller als __get() und __set()! (Hätte ich nicht gedacht)
    • Wenn man keinen dynamischen Zugriff auf die Attribute braucht sind simple Objekte bei
      wiederholter Verwendung fast doppelt so schnell als Arrays (!!!)
    • IteratorAggregate stinkt richtig ab im Vergleich zu simplen Objekten/Arrays (War aber zu erwarten)


    Fazit:

    Im Grunde kann man sagen, dass es keine große Rolle spielt ob man einen Array oder ein Objekt verwendet, solange man nicht versucht die Attribute irgendwie zu "emulieren" (u.a. auch durch getter/setter).

    Bei überwiegend dynamischen Zugriff (Schlüssel ist eine Variable): Array
    Bei überwiegend direktem Zugriff (Schlüssel ist konstant [egal ob vordefiniert oder nicht]): Objekt

    Ein "netter" Nebeneffekt:

    PHP:
    class  Foo 
      private 
    $bar  'whops' ;
    }

    $foo  = new  Foo ;
    foreach (
    $foo  as  $val ) print  $val // whops
     
  2. 27. Mai 2014
    AW: Assoziatives-Array vs. Object - Benchmark

    bei der auswerting sind array ca 10-15% schneller als objekte (0.019 zu 0.016s), die ressourcen vermutlich auch. je nach dem wie viele arrays oder objecte genutzt werden summiert sich das dann am ende doch auch?
     
  3. 27. Mai 2014
    Zuletzt bearbeitet: 27. Mai 2014
    AW: Assoziatives-Array vs. Object - Benchmark

    Objekte und Arrays nutzen intern die selben Datenstrukturen (HashTable), doch haben Objekte noch einiges mehr an Daten.

    Ein Objekt setzt sich aus folgendem zusammen:
    • Eine Referenz zur Klasse
    • Eine Liste mit Attributen (HashTable wie in einem Array)
    • Eine weitere Liste mit Attributen für den optimierten Zugriff
      (deshalb ist der hardcoded Zugriff auf Attribute schneller als bei Arrays).
    • Eine weitere HashTable für "guards" (um in __set() / __get() Rekursionen zu vermeiden).

    Wie viel Bytes ein Objekt nun mehr konsumiert als ein Array kann ich dir nicht sagen.
    Sollte sich aber in Grenzen halten.

    Nach überfliegen des Codes:
    3 Pointer á 4 Byte + sizeof(HashTable) + (Ein paar HashTable Buckets) + sizeof(zend_class_entry)

    Methoden werden btw. nicht beim Objekt gespeichert.

    ** php-src/Zend/zend.h at master · php/php-src · GitHub
    ** php-src/Zend/zend.h at master · php/php-src · GitHub
     
  4. Video Script

    Videos zum Themenbereich

    * gefundene Videos auf YouTube, anhand der Überschrift.