146 votes

Exécution de pour vs FOREACH en PHP

Tout d'abord, je comprends que dans 90% des applications de la différence de performances est complètement hors sujet, mais j'ai juste besoin de savoir qui est le plus rapide à construire. Et...

Les informations actuellement disponibles sur le net est une source de confusion. Beaucoup de gens disent foreach est mauvais, mais, techniquement, il devrait être plus rapide car il est à supposer pour simplifier l'écriture d'un tableau de la traversée à l'aide des itérateurs. Les itérateurs, qui sont de nouveau supposons que pour être plus rapide, mais en PHP sont aussi apparemment mort lente (ou n'est-ce pas une chose PHP?). Je parle des fonctions de tableau: next() prev() reset (), etc. eh bien, si elles sont les même fonctions et pas un de ceux du langage PHP caractéristiques qui ressemblent à des fonctions.

Pour limiter cette baisse un peu: je ne suis pas intéressante en parcourant les tableaux dans les étapes de quelque chose de plus que de 1 (pas négatif des mesures soit, c'est à dire. inverse itération). Je ne suis pas intéressé par une traversée vers et à partir de points arbitraires, à seulement 0 de longueur. Aussi, je ne vois pas de manipuler des tableaux avec plus de 1000 clés qui se passe sur une base régulière, mais je vois un tableau traversée plusieurs fois dans la logique d'une application! Aussi, comme pour les opérations, principalement à la manipulation de la chaîne et de l'écho qui pratiquent.

Voici quelques sites de référence:
http://www.phpbench.com/
http://www.php.lt/benchmark/phpbench.php

Ce que j'entends partout:

  • foreach est lente, et donc for/while est plus rapide
  • PHPs foreach des copies de l'ensemble qu'il parcourt; pour le rendre plus rapide vous avez besoin d'utiliser des références
  • code comme ceci: $key = array_keys($aHash); $size = sizeOf($key); est plus rapide qu'un

Voici mon problème. J'ai écrit ce script de test: http://pastebin.com/1ZgK07US et peu importe combien de fois je lance le script, j'obtiens quelque chose comme ceci:

for ($i=0; $i < $size; $i++)

En bref:

  • foreach plus rapide que de l' foreach 1.1438131332397 foreach (using reference) 1.2919359207153 for 1.4262869358063 foreach (hash table) 1.5696921348572 for (hash table) 2.4778981208801 avec référence
  • foreach plus rapide que de l' foreach
  • foreach plus rapide que de l' for pour une table de hachage

Quelqu'un peut m'expliquer?

  1. Suis-je en train de faire quelque chose de mal?
  2. Est PHP foreach référence chose de vraiment faire une différence? Je veux dire, pourquoi ne serait-il pas le copier si vous passez par référence?
  3. Ce qui est l'équivalent d'itérateur code de l'instruction foreach; j'ai vu un peu sur le net, mais à chaque fois j'ai tester le timing est mauvais; j'ai aussi testé quelques itérateur simple constructions, mais ne semblent jamais à faire même des résultats décents -- sont la matrice des itérateurs en PHP juste horrible?
  4. Existe-il des façons plus rapides, méthodes et constructions de l'itération si un tableau autrement que POUR/FOREACH (et TOUT)?

La Version de PHP 5.3.0


Edit: Réponse

Avec l'aide de personnes ici, j'ai été en mesure de rassembler les réponses à toutes les questions. Je vais résumer ici:

  1. "Suis-je en train de faire quelque chose de mal?" Le consensus semble être: oui, je ne peux pas utiliser echo dans les benchmarks. Personnellement, je ne vois pas comment l'echo est une fonction aléatoire moment de l'exécution ou de la façon dont toute autre fonction qui est d'une certaine manière toute différente, -- que le script pour générer les mêmes résultats de foreach mieux que tout ce qui est dur à expliquer, si ce n'est que "vous êtes à l'aide de l'écho" (bien que je devrais avoir été utilisé). Cependant, je le concède, le test doit être fait avec quelque chose de mieux; cependant, un compromis idéal n'est pas venu à l'esprit.
  2. "Est PHP foreach référence chose de vraiment faire une différence? Je veux dire, pourquoi ne serait-il pas le copier si vous passez par référence?" ircmaxell montre que oui, il l'est, d'autres tests semble prouver dans la plupart des cas, il devrait être plus rapide-mais compte tenu de mon extrait ci-dessus du code, le plus certainement ne signifie pas que tous les. J'accepte le problème est probablement trop non intuitif de s'embêter avec un tel niveau et aurait besoin de quelque chose extrêmes, tels que la décompilation pour déterminer ce qui est mieux pour chaque situation.
  3. "Ce qui est l'équivalent d'itérateur code de l'instruction foreach; j'ai vu un peu sur le net, mais à chaque fois j'ai tester le timing est mauvais; j'ai aussi testé quelques itérateur simple constructions, mais ne semblent jamais à faire même des résultats décents -- sont la matrice des itérateurs en PHP juste horrible?" ircmaxell a fourni la réponse soufflet; si le code peut être seulement valide pour la version de PHP >= 5
  4. "Y at-il des façons plus rapides, méthodes et constructions de l'itération si un tableau autrement que POUR/FOREACH (et PENDANT)?" Merci à Gordon pour la réponse. En utilisant de nouveaux types de données en PHP5 devrait donner soit un gain de performances ou de la mémoire boost (soit de ce qui pourrait être souhaitable en fonction de votre situation). Même si la vitesse sage a beaucoup de nouveaux types de tableau ne semble pas être mieux que array(), le splpriorityqueue et splobjectstorage ne semblent être beaucoup plus rapide. Lien fourni par Gordon: http://matthewturland.com/2010/05/20/new-spl-features-in-php-5-3/

Merci à tous ceux qui ont essayé de l'aider.

Je vais probablement en tenir à foreach (le non-version de référence) pour une simple traversée.

122voto

ircmaxell Points 74865

Mon opinion personnelle est d'utiliser ce qui fait sens dans le contexte. Personnellement, je n'ai presque jamais utiliser for pour le tableau de la traversée. - Je l'utiliser pour d'autres types de l'itération, mais foreach est tout simplement trop facile... La différence de temps va être minime dans la plupart des cas.

La grande chose à surveiller est:

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

C'est sur cher boucle, car il demande de compter à chaque itération. Tant que vous ne faites pas cela, je ne pense pas que cela importe vraiment...

Comme pour la référence de faire une différence, PHP utilise copy-on-write, donc si vous ne pas écrire sur le tableau, il y aura relativement peu de surcharge, tout en boucle. Toutefois, si vous commencez à modifier le tableau dans le tableau, c'est là que vous allez commencer à voir les différences entre eux (puisque l'on va avoir besoin de copier l'ensemble de la matrice, et l'on peut juste modifier inline)...

Comme pour les itérateurs, foreach est équivalent à:

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

    // Contents of loop in here

    $it->next();
}

Aussi loin qu'il n'y étant plus rapide des moyens pour effectuer une itération, cela dépend vraiment sur le problème. Mais j'ai vraiment besoin de demander, pourquoi? Je comprends de vouloir rendre les choses plus efficaces, mais je pense que vous perdez votre temps pour une micro-optimisation. Rappelez-vous, Premature Optimization Is The Root Of All Evil...

Edit: Basé sur le commentaire, j'ai décidé de faire un rapide test exécuter...

$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";

Et les résultats:

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

Si vous êtes à la modification du tableau dans la boucle, elle est plusieurs fois plus rapide à utiliser des références...

Et la surcharge pour simplement la référence est en fait moins que la copie de la matrice (c'est sur 5.3.2)... de Sorte qu'il apparaît (sur 5.3.2 au moins) comme si les références sont nettement plus rapide...

57voto

Jaimie Sirovich Points 88

Je ne suis pas sûr que c'est tellement surprenant. La plupart des gens qui code en PHP ne connaissent pas bien ce que PHP est en fait en train de faire au métal nu. Je vais dire quelques choses, ce qui sera vrai la plupart du temps:

  1. Si vous n'êtes pas la modification de la variable, la valeur est plus rapide en PHP. C'est parce que c'est un décompte de références de toute façon et par valeur, donne moins à faire. Il sait que le deuxième vous modifier que ZVAL (PHP la structure de données interne pour la plupart des types), il devra rompre d'une façon simple (copiez-le et oubliez les autres ZVAL). Mais vous n'avez jamais le modifier, de sorte qu'il n'a pas d'importance. Références en font que de plus en plus compliqué avec plus de tenue de livres qu'il a à faire de savoir quoi faire lorsque vous modifiez la variable. Donc, si vous êtes en lecture seule, paradoxalement, c'est mieux de ne pas le signaler avec le &. Je sais, c'est contre-intuitif, mais il est également vrai.

  2. Foreach n'est pas lent. Et pour une simple itération, la condition c'est l'essai contre - "suis-je à la fin de ce tableau" est effectué à l'aide du code natif, pas de PHP opcodes. Même si c'est de l'APC en cache d'opcodes, c'est encore plus lent qu'un groupe de natifs les opérations effectuées sur le métal nu.

  3. À l'aide d'une boucle "for ($i=0; $i < count($x); $i++) est lente en raison de la fonction count(), et le manque de PHP à la capacité (ou de toute autre langage interprété) à évaluer au moment de l'analyse si quelque chose modifie le tableau. Cela l'empêche d'évaluer le comte une fois.

  4. Mais même une fois que vous fixer avec "$c=count($x); for ($i=0; $i<$c; $i++) $i<$c est un tas de Zend opcodes, au mieux, comme c'est le $i++. Dans le cadre de 100000 itérations, ce peut importe. Foreach sait au niveau natif de quoi faire. Pas de PHP opcodes test "suis-je à la fin de ce tableau de l'état".

  5. Que dire de la vieille école "while(list(" choses"? Eh bien, à l'aide de chacun (), (), etc. allons tous à la participation d'au moins 1 appel de fonction, ce qui n'est pas lent, mais pas gratuitement. Oui, ceux qui sont en PHP opcodes de nouveau! Ainsi, alors que + liste + chacun a ses coûts.

Pour ces raisons foreach est naturellement la meilleure option pour la simple itération.

Et n'oubliez pas, c'est aussi le plus facile à lire, donc c'est gagnant-gagnant.

32voto

Kendall Hopkins Points 12193

Une chose à regarder dehors pour dans les benchmarks (en particulier phpbench.com), même si les chiffres sont son, les tests ne sont pas. Beaucoup de tests sur phpbench.com sont faire des choses triviales et de l'abus de PHP capacité à cache array recherches à fausser les repères ou, dans le cas d'une itération sur un tableau n'est pas réellement le tester dans le monde réel des cas (pas écrit vides pour les boucles). J'ai fait mes propres repères que j'ai trouvé sont assez reflète le monde réel, les résultats et ils ont toujours montrer la langue maternelle de l'itération de la syntaxe foreach sortir sur le dessus (surprise, surprise).

//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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X