2013-04-13 8 views
5

Beim Ausführen dieses einfachen Skript bekomme ich die Ausgabe unten veröffentlicht. Es lässt mich denken, dass es entweder in meinem Code oder im Zend Framework/Magento Stack ein Speicherleck gibt. Dieses Problem tritt auf, wenn Sie eine beliebige Art von Magento-Sammlung durchlaufen. Gibt es etwas, was ich vermisse oder falsch mache?Speicherleck in Magento/Zend Framework

Script:

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

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

Ausgang:

102389104 
102392920 
... 
110542528 
110544744 
+0

@IMSoP in der Tat ... –

+0

Dies ist eine weitere [Referenz] (http://ringsdorff.net/2009/07/23/guest-post-fix-for-memory-leaks-in-magento) das habe ich gefunden. Es sieht so aus, als ob das Problem auf zirkulären Referenzen liegt. – osondoar

+0

@osondoar Wenn Sie mindestens PHP 5.3 verwenden (was Sie jetzt sein sollten), werden zirkuläre Referenzen von einem Garbage Collector abgefangen, wenn auch nicht sofort. Sehen Sie sich jedoch meine Antwort an, warum Ihr Beispiel selbst nicht-zirkuläre Referenzen nicht freigibt. – IMSoP

Antwort

7

Ihr Problem ist, dass Sie ziemlich teuer Abfragen bei jeder Iteration erteilen, wenn Sie die notwendigen Daten über die Sammlungsabfragen laden können:

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

wird das gleiche tun, aber alle in einer Abfrage. Der Vorbehalt zu diesem Ansatz besteht darin, dass, wenn benutzerdefinierte Ereignisbeobachter für customer_load_before oder customer_load_after Ereignisse vorhanden sind (für diese gibt es keine Kernbeobachter), der Beobachter für jedes Datenmodell manuell ausgeführt werden muss.

Edit: Kredit osonodoar für einen falschen Klassenreferenzspek (Kunden/Kunden vs Kunde/customer_collection)

3

Der Speicher für ein Objekt (oder einen anderen Wert) kann nur freigegeben werden, wenn es keine Verweise auf sie überall in der PHP-Prozess sind. In Ihrem Fall verringert die Zeile $customer = null nur die Anzahl der Verweise auf dieses Objekt um eins, aber sie bewirkt nicht, dass sie Null erreicht.

Wenn Sie eine einfachere Schleife betrachten, kann dies klarer werden:

$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 
} 

Da Sie Objekte verwenden, gibt es einen zusätzlichen Effekt - das Objekt innerhalb der Schleife ändern können, ohne dass eine neue Kopie davon zu schaffen :

$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 

in Ihrem Fall wird die ->load() Methode erweitert vermutlich die Datenmenge, die in jedem der Mitglieder der $customersCollection wiederum mehr Speicher für jede erfordern. Die Überprüfung von $customersCollection vor und nach der Schleife würde dies wahrscheinlich bestätigen.

0

Zunächst einmal, wenn Scharfstellungsvariablen verwendet werden, verwenden Sie unset ($ variable) anstelle von $ variable = null. Es macht im Wesentlichen dasselbe, ist aber viel klarer in Bezug auf Ihre Absicht.

Zweitens soll PHP sterben - Speicherlecks sind kein großes Problem, da eine PHP-Anfrage vielleicht ein paar Sekunden dauert, und dann stirbt der Prozess ab und der gesamte Speicher, den er benutzt hat, ist frei für die nächste Anfrage. Wenn Sie keine Skalierungsprobleme haben, brauchen Sie sich keine Sorgen zu machen.

Edit: was ist nicht zu sagen, machen Sie sich keine Sorgen über die Qualität Ihres Codes, aber für so etwas ist es wahrscheinlich nicht die Mühe wert zu versuchen, es zu verhindern, es sei denn, es verursacht Probleme.

+0

Danke für die Kommentare. Das Hauptproblem besteht darin, dass bei der Iteration von mehr als 50000 Kunden Probleme bei der Speicherreservierung auftreten. Wenn Ihr Speicherlimit beispielsweise auf 512 MB eingestellt ist, stürzt das Skript ab – osondoar

0

Andere Ausweg Speicherleck zu handhaben ist, dass Anruf exec innerhalb der Schleife und lassen Sie die exec-Funktion die tun Jobteil, der zu einem Speicherleck führt.

So sobald es seinen Teil abgeschlossen und beendet alle Speicherleck innerhalb dieser Exec wird freigegeben.

Bei großen Iterationen wird dieser Speicherverlust, der ansonsten hinzugefügt wird, beachtet.

0

@benmarks Antwort wäre hier der richtige Ansatz, da der Aufruf von load() innerhalb einer Schleife ein sehr sehr teurer Aufruf ist.

Durch den Aufruf von $ customer-> load() würde inkrementell Speicher zugewiesen, auf den von $ customersCollection verwiesen wird. Dieser Speicher wird erst am Ende der Schleife freigegeben.

Wenn jedoch load() aus irgendeinem Grund aufgerufen werden muss, wird der folgende Code keinen Speicherverlust verursachen, da der GC den gesamten vom Modell in jeder Iteration zugewiesenen Speicher freigibt.

$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

Ihr Code leckt für mich. Das Hinzufügen von '$ customerCopy-> clearInstance();' funktioniert aber. – benmarks

+0

Uhm ... wenn ich es ausführe, sieht es so aus, als ob es ein Leck gibt, ABER der GC kann den Speicher alle 5 Sekunden oder so freigeben, wobei der Speicherverbrauch unter einem bestimmten Level bleibt. Verschiedene PHP/Magento-Konfigurationen könnten erklären, warum wir unterschiedliche Ergebnisse erhalten. Wie auch immer, Ihre Antwort ist der Weg zu gehen, danke! – osondoar