9 Stimmen

php foreach, warum ist die Verwendung von pass by reference eines Arrays schnell?

Unten ist ein Test von php foreach Schleife eines großen Arrays, dachte ich, dass wenn die $v nicht ändern, wird die echte Kopie nicht stattfinden, weil die Kopie auf Schreiben aber warum ist es so schnell, wenn es durch eine Referenz übergeben wird?

Code 1:

function test1($a){
  $c = 0;
  foreach($a as $v){ if($v=='xxxxx') ++$c; }
}

function test2(&$a){
  $c = 0;
  foreach($a as $v){ if($v=='xxxxx') ++$c; }
}

$x = array_fill(0, 100000, 'xxxxx');

$begin = microtime(true);
test1($x);
$end1 = microtime(true);
test2($x);
$end2 = microtime(true);

echo $end1 - $begin . "\n";   //0.03320002555847
echo $end2 - $end1;           //0.02147388458252

Aber dieses Mal ist die Verwendung der Referenzübergabe langsam.

Code 2:

function test1($a){
  $cnt = count($a); $c = 0;
  for($i=0; $i<$cnt; ++$i)
    if($a[$i]=='xxxxx') ++$c;
}
function test2(&$a){
  $cnt = count($a); $c = 0;
  for($i=0; $i<$cnt; ++$i)
    if($a[$i]=='xxxxx') ++$c;
}
$x = array_fill(0, 100000, 'xxxxx');

$begin = microtime(true);
test1($x);
$end1 = microtime(true);
test2($x);
$end2 = microtime(true);

echo $end1 - $begin . "\n";   //0.024326801300049
echo $end2 - $end1;           //0.037616014480591

Kann jemand erklären, warum die Übergabe durch Referenz schnell in code1, aber langsam in code2 ist?

Edita: Mit Code 2 wird die count($a) macht den Hauptunterschied aus, so dass die Zeit, die die Schleife benötigt, fast die gleiche ist.

8voto

hakre Punkte 184133

Ich dachte, wenn die $v ändern Sie nicht [ foreach($a as $v) ], wird die echte Kopie nicht stattfinden, weil Kopie auf Schreiben aber warum ist es so schnell, wenn es durch eine Referenz übergeben wird?

Die Auswirkungen sind nicht auf $v sondern auf $a , das riesige Feld. Sie übergeben es entweder als Wert oder als Referenz an die Funktion. Innerhalb der Funktion ist es dann ein Wert (test1) oder ein Verweis (test2).

Sie haben zwei Codes (Code 1 und Code 2).

Code 1: Wird mit foreach . Mit foreach haben Sie zwei Möglichkeiten: Iterieren Sie über einen Wert oder eine Referenz ( Beispiel ). Wenn Sie über einen Wert iterieren, wird die Iteration auf einer kopieren. des Wertes. Wenn Sie über eine Referenz iterieren, wird keine Kopie erstellt.

Da Sie die Referenz in test2 verwenden, ist es schneller. Die Werte müssen nicht kopiert werden. Aber in test1, übergeben Sie das Array als Wert, wird das Array kopiert.

Code 2: Wird mit for . Denn eigentlich tut sich hier nichts. In beiden Fällen. Sie greifen auf die Variable zu und lesen den Wert aus dem Array. Das ist so ziemlich dasselbe, egal ob es sich um eine Referenz oder eine Kopie handelt (dank der Kopie auf Schreiben Optimierung in PHP).

Sie fragen sich jetzt vielleicht, warum es ist ein Unterschied im Code 2. Der Unterschied ist nicht auf die for sondern wegen der count . Wenn Sie einen Verweis auf count PHP erstellt intern eine Kopie davon, weil es count braucht eine Kopie, keine Referenz.

Lesen Sie auch: Verwenden Sie keine PHP-Referenzen von Johannes Schlüter


Ich habe auch eine Reihe von Tests zusammengestellt. Aber ich habe den Code in die Testfunktionen eingefügt.

  • Blank - Worin besteht der Unterschied beim Aufruf der Funktion?
  • Zählen - Hat count einen Unterschied machen?
  • Für - Was passiert mit for nur (nicht count )?
  • Foreach - Einfach foreach - sogar beim ersten Element brechen.

Jeder Test liegt in zwei Versionen vor, die eine heißt _copy (Übergabe des Arrays als Kopie an die Funktion) und eine mit dem Namen _ref (Übergabe des Arrays als Referenz).

Nicht immer sagen diese Mikro-Benchmarks die Wahrheit, aber wenn man in der Lage ist, bestimmte Punkte zu isolieren, kann man sehr wohl eine fundierte Vermutung anstellen, zum Beispiel, dass nicht for pero count hatte die Auswirkungen:

function blank_copy($a){
}
function blank_ref(&$a){
}
function foreach_copy($a){
    foreach($a as $v) break;
}
function foreach_ref(&$a){
    foreach($a as $v) break;
}
function count_copy($a){
  $cnt = count($a);
}
function count_ref(&$a){
  $cnt = count($a);
}
function for_copy($a){
    for($i=0;$i<100000;$i++)
        $a[$i];
}
function for_ref(&$a){
    for($i=0;$i<100000;$i++)
        $a[$i];
}

$tests = array('blank_copy', 'blank_ref', 'foreach_copy', 'foreach_ref', 'count_copy', 'count_ref', 'for_copy', 'for_ref');

$x = array_fill(0, 100000, 'xxxxx');
$count = count($x);
$runs = 10;

ob_start();

for($i=0;$i<10;$i++)
{
    shuffle($tests);
    foreach($tests as $test)
    {
        $begin = microtime(true);
        for($r=0;$r<$runs;$r++)
            $test($x);
        $end = microtime(true);
        $result = $end - $begin;
        printf("* %'.-16s: %f\n", $test, $result);
    }
}

$buffer = explode("\n", ob_get_clean());
sort($buffer);
echo implode("\n", $buffer);

Ausgabe:

* blank_copy......: 0.000011
* blank_copy......: 0.000011
* blank_copy......: 0.000012
* blank_copy......: 0.000012
* blank_copy......: 0.000012
* blank_copy......: 0.000015
* blank_copy......: 0.000015
* blank_copy......: 0.000015
* blank_copy......: 0.000015
* blank_copy......: 0.000020
* blank_ref.......: 0.000012
* blank_ref.......: 0.000012
* blank_ref.......: 0.000014
* blank_ref.......: 0.000014
* blank_ref.......: 0.000014
* blank_ref.......: 0.000014
* blank_ref.......: 0.000015
* blank_ref.......: 0.000015
* blank_ref.......: 0.000015
* blank_ref.......: 0.000015
* count_copy......: 0.000020
* count_copy......: 0.000022
* count_copy......: 0.000022
* count_copy......: 0.000023
* count_copy......: 0.000024
* count_copy......: 0.000025
* count_copy......: 0.000025
* count_copy......: 0.000025
* count_copy......: 0.000026
* count_copy......: 0.000031
* count_ref.......: 0.113634
* count_ref.......: 0.114165
* count_ref.......: 0.114390
* count_ref.......: 0.114878
* count_ref.......: 0.114923
* count_ref.......: 0.115106
* count_ref.......: 0.116698
* count_ref.......: 0.118077
* count_ref.......: 0.118197
* count_ref.......: 0.123201
* for_copy........: 0.190837
* for_copy........: 0.191883
* for_copy........: 0.193080
* for_copy........: 0.194947
* for_copy........: 0.195045
* for_copy........: 0.195944
* for_copy........: 0.198314
* for_copy........: 0.198878
* for_copy........: 0.200016
* for_copy........: 0.227953
* for_ref.........: 0.191918
* for_ref.........: 0.194227
* for_ref.........: 0.195952
* for_ref.........: 0.196045
* for_ref.........: 0.197392
* for_ref.........: 0.197730
* for_ref.........: 0.201936
* for_ref.........: 0.207102
* for_ref.........: 0.208017
* for_ref.........: 0.217156
* foreach_copy....: 0.111968
* foreach_copy....: 0.113224
* foreach_copy....: 0.113574
* foreach_copy....: 0.113575
* foreach_copy....: 0.113879
* foreach_copy....: 0.113959
* foreach_copy....: 0.114194
* foreach_copy....: 0.114450
* foreach_copy....: 0.114610
* foreach_copy....: 0.118020
* foreach_ref.....: 0.000015
* foreach_ref.....: 0.000016
* foreach_ref.....: 0.000016
* foreach_ref.....: 0.000016
* foreach_ref.....: 0.000018
* foreach_ref.....: 0.000019
* foreach_ref.....: 0.000019
* foreach_ref.....: 0.000019
* foreach_ref.....: 0.000019
* foreach_ref.....: 0.000020

4voto

Sajid Punkte 4321

Mit der ersten Antwort bin ich nicht ganz einverstanden. Das Wichtigste ist, wie in den Kommentaren gesagt wird, dass die Tests nicht dieselben sind. Hier sind die vollständig isolierten Tests, die NUR die Schleifen testen.

Version 1:

<?php
function test1($a) {
    $c = 0;
    $begin = microtime(true);
    foreach ($a as $v) {
        if ($v == 'x') ++$c;
    }
    $end = microtime(true);
    echo $end - $begin . "\n";
    return $c;
}

function test2(&$a) {
    $c = 0;
    $begin = microtime(true);
    foreach ($a as $v) {
        if ($v == 'x') ++$c;
    }
    $end = microtime(true);
    echo $end - $begin . "\n";
    return $c;
}

$x = array_fill(0, 1000000, 'x');

test1($x); // 0.11617302894592
test2($x); // 0.059789180755615

Version 2:

<?php
function test1($a) {
    $cnt = count($a);
    $c = 0;
    $begin = microtime(true);
    for ($i = 0; $i < $cnt; ++$i) if ($a[$i] == 'x') ++$c;
    $end = microtime(true);
    echo $end - $begin . "\n";
    return $c;
}

function test2(&$a) {
    $cnt = count($a);
    $c = 0;
    $begin = microtime(true);
    for ($i = 0; $i < $cnt; ++$i) if ($a[$i] == 'x') ++$c;
    $end = microtime(true);
    echo $end - $begin . "\n";
    return $c;
}

$x = array_fill(0, 1000000, 'x');

test1($x); // 0.086347818374634
test2($x); // 0.086491107940674

Beachten Sie, dass in der vollständig isolierten Form die zweiten Tests KEINE Unterschiede zeigen, während der erste dies tut. Warum?

Die Antwort ist, dass das Array einen internen Zeiger für Dinge wie foreach hat. Auf ihn kann mit Aufrufen wie aktuell . Wenn Sie foreach mit einer Referenz ausführen, werden die Zeiger des ursprünglichen Arrays verwendet. Bei der Übergabe nach Wert müssen die Array-Interna kopiert werden, sobald foreach ausgeführt wird, auch wenn die Werte irgendwie von der Engine verwaltet werden. Daher die Strafe.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X