2010-06-25 12 views
12

Ho appena trovato qualcosa di molto strano in PHP.Perché chiamare una funzione (come strlen, count ecc.) Su un valore di riferimento è così lento?

Se si passa una variabile in una funzione per riferimento, quindi si chiama una funzione, è incredibilmente lento.

Se si esegue il looping della chiamata di funzione interna e la variabile è grande, può essere più lenta di ordini di grandezza rispetto alla variabile passata per valore.

Esempio:

<?php 
function TestCount(&$aArray) 
{ 
    $aArray = range(0, 100000); 
    $fStartTime = microtime(true); 

    for ($iIter = 0; $iIter < 1000; $iIter++) 
    { 
     $iCount = count($aArray); 
    } 

    $fTaken = microtime(true) - $fStartTime; 

    print "took $fTaken seconds\n"; 
} 

$aArray = array(); 
TestCount($aArray); 
?> 

Questo richiede costantemente circa 20 secondi per l'esecuzione sulla mia macchina (su PHP 5.3).

Ma se cambio la funzione di passare dal valore (cioè function TestCount($aArray) invece di function TestCount(&$aArray)), allora viene eseguito in circa 2 ms - letteralmente 10.000 volte più veloce!

Lo stesso vale per altre funzioni integrate come strlen e per funzioni definite dall'utente.

Cosa sta succedendo?

+3

È 10000 volte più lento perché si sta iterando all'interno del benchmark. Questo non ti darà la misura corretta per 'count()'. Usa un profiler e vedrai che è solo 3 volte più lento. Per una spiegazione, vedi http://derickrethans.nl/talks/phparch-php-variables-article.pdf – Gordon

+0

@Gordon - sì, è vero, ma il motivo per cui abbiamo scoperto questo è che avevamo un codice di produzione che si comportava in modo molto simile a l'esempio (cambiando la variabile ovviamente). Non è un caso d'uso particolarmente esoterico. –

+0

non dicendo che è esoterico. solo dicendo che i numeri sono molto esagerati. – Gordon

risposta

13

ho trovato un bug report a partire dal 2005 che descrive esattamente questo problema: http://bugs.php.net/bug.php?id=34540

Così il problema sembra essere che quando passa un valore di riferimento per una funzione che non accetta un riferimento, PHP ha bisogno di copiarlo .

Questo può essere dimostrato con questo codice di prova:

<?php 
function CalledFunc(&$aData) 
{ 
    // Do nothing 
} 

function TestFunc(&$aArray) 
{ 
    $aArray = range(0, 100000); 
    $fStartTime = microtime(true); 

    for ($iIter = 0; $iIter < 1000; $iIter++) 
    { 
     CalledFunc($aArray); 
    } 

    $fTaken = microtime(true) - $fStartTime; 

    print "took $fTaken seconds\n"; 
} 

$aArray = array(); 
TestFunc($sData); 
?> 

Questa corre in fretta, ma se si cambia function CalledFunc(&$aData) a function CalledFunc($aData) Vedrete una simile rallentamento all'esempio count.

Questo è piuttosto preoccupante, dal momento che ho codificato PHP per un po 'e non avevo idea di questo problema.

Fortunatamente esiste una soluzione semplice che è applicabile in molti casi: utilizzare una variabile locale temporanea all'interno del ciclo e copiare alla variabile di riferimento alla fine.

+3

true, ma penso che il comportamento (PHP clona l'array) sia corretto e ragionevole dal momento che non vogliamo la funzione che accetta l'array come valore per modificare l'array originale se non è clonato. non posso vivere senza di essa. forse quello che possiamo fare come programmatori è guardare fuori da questo scenario ed evitarlo. – Lukman

+0

In effetti è completamente rotto. mi sono imbattuto in quel bug 5 minuti fa. w/e mi occuperò anche dello strlen - –

1

Quindi, prendendo la risposta già fornita, è possibile in parte evitare questo problema forzando la copia prima del lavoro iterativo (Copia in seguito se i dati vengono modificati).

<?php 
function TestCountNon($aArray) 
{ 
    $aArray = range(0, 100000); 
    $fStartTime = microtime(true); 
    for ($iIter = 0; $iIter < 1000; $iIter++) 
    { 
     $iCount = count($aArray); 
    } 
    $fTaken = microtime(true) - $fStartTime; 

    print "Non took $fTaken seconds\n<br>"; 
} 

function TestCount(&$aArray) 
{ 
    $aArray = range(0, 100000); 
    $fStartTime = microtime(true); 
    for ($iIter = 0; $iIter < 1000; $iIter++) 
    { 
     $iCount = count($aArray); 
    } 
    $fTaken = microtime(true) - $fStartTime; 

    print "took $fTaken seconds\n<br>"; 
} 

function TestCountA(&$aArray) 
{ 
    $aArray = range(0, 100000); 
    $fStartTime = microtime(true); 
    $bArray = $aArray; 
    for ($iIter = 0; $iIter < 1000; $iIter++) 
    { 
     $iCount = count($bArray); 
    } 
    $aArray = $bArray; 
    $fTaken = microtime(true) - $fStartTime; 

    print "A took $fTaken seconds\n<br>"; 
} 

$nonArray = array(); 
TestCountNon($nonArray); 

$aArray = array(); 
TestCount($aArray); 

$bArray = array(); 
TestCountA($bArray); 
?> 

I risultati sono:

Non took 0.00090217590332031 seconds 
took 17.676940917969 seconds 
A took 0.04144287109375 seconds 

Non era così buono, ma un sacco dannatamente meglio.

+0

Sì, usando un temporaneo locale si evita il tarpit. –

Problemi correlati