2013-04-13 6 views
5

Quando si esegue questo semplice script ottengo l'output pubblicato di seguito. Mi fa pensare che ci sia una perdita di memoria nel mio codice o nello stack Zend Framework/Magento. Questo problema si verifica durante l'iterazione di qualsiasi tipo di raccolta Magento. C'è qualcosa che mi manca o che non funziona?Perdita di memoria in Magento/Zend Framework

Script:

$customersCollection = Mage::getModel('customer/customer')->getCollection(); 

foreach($customersCollection as $customer) { 
    $customer->load(); 
    $customer = null; 
    echo memory_get_usage(). "\n"; 
} 

uscita:

102389104 
102392920 
... 
110542528 
110544744 
+0

@IMSoP anzi ... –

+0

Questo è un altro [di riferimento] (http://ringsdorff.net/2009/07/23/guest-post-fix-for-memory-leaks-in-magento) che ho trovato. Sembra che il problema risieda in riferimenti circolari. – osondoar

+0

@osondoar Se si utilizza almeno PHP 5.3 (che dovrebbe essere ormai) i riferimenti circolari verranno catturati da un Garbage Collector, sebbene non immediatamente. Tuttavia, vedi la mia risposta sul perché il tuo esempio non libererà anche riferimenti non circolari. – IMSoP

risposta

7

Il tuo problema è che si sta emissione query piuttosto costosi, con ogni iterazione, quando è possibile caricare i dati necessari attraverso le query di raccolta:

$collection = Mage::getResourceModel('customer/customer_collection')->addAttributeToSelect('*'); 

farà lo stesso, ma tutti in una query. L'avvertenza a questo approccio è che se ci sono osservatori di eventi personalizzati per eventi customer_load_before o customer_load_after (per questi non ci sono osservatori di base), l'osservatore dovrà essere eseguito manualmente per ogni modello di dati.

Edit: credito per osonodoar per individuare un riferimento di classe non corretta (cliente/cliente vs cliente/customer_collection)

3

La memoria per un oggetto (o altro valore) possono essere liberati solo quando non ci sono riferimenti ad essa in qualsiasi parte del processo di PHP. Nel tuo caso, la riga $customer = null diminuisce di un numero il numero di riferimenti a quell'oggetto, ma non lo raggiunge.

Se si considera un ciclo più semplice, questo può diventare più chiaro:

$test = array('a' => 'hello'); 
foreach ($test as $key => $value) 
{ 
    // $value points at the same memory location as $test['a'] 
    // internally, that "zval" has a "refcount" of 2 

    $value = null; 
    // $value now points to a new memory location, but $test['a'] is unnaffected 
    // the refcount drops to 1, but no memory is freed 
} 

Poiché si utilizza oggetti, c'è un tocco aggiunto - è possibile modificare l'oggetto all'interno del ciclo senza creare una nuova copia di esso :

$test = array('a' => new __stdClass); 
// $test['a'] is an empty object 

foreach ($test as $key => $value) 
{ 
    // $value points at the same object as $test['a'] 
    // internally, that object has a "refcount" of 2 

    $value->foo = "Some data that wasn't there before"; 
    // $value is still the same object as $test['a'], but that object now has extra data 
    // This requires additional memory to store that object 

    $value = null; 
    // $value now points to a new memory location, but $test['a'] is unnaffected 
    // the refcount drops to 1, but no memory is freed 
} 

// $test['a']->foo now contains the string assigned in the loop, consuming extra memory 

nel suo caso, il metodo ->load() è presumibilmente espandendo la quantità di dati in ciascuno degli organi di $customersCollection a loro volta, richiedono più memoria per ciascuno. Ispezionare $customersCollection prima e dopo il ciclo probabilmente lo confermerebbero.

0

Prima di tutto, quando le variabili di blocco utilizzano unset (variabile $) anziché $ variabile = null. Fa essenzialmente la stessa cosa, ma è molto più chiaro per quanto riguarda il tuo intento.

In secondo luogo, PHP è destinato a morire - perdite di memoria non sono un problema enorme, come una richiesta di PHP dura forse pochi secondi, e quindi il processo muore e tutta la memoria che stava usando è liberata per la prossima richiesta. A meno che non ci siano problemi di ridimensionamento, non c'è nulla di cui preoccuparsi.

Modifica: che non vuol dire non preoccuparti della qualità del tuo codice, ma per qualcosa di simile, probabilmente non vale la pena tentare di impedire che accada a meno che non stia causando problemi.

+0

Grazie per i commenti. Il problema principale qui è che quando si passa da 50000+ clienti emergono problemi di allocazione della memoria. Ad esempio, hai impostato il limite di memoria impostato su 512 Mb, lo script si arresta in modo anomalo – osondoar

0

altra via d'uscita per gestire perdita di memoria è che exec chiamata entro ciclo e lasciare che la funzione exec fare il parte del lavoro che si traduce in perdita di memoria.

Quindi una volta completata la parte e si interrompe tutta la perdita di memoria all'interno di quel exec verrà rilasciato.

Quindi con enormi iterazioni questa perdita di memoria che continua ad aggiungersi in caso contrario verrà presa in considerazione.

0

La risposta @benmarks sarebbe l'approccio giusto qui, poiché chiamare load() all'interno di un ciclo è una chiamata molto costosa.

Chiamando $ customer-> load() allocare la memoria in modo incrementale a cui fare riferimento da $ customersCollection, tale memoria non verrà rilasciata fino alla fine del ciclo.

Tuttavia, se load() deve essere chiamato per qualsiasi motivo, il codice seguente non perderà memoria, poiché il GC rilascia tutta la memoria allocata dal modello in ogni iterazione.

$customersCollection = Mage::getModel('customer/customer')->getCollection(); 

foreach($customersCollection as $customer) { 
    $customerCopy = Mage::getModel('customer/customer')->load($customer->getId()); 

    //Call to $customerCopy methods 

    echo memory_get_usage(). "\n"; 
} 
+1

Il tuo codice perde per me. Aggiungendo '$ customerCopy-> clearInstance();' il trucco però. – benmarks

+0

Uhm ... quando eseguo sembra che ci sia una perdita, MA il GC è in grado di rilasciare memoria ogni 5 secondi circa, mantenendo il consumo di memoria sotto un certo livello. Diverse configurazioni PHP/Magento potrebbero spiegare perché otteniamo risultati diversi. Ad ogni modo, la tua risposta è la strada da percorrere, grazie! – osondoar