Prestazioni di FOR vs FOREACH in PHP


134

Prima di tutto, capisco che nel 90% delle applicazioni la differenza di prestazioni è completamente irrilevante, ma devo solo sapere qual è il costrutto più veloce. Quello e ...

Le informazioni attualmente disponibili su di loro in rete sono confuse. Molte persone dicono che foreach è un male, ma tecnicamente dovrebbe essere più veloce poiché si suppone di semplificare la scrittura di un attraversamento di array usando iteratori. Gli iteratori, che si suppone siano ancora più veloci, ma in PHP sono anche apparentemente morti lentamente (o questa non è una cosa PHP?). Sto parlando delle funzioni dell'array: next () prev () reset () ecc. Bene, se sono anche funzioni e non una di quelle caratteristiche del linguaggio PHP che sembrano funzioni.

Per restringere un po 'questo : non sono interessante attraversare array in passi di qualcosa di più di 1 (nessun passaggio negativo, cioè inversione dell'iterazione). Inoltre non mi interessa un attraversamento da e verso punti arbitrari, solo da 0 a lunghezza. Inoltre, non vedo manipolare array con più di 1000 chiavi che si verificano su base regolare, ma vedo un array attraversato più volte nella logica di un'applicazione! Anche per quanto riguarda le operazioni, in gran parte solo manipolazione ed eco delle stringhe.

Ecco alcuni siti di riferimento:
http://www.phpbench.com/
http://www.php.lt/benchmark/phpbench.php

Quello che sento ovunque:

  • foreachè lento e quindi for/ whileè più veloce
  • PHPs foreachcopia l'array su cui scorre; per renderlo più veloce è necessario utilizzare i riferimenti
  • codice come questo: è più veloce di un$key = array_keys($aHash); $size = sizeOf($key);
    for ($i=0; $i < $size; $i++)
    foreach

Ecco il mio problema Ho scritto questo script di prova: http://pastebin.com/1ZgK07US e non importa quante volte eseguo lo script, ottengo qualcosa del genere:

foreach 1.1438131332397
foreach (using reference) 1.2919359207153
for 1.4262869358063
foreach (hash table) 1.5696921348572
for (hash table) 2.4778981208801

In breve:

  • foreachè più veloce che foreachcon riferimento
  • foreach è più veloce di for
  • foreachè più veloce di foruna tabella hash

Qualcuno può spiegare?

  1. Sto facendo qualcosa di sbagliato?
  2. PHP per ogni riferimento è davvero qualcosa che fa la differenza? Voglio dire, perché non dovrebbe copiarlo se passi per riferimento?
  3. Qual è il codice iteratore equivalente per l'istruzione foreach; Ne ho visti alcuni in rete, ma ogni volta che li collaudo i tempi sono lontani; Ho anche testato alcuni costrutti di iteratori semplici ma non sembra mai ottenere risultati decenti - gli iteratori di array in PHP sono semplicemente orribili?
  4. Ci sono modi / metodi / costrutti più veloci per iterare attraverso una matrice diversa da FOR / FOREACH (e WHILE)?

Versione PHP 5.3.0


Modifica: Risposta Con l'aiuto delle persone qui sono stato in grado di mettere insieme le risposte a tutte le domande. Li riassumo qui:

  1. "Sto facendo qualcosa di sbagliato?" Il consenso sembra essere: sì, non posso usare l'eco nei benchmark. Personalmente, non vedo ancora come l'eco sia una funzione con tempo di esecuzione casuale o come qualsiasi altra funzione sia in qualche modo diversa - che e la capacità di quello script di generare semplicemente gli stessi risultati di foreach meglio di tutto è difficile per spiegare però solo "stai usando l'eco" (bene cosa avrei dovuto usare). Tuttavia, concedo che il test dovrebbe essere fatto con qualcosa di meglio; sebbene non venga in mente un compromesso ideale.
  2. "PHP per ogni cosa di riferimento fa davvero la differenza? Voglio dire, perché non dovrebbe copiarla se passi per riferimento?" ircmaxell mostra che sì, ulteriori test sembrano dimostrare nella maggior parte dei casi che il riferimento dovrebbe essere più veloce - sebbene dato il mio frammento di codice sopra, sicuramente non significa tutto. Accetto che il problema sia probabilmente troppo non intuitivo per preoccuparsi a tale livello e richiederebbe qualcosa di estremo come la decompilazione per determinare quale sia la migliore per ogni situazione.
  3. "Qual è il codice iteratore equivalente per l'istruzione foreach; ne ho visti alcuni in rete ma ogni volta che li collaudo il tempismo è molto lontano; ho anche testato alcuni semplici costrutti iteratori ma sembra che non ottengano mai risultati decenti - gli iteratori di array in PHP sono semplicemente orribili? " ircmaxell ha fornito la risposta qui sotto; sebbene il codice potrebbe essere valido solo per la versione di PHP> = 5
  4. "Ci sono modi / metodi / costrutti più veloci per iterare attraverso una matrice diversa da FOR / FOREACH (e WHILE)?" Grazie a Gordon per la risposta. L'uso di nuovi tipi di dati in PHP5 dovrebbe fornire un aumento delle prestazioni o un aumento della memoria (uno dei due potrebbe essere desiderabile a seconda della situazione). Mentre per quanto riguarda la velocità molti dei nuovi tipi di array non sembrano migliori di array (), splpriorityqueue e splobjectstorage sembrano essere sostanzialmente più veloci. Link fornito da Gordon: http://matthewturland.com/2010/05/20/new-spl-features-in-php-5-3/

Grazie a tutti coloro che hanno cercato di aiutare.

Probabilmente continuerò a cercare (la versione non di riferimento) per qualsiasi traversata semplice.


7
Regola 2.71 del benchmarking: non fare eco al benchmark.
Mchl

1
foreach con riferimento deve essere contrassegnato con per con riferimento. hai una conclusione imperfetta lì. qualsiasi uso di un riferimento sarà ovviamente più lento di quello senza un riferimento, anche in un ciclo do-while.
bcosca,

2
Poiché questo è per php 5.3, potresti anche prendere in considerazione la possibilità di provare i nuovi tipi di dati Spl vs array. Oppure guarda qui: matthewturland.com/2010/05/20/new-spl-features-in-php-5-3
Gordon

@ Mchl: l'ho eseguito alcune volte e ho ottenuto gli stessi risultati - se l'eco corrompe il benchmark, non dovrei ottenere risultati completamente casuali? inoltre vorrei iterare qualcosa e emetterlo in modo che l'eco sia davvero importante per me; se foreach è più veloce durante l'eco, allora è un grosso pezzo di codice in cui dovrei usare foreach. @ ancora in sospeso: quello che sto ascoltando è fondamentalmente sulla falsariga di "riferimento in foreach rende più veloce (sempre), scrivi sempre con riferimento", ecco perché ho provato in questo modo - non sono davvero interessato al confronto con altri loop di riferimento
srcspider,

2
queste domande vuote dovrebbero naturalmente essere bandite. così come quell'ingannevole sito phpbench
Il tuo buon senso

Risposte:


110

La mia opinione personale è quella di usare ciò che ha senso nel contesto. Personalmente non lo uso quasi mai forper l'attraversamento di array. Lo uso per altri tipi di iterazioni, ma foreachè troppo semplice ... La differenza di tempo sarà minima nella maggior parte dei casi.

La cosa importante da guardare è:

for ($i = 0; $i < count($array); $i++) {

È un ciclo costoso, poiché chiama contare su ogni singola iterazione. Finché non lo fai, non penso che importi davvero ...

Per quanto riguarda il riferimento che fa la differenza, PHP usa il copy-on-write, quindi se non si scrive sull'array, ci sarà relativamente poco overhead durante il loop. Tuttavia, se inizi a modificare l'array all'interno dell'array, è lì che inizierai a vedere le differenze tra loro (dal momento che sarà necessario copiare l'intero array e il riferimento può semplicemente modificare in linea) ...

Per quanto riguarda gli iteratori, foreachequivale a:

$it->rewind();
while ($it->valid()) {
    $key = $it->key();     // If using the $key => $value syntax
    $value = $it->current();

    // Contents of loop in here

    $it->next();
}

Per quanto ci siano modi più veloci per iterare, dipende davvero dal problema. Ma ho davvero bisogno di chiedere, perché? Capisco il desiderio di rendere le cose più efficienti, ma penso che stai sprecando il tuo tempo per una micro-ottimizzazione. Ricorda, Premature Optimization Is The Root Of All Evil...

Modifica: in base al commento, ho deciso di eseguire una rapida analisi comparativa ...

$a = array();
for ($i = 0; $i < 10000; $i++) {
    $a[] = $i;
}

$start = microtime(true);
foreach ($a as $k => $v) {
    $a[$k] = $v + 1;
}
echo "Completed in ", microtime(true) - $start, " Seconds\n";

$start = microtime(true);
foreach ($a as $k => &$v) {
    $v = $v + 1;
}
echo "Completed in ", microtime(true) - $start, " Seconds\n";

$start = microtime(true);
foreach ($a as $k => $v) {}
echo "Completed in ", microtime(true) - $start, " Seconds\n";

$start = microtime(true);
foreach ($a as $k => &$v) {}    
echo "Completed in ", microtime(true) - $start, " Seconds\n";

E i risultati:

Completed in 0.0073502063751221 Seconds
Completed in 0.0019769668579102 Seconds
Completed in 0.0011849403381348 Seconds
Completed in 0.00111985206604 Seconds

Quindi, se stai modificando l'array nel loop, è molto più veloce usare i riferimenti ...

E l'overhead per il solo riferimento è in realtà inferiore alla copia dell'array (questo è su 5.3.2) ... Quindi appare (almeno su 5.3.2) come se i riferimenti fossero significativamente più veloci ...


1
Non vuoi dire "l'ottimizzazione [non pianificata] è la radice di tutti i mali"? ;) Beh, la cosa è che tutti fanno la stessa cosa, quindi non è tanto un'ottimizzazione come è: che è "il modo standard migliore per adottare". Anche alcune altre domande senza risposta: dici perché non deve essere copiato, ma l'uso del riferimento non è un sovraccarico? Il commento di Stillstanding alla mia domanda sembra non essere d'accordo con le tue assunzioni. Inoltre, perché il codice produce più lentamente per riferimento anche lì. Il foreach è cambiato in 5.3.0 per convertire qualsiasi array () in un oggetto (es. SplFixedArray)?
srcspider,

@srcspider: risposta modificata con codice di riferimento e risultati che mostrano i riferimenti sono davvero molto più veloci dei non riferimenti ...
ircmaxell

1
Le "the better standard way to adopt." prestazioni di @srcspider non sono gli unici criteri per scegliere cosa adottare. specialmente in un caso così inverosimile. Francamente, stai solo perdendo tempo
Il tuo senso comune

@Col. Shrapnel Sono d'accordo al 100%. La leggibilità e la manutenibilità vincono le prestazioni di un ampio margine in questo caso particolare ... Concordo sulla scelta di uno standard e sul rispetto di esso, ma baso tale standard su altri fattori
ircmaxell

@ircmaxell: l'esecuzione rapida della sceneggiatura sembra dimostrare il tuo punto di vista, ma voglio approfondire ulteriormente; Potrei modificare la mia domanda originale con più test per includere alcune delle nuove funzionalità 5.3. @Col. Shrapnel: FOR è un livello di programmazione quasi universale per la scuola materna, FOREACH è una sintassi più semplice. Per quanto riguarda la leggibilità, sembrano essere su un piano di parità. Questo è tutto a un livello così basso che non penso che la manutenzione sia un problema come lo sarebbe per alcuni modelli di alto livello. E non credo di perdere tempo poiché questo "costrutto di base" spiegherebbe molto codice che scriverei. :)
srcspider

54

Non sono sicuro che sia così sorprendente. La maggior parte delle persone che codificano in PHP non sono ben informate su ciò che PHP sta realmente facendo al bare metal. Dichiarerò alcune cose, che saranno vere la maggior parte delle volte:

  1. Se non stai modificando la variabile, il valore per è più veloce in PHP. Questo perché il suo riferimento viene comunque conteggiato e per valore dà meno da fare. Conosce il secondo che modifichi ZVAL (la struttura di dati interna di PHP per la maggior parte dei tipi), dovrà interromperlo in modo semplice (copiarlo e dimenticare l'altro ZVAL). Ma non lo modifichi mai, quindi non importa. I riferimenti rendono questo più complicato con più contabilità che deve fare per sapere cosa fare quando si modifica la variabile. Quindi, se sei di sola lettura, paradossalmente è meglio non sottolineare il punto &. Lo so, è contro intuitivo, ma è anche vero.

  2. Foreach non è lento. E per una semplice iterazione, la condizione a cui sta testando - "Sono alla fine di questo array" - è fatta usando il codice nativo, non i codici operativi PHP. Anche se si tratta di codici postali memorizzati nella cache di APC, è ancora più lento di un gruppo di operazioni native eseguite sul bare metal.

  3. L'uso di un ciclo for "per ($ i = 0; $ i <count ($ x); $ i ++) è lento a causa del count () e della mancanza della capacità di PHP (o di qualsiasi linguaggio interpretato) di valutare all'analisi tempo se qualcosa modifica l'array, questo gli impedisce di valutare una volta il conteggio.

  4. Ma anche una volta risolto con "$ c = count ($ x); per ($ i = 0; $ i <$ c; $ i ++) $ i <$ c è un gruppo di codici operativi Zend nella migliore delle ipotesi, così come è $ i ++. Nel corso di 100000 iterazioni, questo può importare. Foreach sa a livello nativo cosa fare. Non è necessario alcun codice operativo PHP per testare la condizione "Sono io alla fine di questo array".

  5. Che dire della vecchia scuola "while (list (" stuff? Beh, usando each (), current (), ecc.) Tutti implicano almeno 1 chiamata di funzione, che non è lenta, ma non gratuita. Sì, quelli sono di nuovo codici operativi PHP! Quindi, mentre + list + ha anche i suoi costi.

Per questi motivi foreach è comprensibilmente l'opzione migliore per una semplice iterazione.

E non dimenticare, è anche il più facile da leggere, quindi è vantaggioso per tutti.


Questa è esattamente la spiegazione che stavo cercando, grazie.
difficile

Questa risposta dovrebbe davvero essere un supplimento o un riassunto della risposta contrassegnata. Sono contento di averlo letto, buon lavoro.
doz87,

30

Una cosa a cui fare attenzione nei benchmark (in particolare phpbench.com), anche se i numeri sono validi, i test no. Molti dei test su phpbench.com stanno facendo cose banali e abusano della capacità di PHP di memorizzare nella cache ricerche di array per distorcere i benchmark o nel caso di iterare su un array in realtà non lo testare in casi reali (nessuno scrive vuoto per loop). Ho fatto i miei parametri di riferimento che ho trovato riflettono abbastanza i risultati del mondo reale e mostrano sempre la sintassi iterativa nativa del linguaggio foreachche emerge in cima (sorpresa, sorpresa).

//make a nicely random array
$aHash1 = range( 0, 999999 );
$aHash2 = range( 0, 999999 );
shuffle( $aHash1 );
shuffle( $aHash2 );
$aHash = array_combine( $aHash1, $aHash2 );


$start1 = microtime(true);
foreach($aHash as $key=>$val) $aHash[$key]++;
$end1 = microtime(true);

$start2 = microtime(true);
while(list($key) = each($aHash)) $aHash[$key]++;
$end2 = microtime(true);


$start3 = microtime(true);
$key = array_keys($aHash);
$size = sizeOf($key);
for ($i=0; $i<$size; $i++) $aHash[$key[$i]]++;
$end3 = microtime(true);

$start4 = microtime(true);
foreach($aHash as &$val) $val++;
$end4 = microtime(true);

echo "foreach ".($end1 - $start1)."\n"; //foreach 0.947947025299
echo "while ".($end2 - $start2)."\n"; //while 0.847212076187
echo "for ".($end3 - $start3)."\n"; //for 0.439476966858
echo "foreach ref ".($end4 - $start4)."\n"; //foreach ref 0.0886030197144

//For these tests we MUST do an array lookup,
//since that is normally the *point* of iteration
//i'm also calling noop on it so that PHP doesn't
//optimize out the loopup.
function noop( $value ) {}

//Create an array of increasing indexes, w/ random values
$bHash = range( 0, 999999 );
shuffle( $bHash );

$bstart1 = microtime(true);
for($i = 0; $i < 1000000; ++$i) noop( $bHash[$i] );
$bend1 = microtime(true);

$bstart2 = microtime(true);
$i = 0; while($i < 1000000) { noop( $bHash[$i] ); ++$i; }
$bend2 = microtime(true);


$bstart3 = microtime(true);
foreach( $bHash as $value ) { noop( $value ); }
$bend3 = microtime(true);

echo "for ".($bend1 - $bstart1)."\n"; //for 0.397135972977
echo "while ".($bend2 - $bstart2)."\n"; //while 0.364789962769
echo "foreach ".($bend3 - $bstart3)."\n"; //foreach 0.346374034882

3

È il 2020 e le cose si sono evolute notevolmente con php 7.4 e opcache .

Ecco il benchmark OP ^, eseguito come CLI unix , senza le parti echo e html.

Il test è stato eseguito localmente su un normale computer.

php -v

PHP 7.4.6 (cli) (built: May 14 2020 10:02:44) ( NTS )

Script di benchmark modificato:

<?php 
 ## preperations; just a simple environment state

  $test_iterations = 100;
  $test_arr_size = 1000;

  // a shared function that makes use of the loop; this should
  // ensure no funny business is happening to fool the test
  function test($input)
  {
    //echo '<!-- '.trim($input).' -->';
  }

  // for each test we create a array this should avoid any of the
  // arrays internal representation or optimizations from getting
  // in the way.

  // normal array
  $test_arr1 = array();
  $test_arr2 = array();
  $test_arr3 = array();
  // hash tables
  $test_arr4 = array();
  $test_arr5 = array();

  for ($i = 0; $i < $test_arr_size; ++$i)
  {
    mt_srand();
    $hash = md5(mt_rand());
    $key = substr($hash, 0, 5).$i;

    $test_arr1[$i] = $test_arr2[$i] = $test_arr3[$i] = $test_arr4[$key] = $test_arr5[$key]
      = $hash;
  }

  ## foreach

  $start = microtime(true);
  for ($j = 0; $j < $test_iterations; ++$j)
  {
    foreach ($test_arr1 as $k => $v)
    {
      test($v);
    }
  }
  echo 'foreach '.(microtime(true) - $start)."\n";  

  ## foreach (using reference)

  $start = microtime(true);
  for ($j = 0; $j < $test_iterations; ++$j)
  {
    foreach ($test_arr2 as &$value)
    {
      test($value);
    }
  }
  echo 'foreach (using reference) '.(microtime(true) - $start)."\n";

  ## for

  $start = microtime(true);
  for ($j = 0; $j < $test_iterations; ++$j)
  {
    $size = count($test_arr3);
    for ($i = 0; $i < $size; ++$i)
    {
      test($test_arr3[$i]);
    }
  }
  echo 'for '.(microtime(true) - $start)."\n";  

  ## foreach (hash table)

  $start = microtime(true);
  for ($j = 0; $j < $test_iterations; ++$j)
  {
    foreach ($test_arr4 as $k => $v)
    {
      test($v);
    }
  }
  echo 'foreach (hash table) '.(microtime(true) - $start)."\n";

  ## for (hash table)

  $start = microtime(true);
  for ($j = 0; $j < $test_iterations; ++$j)
  {
    $keys = array_keys($test_arr5);
    $size = sizeOf($test_arr5);
    for ($i = 0; $i < $size; ++$i)
    {
      test($test_arr5[$keys[$i]]);
    }
  }
  echo 'for (hash table) '.(microtime(true) - $start)."\n";

Produzione:

foreach 0.0032877922058105
foreach (using reference) 0.0029420852661133
for 0.0025191307067871
foreach (hash table) 0.0035080909729004
for (hash table) 0.0061779022216797

Come vedi l'evoluzione è folle, circa 560 volte più veloce di quanto riportato nel 2012.

Sui miei computer e server, seguendo i miei numerosi esperimenti, le basi per i loop sono le più veloci. Questo è ancora più chiaro usando i cicli nidificati ( $ i $ j $ k ..)

È anche il più flessibile nell'uso e ha una migliore leggibilità dal mio punto di vista.


0

Penso, ma non sono sicuro: il forciclo richiede due operazioni per controllare e incrementare i valori. foreachcarica i dati in memoria, quindi ripeterà tutti i valori.


7
Tutti hanno un'opinione, le persone vengono a Stack Overflow per trovare le risposte. Se non sei sicuro di ciò che dichiari, controlla il codice sorgente, la documentazione, fai una ricerca su google, ecc.
Sebastien F.

Poiché le prestazioni si basano su ricerche e test, è necessario fornire alcune prove. Si prega di fornire i riferimenti di conseguenza. Spero che tu possa migliorare la tua risposta.
Marwan Salim,

Penso che dipenda anche dal carico effettivo del server e da cosa vuoi fare nel loop. Penso che dipenda anche dal carico effettivo del server e da cosa vuoi fare nel loop. Volevo sapere se per iterare su un array numerato dovrei usare meglio foreach - o for-loop, quindi ho eseguito un benchmark su sandbox.onlinephpfunctions.com con PHP 7.4. Eseguo ripetutamente gli stessi tempi multipli dello script e ogni esecuzione mi dà risultati diversi. Una volta il for-loop era più veloce un'altra volta il foreach-loop e un'altra volta erano uguali.
Alexander Behling
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.