J'ai rencontré le message d'erreur redouté, éventuellement un effort laborieux, PHP est à court de mémoire:
Taille mémoire autorisée de #### octets épuisés (tentative d'allocation de #### octets) dans le fichier fichier.php à la ligne 123
Si vous savez ce que vous faites et souhaitez augmenter la limite, voir memory_limit :
ini_set('memory_limit', '16M');
ini_set('memory_limit', -1); // no limit
Il faut se méfier! Vous ne pouvez que résoudre le symptôme et non le problème!
Le message d'erreur pointe vers une ligne contenant une boucle qui, selon moi, perd de la mémoire ou accumule inutilement de la mémoire. J'ai imprimé les instructions memory_get_usage()
à la fin de chaque itération et je peux voir le nombre augmenter lentement jusqu'à atteindre la limite:
foreach ($users as $user) {
$task = new Task;
$task->run($user);
unset($task); // Free the variable in an attempt to recover memory
print memory_get_usage(true); // increases over time
}
Aux fins de cette question, supposons que le pire code de spaghetti imaginable se cache dans la portée globale quelque part dans $user
ou Task
.
Quels outils, astuces PHP ou débogage voodoo peuvent m'aider à trouver et résoudre le problème?
PHP n'a pas de ramasse-miettes. Il utilise le comptage de références pour gérer la mémoire. Ainsi, les sources de fuites de mémoire les plus courantes sont les références cycliques et les variables globales. Si vous utilisez un framework, vous aurez beaucoup de code à parcourir pour le trouver, j'en ai bien peur. L'instrument le plus simple consiste à effectuer des appels sélectifs à memory_get_usage
et à le limiter au point de fuite. Vous pouvez également utiliser xdebug pour créer une trace du code. Exécutez le code avec traces d'exécution et show_mem_delta
.
Il y a plusieurs points de fuite de mémoire possibles en php:
Il est assez difficile de trouver et de réparer les 3 premiers sans une connaissance approfondie du reverse engineering ou du code source php. Pour le dernier, vous pouvez utiliser la recherche binaire pour le code contenant de la fuite de mémoire avec memory_get_usage
Voici une astuce que nous avons utilisée pour identifier les scripts qui utilisent le plus de mémoire sur notre serveur.
Enregistrez le fragment de code suivant dans un fichier, par exemple, /usr/local/lib/php/strangecode_log_memory_usage.inc.php
:
<?php
function strangecode_log_memory_usage()
{
$site = '' == getenv('SERVER_NAME') ? getenv('SCRIPT_FILENAME') : getenv('SERVER_NAME');
$url = $_SERVER['PHP_SELF'];
$current = memory_get_usage();
$peak = memory_get_peak_usage();
error_log("$site current: $current peak: $peak $url\n", 3, '/var/log/httpd/php_memory_log');
}
register_shutdown_function('strangecode_log_memory_usage');
Utilisez-le en ajoutant ce qui suit à httpd.conf:
php_admin_value auto_prepend_file /usr/local/lib/php/strangecode_log_memory_usage.inc.php
Puis analysez le fichier journal à /var/log/httpd/php_memory_log
Vous devrez peut-être touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_log
pour que votre utilisateur Web puisse écrire dans le fichier journal.
J'ai remarqué une fois dans un ancien script que PHP maintiendrait la variable "as" dans le champ d'application même après ma boucle foreach. Par exemple,
foreach($users as $user){
$user->doSomething();
}
var_dump($user); // would output the data from the last $user
Je ne sais pas si les versions futures PHP ont corrigé cela depuis que je l'ai vu. Si tel est le cas, vous pouvez utiliser unset($user)
après la ligne doSomething()
pour l'effacer de la mémoire. YMMV.
Je suis tombé sur le même problème et ma solution a été de remplacer foreach par un habitué. Je ne suis pas sûr des détails, mais il semble que foreach crée une copie (ou en quelque sorte une nouvelle référence) de l'objet. À l'aide d'une boucle for régulière, vous accédez directement à l'élément.
J'ai récemment rencontré ce problème sur une application, dans ce que je suppose être des circonstances similaires. Un script qui s'exécute dans la cli de PHP qui boucle sur de nombreuses itérations. Mon script dépend de plusieurs bibliothèques sous-jacentes. Je pense qu'une bibliothèque en est la cause et j'ai passé plusieurs heures en vain à essayer d'ajouter des méthodes de destruction appropriées à ses classes, mais en vain. Confronté à un long processus de conversion vers une autre bibliothèque (ce qui pourrait s'avérer avoir les mêmes problèmes), j'ai proposé un travail grossier pour résoudre le problème dans mon cas.
Dans ma situation, sur une cli linux, je parcourais plusieurs enregistrements d’utilisateurs et je créais pour chacun d’eux une nouvelle instance de plusieurs classes que j’avais créées. J'ai décidé d'essayer de créer les nouvelles instances des classes en utilisant la méthode exec de PHP afin que ces processus s'exécutent dans un "nouveau thread". Voici un exemple très basique de ce à quoi je me réfère:
foreach ($ids as $id) {
$lines=array();
exec("php ./path/to/my/classes.php $id", $lines);
foreach ($lines as $line) { echo $line."\n"; } //display some output
}
De toute évidence, cette approche a ses limites et il faut être conscient des dangers, car il serait facile de créer un métier de lapin. Cependant, dans de rares cas, cela peut aider à surmonter un point difficile, en attendant de trouver une meilleure solution. , comme dans mon cas.
J'ai récemment remarqué que PHP 5.3 fonctions lambda laissent une quantité de mémoire supplémentaire utilisée lors de leur suppression.
for ($i = 0; $i < 1000; $i++)
{
//$log = new Log;
$log = function() { return new Log; };
//unset($log);
}
Je ne sais pas pourquoi, mais cela semble prendre 250 octets supplémentaires par lambda même après la suppression de la fonction.
Je vous suggère de vérifier le manuel php ou d'ajouter la fonction gc_enable()
pour collecter les déchets ... C'est-à-dire que les fuites de mémoire n'affectent pas le fonctionnement de votre code.
PS: php a un garbage collector gc_enable()
qui ne prend aucun argument.
Je ne l'ai pas vu explicitement mentionné, mais xdebug fait un excellent travail de profilage du temps et de la mémoire (à partir de 2.6 ). Vous pouvez prendre les informations qu’il génère et les transmettre à l’interface utilisateur de votre choix: webgrind (heure uniquement), kcachegrind , qcachegrind ou d’autres et génère des arbres d’appel très utiles et des graphiques pour vous permettre de trouver les sources de vos différents malheurs.
Si ce que vous dites sur PHP ne faisant que GC après une fonction est vrai, vous pouvez envelopper le contenu de la boucle dans une fonction en tant que solution de contournement/expérience.
Un gros problème que j'ai eu était en utilisant create_function . Comme dans les fonctions lambda, il laisse en mémoire le nom temporaire généré.
Une autre cause de fuites de mémoire (dans le cas de Zend Framework) est Zend_Db_Profiler . Assurez-vous que cette option est désactivée si vous exécutez des scripts sous Zend Framework . Par exemple, j'avais dans mon application.ini ce qui suit:
resources.db.profiler.enabled = true
resources.db.profiler.class = Zend_Db_Profiler_Firebug
L'exécution d'environ 25 000 requêtes + charges de traitement auparavant a amené la mémoire à une capacité de Nice de 128 Mo (limite maximale de mémoire).
En mettant juste:
resources.db.profiler.enabled = false
il suffisait de garder moins de 20 Mo
Et ce script s’exécutait dans la CLI, mais il instanciait Zend_Application et exécutait le Bootstrap. Il utilisait donc la configuration "développement".
Cela a vraiment aidé l'exécution du script avec xDebug profiling
Je suis un peu en retard dans cette conversation mais je vais partager quelque chose de pertinent pour Zend Framework.
J'ai eu un problème de fuite de mémoire après avoir installé php 5.3.8 (en utilisant phpfarm) pour travailler avec une application ZF développée avec php 5.2.9. J'ai découvert que la fuite de mémoire était en cours de déclenchement dans le fichier httpd.conf d'Apache, dans la définition de mon hôte virtuel, où il est indiqué SetEnv APPLICATION_ENV "development"
. Après avoir commenté cette ligne, les fuites de mémoire ont cessé. J'essaie de trouver une solution de contournement en ligne dans mon script php (principalement en le définissant manuellement dans le fichier index.php principal).
Je ne l'ai pas vu mentionné ici, mais une chose qui pourrait être utile est d'utiliser xdebug et xdebug_debug_zval ('nomVariable') pour voir le nombre de références.
Je peux également fournir un exemple d'extension PHP prenant le chemin: Z-Ray de Zend Server. Si la collecte de données est activée, la mémoire sera utilisée à chaque itération, comme si la récupération de place était désactivée.