processus python croissance des problèmes de mémoire, des solutions et des outils

spectacle

Environnement d'exploitation:

# uname -a
Linux ** 3.10.0-327.el7.x86_64 #1 SMP Thu Nov 19 22:10:57 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

# python2 --version
Python 2.7.5

# cat /etc/*-release
CentOS Linux release 7.2.1511 (Core)

programme de python après une longue période de temps (une plus grande charge) en cours d'exécution pendant un certain temps, la mémoire de python pour les processus du système continue à augmenter:

# ps aux | grep python2
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root     124910 10.2  0.8 5232084 290952 ?      Sl   Mar17 220:37 python2 offline.py restart
#                                 ~~~~~~
#                                 290M 内存占用

processus ici python grand nombre de demandes en cours de traitement, la mémoire continue d'augmenter, mais après la chute de pression de charge finale, et ne laissez pas tomber une mémoire.

solutions

Pour gagner du temps au lecteur, cette conclusion est donnée en premier, suivi par les étapes de dépannage détaillées de réenregistrement.

Nous progressivement en plusieurs étapes pour localiser le problème:

  • Tout d'abord, de déterminer ce que le programme a été fait, qu'il y ait un comportement anormal.
  • Après l'exclusion d'un comportement anormal, utilisation de la mémoire pour voir le python, si tous les objets récupérés ont été récupérés.
  • Après avoir retiré le problème de fuite de mémoire dans le python de collecte des ordures, accédez au problème de la mise en œuvre libc malloc.

La solution finale est très simple, remplacement direct pour le module malloc tcmalloc:

LD_PRELOAD="/usr/lib64/libtcmalloc.so" python x.py

Processus d'orientation

gdb-python: programme python pour savoir ce qu'il faut faire

Il faut d'abord déterminer ce python fait, n'est pas une tâche importante de la consommation de mémoire normale est en cours d'exécution, blocage de comportement anormal.

Cet aspect peut être utilisé pour aider gdb, du gdb-7 départ, le soutien gdb gdb en python pour réaliser l'expansion. Comme on peut le programme de mise au point c, vérifier avec gdb pour les fils de python, pile d'appel et ainsi de suite.

Et vous pouvez appeler pile code c à l' intérieur du code python et imprimer en même temps .

Une telle incertitude est un problème de code python ou question son code sous-jacent c quand utile.

Les étapes détaillées peuvent être appelées  débogage avec GDB .


prêt gdb

Tout d'abord, installez le python debuginfo:

# debuginfo-install python-2.7.5-39.el7_2.x86_64

Dans le debuginfo d'absence, en cours d'exécution gdb pas en arrière sera rapide blabla, suivez les instructions pour poursuivre l'installation, etc.:

Missing separate debuginfos, use: debuginfo-install python-2.7.5-39.el7_2.x86_64

accès gdb

Ensuite, nous pouvons utiliser gdb attacher directement à un processus de python pour voir son état de fonctionnement:

# gdb python 11122

Après avoir entré joindre une gdb, vous pouvez faire plus de quelques étapes de base pour vérifier:


Afficher le fil

(gdb) info threads
  Id   Target Id         Frame
  206  Thread 0x7febdbfe3700 (LWP 124916) "python2" 0x00007febe9b75413 in select () at ../sysdeps/unix/syscall-template.S:81
  205  Thread 0x7febdb7e2700 (LWP 124917) "python2" 0x00007febe9b75413 in select () at ../sysdeps/unix/syscall-template.S:81
  204  Thread 0x7febdafe1700 (LWP 124918) "python2" 0x00007febe9b75413 in select () at ../sysdeps/unix/syscall-template.S:81
  203  Thread 0x7febda7e0700 (LWP 124919) "python2" 0x00007febe9b7369d in poll () at ../sysdeps/unix/syscall-template.S:81

blocage de verrouillage général presque peut voir ici, il y a un fil coincé dans la fonction xx_wait comme.

Avant d'utiliser cette méthode de localisation d'un module d'enregistrement de python en raison de l'exécution dans une fourchette de processus multithread, ce qui après verrouillage de l'enregistrement est verrouillé à nouveau processus fourche, mais le fil ne fourche pas débloquer de nouveaux processus a provoqué la mort problème de verrouillage.


En regardant la pile d'appel

Si vous rencontrez un problème avec un fil, le commutateur de fil pour afficher la pile d'appel pour déterminer les étapes de mise en œuvre spécifique, utilisez la bt commande suivante :

(gdb) bt
#16 0x00007febea8500bd in PyEval_EvalCodeEx (co=<optimized out>, globals=<optimized out>, locals=locals@entry=0x0, args=<optimized out>,
    argcount=argcount@entry=1, kws=0x38aa668, kwcount=2, defs=0x3282a88, defcount=2, closure=closure@entry=0x0)
    at /usr/src/debug/Python-2.7.5/Python/ceval.c:3330

...

#19 PyEval_EvalFrameEx (
    f=f@entry=Frame 0x38aa4d0, for file t.py, line 647, in run (part_num=2, consumer=<...

bt Commande c peut non seulement voir la pile d'appel, mais montre aussi la source de python de pile d'appel, comme ci-dessus, c est le cadre 16, le cadre-19 a montré un code source python qui les correspond à une ligne.

Si vous ne voyez le code de la pile d'appel en python utilisant py-btla commande suivante :

(gdb) py-bt
#1 <built-in method poll of select.epoll object at remote 0x7febeacc5930>
#3 Frame 0x3952450, for file /usr/lib64/python2.7/site-packages/twisted/internet/epollreactor.py, line 379, in doPoll (self=<...
    l = self._poller.poll(timeout, len(self._selectables))
#7 Frame 0x39502a0, for file /usr/lib64/python2.7/site-packages/twisted/internet/base.py, line 1204, in mainLoop (self=<...

py-btIndique le source de python de pile d'appel, paramètres d'appel et le code de la rangée.


coredump

Si vous voulez plus de temps tracking est préférable de traiter tout le programme python d'information sur Coredump, après que le fichier de base pour l'analyse pour ne pas affecter le déroulement du programme.

(gdb) generate-core-file

Cette commande vider le programme en cours gdb joindre au répertoire d'exécution, le nom core.<pid>, et ensuite utiliser gdb charger le fichier de base, pile d'impression, l' analyse des variables de la montre, etc., sans avoir à attacher à un programme en cours d' exécution:

# gdb python core.<pid>

D'autres commandes

D' autres commandes peuvent être saisies dans gdb py<TAB><TAB> voir, gdb et la commande correspondant, par exemple:

(gdb) py
py-bt               py-list             py-print            python
py-down             py-locals           py-up               python-interactive
  • py-uppy-down Il peut être utilisé pour passer à la précédente ou la gare d'appel python cadre.
  • py-locals Les variables locales utilisées pour imprimer

Et ainsi de suite et ainsi de suite peut également utiliser gdb en helpvue de la commande help:

(gdb) help py-print
Look up the given python variable name, and print it

Dans le processus de suivi avec gdb-python exclure le problème de la logique du programme, puis continuer à suivre les fuites de mémoire:

pyrasite: connexion dans le programme python

pyrasite est 1 peut se connecter directement un programme en cours d'exécution de python, ouvert ipython un terminal interactif similaire pour exécuter le programme pour vérifier l'état de la commande.

Cela nous donne débogueur offre une grande commodité. Tout simplement artefact.

Installation:

# pip install pyrasite
...

# pip show pyrasite
Name: pyrasite
Version: 2.0
Summary: Inject code into a running Python process
Home-page: http://pyrasite.com
Author: Luke Macken
...

Relié au programme en question, commencer à recueillir des informations:

pyrasite-shell <pid>
>>>

Ensuite , vous pouvez dans <pid>le processus d'appel à un code python arbitraire pour voir l'état du processus.

Voici quelques petits ascenseur public (spéciale Ce que je méthode d'entrée moyenne outil ..) peut être utilisé pour afficher l'état de la mémoire dans le processus:

psutil afficher l'état du processus de python

pip install psutil

D'abord, regardez la quantité de processus mémoire système python RSS:

pyrasite-shell 11122
>>> import psutil, os
>>> psutil.Process(os.getpid()).memory_info().rss
29095232

commande Ps affiche les résultats de base et cohérente

rss La taille mémoire réelle (set résident) du procédé (à 1024 unités d'octet).

Guppy obtenir divers objets utilisation de la mémoire d'occupation

guppy peut être utilisé pour imprimer une variété d'objets chacun prennent beaucoup de place, s'il n'y a pas de libération cible de processus de python, entraînant l'empreinte mémoire accrue, vous pouvez vérifier par guppy:

De même, en suivant les étapes sont pyrasite-coquille, attacher au processus cible après l'opération.

# pip install guppy
from guppy import hpy
h = hpy()

h.heap()
# Partition of a set of 48477 objects. Total size = 3265516 bytes.
#  Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
#      0  25773  53  1612820  49   1612820  49 str
#      1  11699  24   483960  15   2096780  64 tuple
#      2    174   0   241584   7   2338364  72 dict of module
#      3   3478   7   222592   7   2560956  78 types.CodeType
#      4   3296   7   184576   6   2745532  84 function
#      5    401   1   175112   5   2920644  89 dict of class
#      6    108   0    81888   3   3002532  92 dict (no owner)
#      7    114   0    79632   2   3082164  94 dict of type
#      8    117   0    51336   2   3133500  96 type
#      9    667   1    24012   1   3157512  97 __builtin__.wrapper_descriptor
# <76 more rows. Type e.g. '_.more' to view.>

h.iso(1,[],{})
# Partition of a set of 3 objects. Total size = 176 bytes.
#  Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
#      0      1  33      136  77       136  77 dict (no owner)
#      1      1  33       28  16       164  93 list
#      2      1  33       12   7       176 100 int

A travers les étapes ci-dessus, vous pouvez voir que pas beaucoup d'objet python occupe plus de mémoire.

Les objets ne peuvent être récupérés

python lui-même est la collecte des ordures, mais la situation est en quelque sorte d'objets du programme python ne peut pas être ordures (objet irrécouvrable), remplir deux conditions:

  • références circulaires
  • Un objet de la chaîne définit une référence circulaire __del__procédé.

L'argument officiel est qu'un ensemble d'objets avec le module de références circulaires est identifié comme recyclable, mais vous devez faire appel à chaque objet des __del__méthodes pour récupérer, mais définis par l' utilisateur des __del__objets, le système gc ne sait pas appeler doit sonner qui , __del__ne peut pas être récupéré. ces objets.

objets Python ne peuvent pas être recyclés continuera d'occuper la mémoire, quand on soupçonne qu'il ya un problème trouve ici qui ne peuvent pas être des objets recyclés cause de la mémoire continue d'augmenter.

Nous essayons donc de lister tous les objets ne peuvent pas être recyclés.

Ceci a été déterminé par la suite de ne pas les problèmes de mémoire causés par Autorisation non disponible. Ne peut pas être récupéré par un cours peut gc.get_objects() être inscrit, et gc.collect()rejoindre après l'appel à gc.garbagela liste. Mais ça ne nous a pas la présence de ces objets.

Trouvez les objets non recouvrables:

pyrasite-shell 11122
>>> import gc
>>> gc.collect() # first run gc, find out uncollectable object and put them in gc.garbage
                 # output number of object collected
>>> gc.garbage   # print all uncollectable objects
[]               # empty

Si vous imprimez la dernière étape ci - dessus tout objet qui ne peut être recyclé, nous devons examiner d' autres références circulaires sur la chaîne sur laquelle l' objet contient des __del__méthodes.

Voici un exemple de la façon de générer ne pouvait récupérer l'objet:

Des exemples d'objets ne peuvent pas être récupérés?

uncollectible.py

from __future__ import print_function

import gc


'''
This snippet shows how to create a uncollectible object:
It is an object in a cycle reference chain, in which there is an object
with __del__ defined.
The simpliest is an object that refers to itself and with a __del__ defined.

    > python uncollectible.py

    ======= collectible object =======

    *** init,     nr of referrers: 4
                  garbage:         []
                  created:         collectible: <__main__.One object at 0x102c01090>
                  nr of referrers: 5
                  delete:
    *** __del__ called
    *** after gc, nr of referrers: 4
                  garbage:         []

    ======= uncollectible object =======

    *** init,     nr of referrers: 4
                  garbage:         []
                  created:         uncollectible: <__main__.One object at 0x102c01110>
                  nr of referrers: 5
                  delete:
    *** after gc, nr of referrers: 5
                  garbage:         [<__main__.One object at 0x102c01110>]

'''


def dd(*msg):
    for m in msg:
        print(m, end='')
    print()


class One(object):

    def __init__(self, collectible):
        if collectible:
            self.typ = 'collectible'
        else:
            self.typ = 'uncollectible'

            # Make a reference to it self, to form a reference cycle.
            # A reference cycle with __del__, makes it uncollectible.
            self.me = self

    def __del__(self):
        dd('*** __del__ called')


def test_it(collectible):

    dd()
    dd('======= ', ('collectible' if collectible else 'uncollectible'), ' object =======')
    dd()

    gc.collect()
    dd('*** init,     nr of referrers: ', len(gc.get_referrers(One)))
    dd('              garbage:         ', gc.garbage)

    one = One(collectible)
    dd('              created:         ', one.typ, ': ', one)
    dd('              nr of referrers: ', len(gc.get_referrers(One)))

    dd('              delete:')
    del one

    gc.collect()

    dd('*** after gc, nr of referrers: ', len(gc.get_referrers(One)))
    dd('              garbage:         ', gc.garbage)


if __name__ == "__main__":
    test_it(collectible=True)
    test_it(collectible=False)

Le code ci - dessus crée deux objets, on peut être recyclé, on ne peut pas être recyclé, ils définissent les deux __del__méthodes, la seule différence est que les références elles - mêmes (constituant ainsi un anneau de référence).

Si cette étape trouver une référence circulaire, il est nécessaire d'étudier plus ce qui a causé la relation entre la référence référence circulaire, puis détruit la référence circulaire, de sorte que devient l'objet peut être recyclé.

objgraph trouver une référence circulaire

# pip install objgraph
pyrasite-shell 11122
>>> import objgraph
>>> objgraph.show_refs([an_object], filename='sample-graph.png')

Dans l'exemple ci-dessus, une image sera générée localement, est décrit par le diagramme peut être référencé par an_object à:

Une référence spécifique:  objgraph

Dans cette étape, on n'a pas trouvé l'objet ne peut être recyclé, et enfin nous soupçonnons que le problème de la glibc malloc quand utiliser tcmalloc après que le problème de malloc glibc alternative par défaut est fixé.


Archiver

Publié 13 articles originaux · louange gagné 78 · Vues 450000 +

Je suppose que tu aimes

Origine blog.csdn.net/bluehawksky/article/details/100655891
conseillé
Classement