2012-05-23 10 views
9

C'è un grosso problema di prestazioni quando si usano gli array in-object come proprietà rispetto all'uso di una variabile php globale, perché?L'array di oggetti PHP non scala in modo lineare mentre gli array globali funzionano?

Per confrontare questo problema ho creato il seguente benchmark che memorizza un array sempre più grande con uno stdClass come nodo, due test sono stati eseguiti uno utilizzando una proprietà array in una classe e l'altro un array globale.

Il codice di test

ini_set('memory_limit', '2250M'); 
class MyTest { 
    public $storage = []; 
    public function push(){ 
     $this->storage[] = [new stdClass()]; 
    } 
} 

echo "Testing Objects".PHP_EOL; 
for($size = 1000; $size < 5000000; $size *= 2) { 
    $start = milliseconds(); 
    for ($a=new MyTest(), $i=0;$i<$size;$i++) { 
     $a->push(); 
    } 
    $end = milliseconds(); 
    echo "Array Size $size".PHP_EOL; 
    echo $end - $start . " milliseconds to perform".PHP_EOL; 
} 
echo "================".PHP_EOL; 
echo "Testing Array".PHP_EOL; 
for($size = 1000; $size < 5000000; $size *= 2) { 
    $start = milliseconds(); 
    for ($a=[], $i=0;$i<$size;$i++) { 
     $a[] = [new stdClass()]; 
    } 
    $end = milliseconds(); 
    echo "Array Size $size".PHP_EOL; 
    echo $end - $start . " milliseconds to perform".PHP_EOL; 
} 

E i risultati scioccanti:

Testing Objects 
Array Size 1000 
2 milliseconds to perform 
Array Size 2000 
3 milliseconds to perform 
Array Size 4000 
6 milliseconds to perform 
Array Size 8000 
12 milliseconds to perform 
Array Size 16000 
35 milliseconds to perform 
Array Size 32000 
97 milliseconds to perform 
Array Size 64000 
246 milliseconds to perform 
Array Size 128000 
677 milliseconds to perform 
Array Size 256000 
2271 milliseconds to perform 
Array Size 512000 
9244 milliseconds to perform 
Array Size 1024000 
31186 milliseconds to perform 
Array Size 2048000 
116123 milliseconds to perform 
Array Size 4096000 
495588 milliseconds to perform 
================ 
Testing Array 
Array Size 1000 
1 milliseconds to perform 
Array Size 2000 
2 milliseconds to perform 
Array Size 4000 
4 milliseconds to perform 
Array Size 8000 
8 milliseconds to perform 
Array Size 16000 
28 milliseconds to perform 
Array Size 32000 
61 milliseconds to perform 
Array Size 64000 
114 milliseconds to perform 
Array Size 128000 
245 milliseconds to perform 
Array Size 256000 
494 milliseconds to perform 
Array Size 512000 
970 milliseconds to perform 
Array Size 1024000 
2003 milliseconds to perform 
Array Size 2048000 
4241 milliseconds to perform 
Array Size 4096000 
14260 milliseconds to perform 

Ora oltre l'overhead ovvio dell'oggetto stesso della bilancia di proprietà oggetto array terribilmente a volte assumono 3 chiama - 4 volte più a lungo quando la matrice diventa più grande ma questo non è il caso con la variabile di array globale standard.

Qualche idea o risposta riguardo a questo problema e questo è un possibile bug del motore PHP?

+0

Dubbi che si tratta di un problema di array, ma potrebbe trattarsi di un overhead OOP, dal momento che stai costruendo quella matrice all'interno di un oggetto - un sacco di overhead OOP. Se si sostituisce temporaneamente tale membro oggetto con una variabile globale standard, la modifica delle prestazioni non cambia affatto? –

+0

Potresti per favore ridurre questa domanda al problema reale, lasciando fuori tutta la roba del prggmr? Rende la domanda difficile da capire. – NikiC

+0

@MarcB C'è un cambiamento ma non è un cambiamento significativo (cambiando la cronologia per memorizzare solo true '' '$ this -> _ event_history [] = [true]' '' produce 9532 al secondo in 10 secondi. @NikiC Do hai un consiglio su come potresti sistemare questo in basso? – Nick

risposta

6

ho testato il codice su PHP 5.3.9. Per farlo dovevo tradurre [] a array(), e ho anche dovuto correggere la linea n. 12: da $a=new MyTest($size), a $mytest=new MyTest($size) (BTW, l'argomento del costruttore viene silenziosamente ignorato, divertente). Ho anche aggiunto di questo codice:

echo "================".PHP_EOL; 
echo "Testing Function".PHP_EOL; 
for($size = 1000; $size < 1000000; $size *= 2) { 
    $start = milliseconds(); 
    for ($a=array(), $i=0;$i<$size;$i++) { 
     my_push($a); 
    } 
    $end = milliseconds(); 
    echo "Array Size $size".PHP_EOL; 
    echo $end - $start . " milliseconds to perform".PHP_EOL; 
    echo "memory usage: ".memory_get_usage()." , real: ".memory_get_usage(true).PHP_EOL; 
} 

function my_push(&$a) 
{ 
    $a[] = array(new stdClass()); 
} 

ho aggiunto la linea di utilizzo della memoria per i loop nello stesso punto, ha aggiunto un unset($mytest); dopo il caso oggetto (per ottenere un registro di memoria più consistente), e anche sostituito il tuo 5000000 di con 1000000 perché ho solo 2 GB di RAM. Questo è quello che ho ottenuto:

Testing Objects 
Array Size 1000 
2 milliseconds to perform 
memory usage: 1666376 , real: 1835008 
Array Size 2000 
5 milliseconds to perform 
memory usage: 2063280 , real: 2097152 
Array Size 4000 
10 milliseconds to perform 
memory usage: 2857008 , real: 2883584 
Array Size 8000 
19 milliseconds to perform 
memory usage: 4444456 , real: 4718592 
Array Size 16000 
44 milliseconds to perform 
memory usage: 7619392 , real: 8126464 
Array Size 32000 
103 milliseconds to perform 
memory usage: 13969256 , real: 14417920 
Array Size 64000 
239 milliseconds to perform 
memory usage: 26668936 , real: 27262976 
Array Size 128000 
588 milliseconds to perform 
memory usage: 52068368 , real: 52690944 
Array Size 256000 
1714 milliseconds to perform 
memory usage: 102867104 , real: 103546880 
Array Size 512000 
5452 milliseconds to perform 
memory usage: 204464624 , real: 205258752 
================ 
Testing Array 
Array Size 1000 
1 milliseconds to perform 
memory usage: 18410640 , real: 20709376 
Array Size 2000 
4 milliseconds to perform 
memory usage: 18774760 , real: 20709376 
Array Size 4000 
7 milliseconds to perform 
memory usage: 19502976 , real: 20709376 
Array Size 8000 
13 milliseconds to perform 
memory usage: 20959360 , real: 21233664 
Array Size 16000 
29 milliseconds to perform 
memory usage: 23872176 , real: 24379392 
Array Size 32000 
61 milliseconds to perform 
memory usage: 29697720 , real: 30146560 
Array Size 64000 
124 milliseconds to perform 
memory usage: 41348856 , real: 41943040 
Array Size 128000 
280 milliseconds to perform 
memory usage: 64651088 , real: 65273856 
Array Size 256000 
534 milliseconds to perform 
memory usage: 111255536 , real: 111935488 
Array Size 512000 
1085 milliseconds to perform 
memory usage: 204464464 , real: 205258752 
================ 
Testing Function 
Array Size 1000 
357 milliseconds to perform 
memory usage: 18410696 , real: 22544384 
Array Size 2000 
4 milliseconds to perform 
memory usage: 18774768 , real: 22544384 
Array Size 4000 
9 milliseconds to perform 
memory usage: 19503008 , real: 22544384 
Array Size 8000 
17 milliseconds to perform 
memory usage: 20959392 , real: 22544384 
Array Size 16000 
36 milliseconds to perform 
memory usage: 23872208 , real: 24379392 
Array Size 32000 
89 milliseconds to perform 
memory usage: 29697720 , real: 30146560 
Array Size 64000 
224 milliseconds to perform 
memory usage: 41348888 , real: 41943040 
Array Size 128000 
529 milliseconds to perform 
memory usage: 64651088 , real: 65273856 
Array Size 256000 
1587 milliseconds to perform 
memory usage: 111255616 , real: 111935488 
Array Size 512000 
5244 milliseconds to perform 
memory usage: 204464512 , real: 205258752 

Come si può vedere, aggiungendo alla matrice all'interno di una chiamata di funzione costa quasi quanto (e ha lo stesso comportamento non-lineare come) a farlo all'interno della vostra chiamata al metodo originale. Una cosa si può dire con certezza:

Sono le chiamate di funzione che consumano il tempo della CPU!

Per quanto riguarda il comportamento non lineare, diventa davvero evidente solo al di sopra di una certa soglia. Mentre tutti e tre i casi hanno lo stesso comportamento di memoria (a causa della raccolta incompleta di gargabe questo è evidente solo tra il caso "plain array" e "array inside function", in questo log), è il "array inside method" e il " array all'interno della funzione "casi che hanno lo stesso comportamento nel tempo di esecuzione. Ciò significa che sono le stesse chiamate di funzione che causano un aumento non lineare nel tempo. Mi sembra che questo si possa dire:

La quantità di dati che si trova durante una chiamata di funzione influenza la sua durata.

Per verificare questa ho sostituito tutti $a[] con $a[0] e tutte 1000000 con 5000000 (per ottenere analoghi tempi di esecuzione totale) ed ottenuto questo output:

Testing Objects 
Array Size 1000 
2 milliseconds to perform 
memory usage: 1302672 , real: 1572864 
Array Size 2000 
4 milliseconds to perform 
memory usage: 1302672 , real: 1572864 
Array Size 4000 
8 milliseconds to perform 
memory usage: 1302672 , real: 1572864 
Array Size 8000 
15 milliseconds to perform 
memory usage: 1302672 , real: 1572864 
Array Size 16000 
31 milliseconds to perform 
memory usage: 1302672 , real: 1572864 
Array Size 32000 
62 milliseconds to perform 
memory usage: 1302672 , real: 1572864 
Array Size 64000 
123 milliseconds to perform 
memory usage: 1302672 , real: 1572864 
Array Size 128000 
246 milliseconds to perform 
memory usage: 1302672 , real: 1572864 
Array Size 256000 
493 milliseconds to perform 
memory usage: 1302672 , real: 1572864 
Array Size 512000 
985 milliseconds to perform 
memory usage: 1302672 , real: 1572864 
Array Size 1024000 
1978 milliseconds to perform 
memory usage: 1302672 , real: 1572864 
Array Size 2048000 
3965 milliseconds to perform 
memory usage: 1302672 , real: 1572864 
Array Size 4096000 
7905 milliseconds to perform 
memory usage: 1302672 , real: 1572864 
================ 
Testing Array 
Array Size 1000 
1 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 2000 
3 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 4000 
5 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 8000 
10 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 16000 
20 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 32000 
40 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 64000 
80 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 128000 
161 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 256000 
322 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 512000 
646 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 1024000 
1285 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 2048000 
2574 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 4096000 
5142 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
================ 
Testing Function 
Array Size 1000 
1 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 2000 
4 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 4000 
6 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 8000 
14 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 16000 
26 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 32000 
53 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 64000 
105 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 128000 
212 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 256000 
422 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 512000 
844 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 1024000 
1688 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 2048000 
3377 milliseconds to perform 
memory usage: 1302464 , real: 1572864 
Array Size 4096000 
6814 milliseconds to perform 
memory usage: 1302464 , real: 1572864 

nota come i tempi sono quasi perfettamente lineare ora. Ovviamente, la dimensione dell'array è bloccata a 1 ora. Si noti anche come le differenze dei tempi di esecuzione dei tre casi siano meno pronunciate di prima. Ricorda che l'operazione più interna è la stessa in tutti i casi.

Non ho intenzione di provare a spiegare tutto questo (raccolta di gargabee su chiamata di funzione? Frammentazione di memoria? ...?), Ma penso di aver comunque raccolto alcune informazioni utili, per tutti qui e per me stesso pure.

+1

Anche se non si aggiungeva 'unset ($ mytest);' tutto sarebbe più lento del 10% circa. – sanmai

+0

Ottima risposta qui ... questo richiede davvero un'immersione nella sorgente php poiché mi sembra che stia succedendo qualcosa quando si accede a variabili tra il loro contesto che sta causando un'enorme riduzione del tempo di utilizzo e considerando che sono solo puntatori e riferimenti (sono loro? non dovrebbero essere?) la dimensione dei dati non dovrebbe importare dal momento che nulla viene scritto da nessuna parte oltre ai dati aggiuntivi aggiunti su ... o forse questo è solo un incontro con alcune delle decisioni degli sviluppatori hanno preso quando hanno sviluppato la lingua che forse è passata inosservata ... – Nick

2

Non riesco a postare tutto questo in un commento, quindi questa è più un'osservazione che una risposta. Sembra che SplObjectStorage sia piuttosto lento. Anche che array_push è molto più veloce di $ array [] = 'item';

Disclaimer: Ci scusiamo per il codice sciatta :)

<?php 

$time = microtime(); 
$time = explode(' ', $time); 
$time = $time[1] + $time[0]; 
$start = $time; 

$iteration = 10000; 

switch ($_REQUEST['test']) 
{ 
    case 1: 
     $s = new SplObjectStorage(); 

     for ($i = 0; $i < $iteration; $i++) { 
      $obj = new stdClass; 
      $s[$obj] = 'test'; 
     } 
     break; 
    case 2: 

     $s = array(); 
     for ($i = 0; $i < $iteration; $i++) { 
      $obj = new stdClass; 
      $s[$i] = $obj; 
     } 
     break; 

    case 3: 
     class Test { 
      public $data = array(); 
     } 
     $s = new Test; 
     for ($i = 0; $i < $iteration; $i++) { 
      $obj = new stdClass; 
      $s->data[] = $obj; 
     } 
     break; 

    case 4: 
     class Test { 
      public static $data = array(); 
     } 
     $s = new Test; 
     for ($i = 0; $i < $iteration; $i++) { 
      $obj = new stdClass; 
      $s->data[] = $obj; 
     } 
     break; 
    case 5: 
     class Test { 
      public $data = array(); 
     } 
     $s = new Test; 
     for ($i = 0; $i < $iteration; $i++) { 
      $obj = new stdClass; 
      array_push($s->data, $obj); 
     } 
     break; 
    default: 
     echo 'Type in ?test=#'; 
} 

$time = microtime(); 
$time = explode(' ', $time); 
$time = $time[1] + $time[0]; 
$finish = $time; 
$total_time = round(($finish - $start), 6); 
echo 'Page generated in '.$total_time.' seconds.'; 
+1

mi piacerebbe sapere il motivo per cui è stato down-votato, sembra una risposta ragionevole. –

+0

@AlixAxel: probabilmente perché non è una risposta. Non sto facendo downvoting perché sembra che il codice sia utile per esplorare le caratteristiche del PHP. – wallyk

Problemi correlati