c matplotlib - Python traçant un défaut de segmentation




figure title (5)

Si vous êtes sur Linux, lancez python sous gdb

gdb python
(gdb) run /path/to/script.py
## wait for segfault ##
(gdb) backtrace
## stack trace of the c code

Je développe des extensions C à partir de python et j'obtiens quelques segfaults (inévitables lors du développement ...).

Je cherche un moyen d'afficher à quelle ligne de code se produit la segfault (une idée est comme tracer chaque ligne de code), comment je peux faire ça?


Voici un moyen d'afficher le nom de fichier et le numéro de ligne de chaque ligne de Python exécutée par votre code:

import sys

def trace(frame, event, arg):
    print "%s, %s:%d" % (event, frame.f_code.co_filename, frame.f_lineno)
    return trace

def test():
    print "Line 8"
    print "Line 9"

sys.settrace(trace)
test()

Sortie:

call, test.py:7
line, test.py:8
Line 8
line, test.py:9
Line 9
return, test.py:9

(Vous voudrez probablement écrire la sortie de trace dans un fichier, bien sûr.)


Segfaults à partir d'extensions C sont très souvent le résultat de ne pas incrémenter un nombre de références lorsque vous créez une nouvelle référence à un objet. Cela les rend très difficile à traquer car la segfault se produit seulement après que la dernière référence a été retirée de l'objet, et même souvent souvent seulement lorsqu'un autre objet est alloué.

Vous ne dites pas combien de code d'extension C vous avez écrit jusqu'ici, mais si vous débutez, réfléchissez si vous pouvez utiliser ctypes ou Cython . Les ctypes ne sont peut-être pas assez flexibles pour vos besoins, mais vous devriez être capable de créer des liens vers n'importe quelle bibliothèque C avec Cython et avoir tous les comptes de référence automatiquement à votre disposition.

Ce n'est pas toujours suffisant: si vos objets Python et les objets C sous-jacents ont des durées de vie différentes, vous pouvez toujours avoir des problèmes, mais cela simplifie considérablement les choses.


Il existe quelques extensions python non documentées pour gdb.

À partir de la source Python, Tools/gdb/libpython.py (il n'est pas inclus dans une installation normale).

Mettez ceci dans sys.path

Alors:

# gdb /gps/python2.7_x64/bin/python coredump
...
Core was generated by `/usr/bin/python script.py'.
Program terminated with signal 11, Segmentation fault.
#0  call_function (oparg=<optimized out>, pp_stack=0x7f9084d15dc0) at Python/ceval.c:4037
...
(gdb) python
>import libpython
>
>end
(gdb) bt
#0  call_function (oparg=<optimized out>, pp_stack=0x7f9084d15dc0) at Python/ceval.c:4037
#1  PyEval_EvalFrameEx ([email protected]=
    Frame 0x7f9084d20ad0, 
    for file /usr/lib/python2.7/site-packages/librabbitmq/__init__.py, line 220, 
    in drain_events (self=<Connection(channels={1: <Channel(channel_id=1, connection=<...>, is_open=True, connect_timeout=4, _default_channel=<....(truncated), [email protected]=0) at Python/ceval.c:2681
...
(gdb) py-list
 218            else:
 219                timeout = float(timeout)
>220            self._basic_recv(timeout)
 221
 222        def channel(self, channel_id=None):

Comme vous pouvez le voir, nous avons maintenant une visibilité sur la pile Python correspondant à la chaîne d'appel CPython.

Quelques mises en garde:

  • Votre version de gdb doit être supérieure à 7 et doit être compilée avec --with-python
  • gdb embarque python (en liant à libpython ), il ne l'exécute pas dans un sous-shell. Cela signifie qu'il ne correspond pas nécessairement à la version de python qui est sur $PATH .
  • Vous devez télécharger libpython.py partir de n'importe quelle version de la source Python correspondant à celle de gdb .
  • Vous devrez peut-être exécuter gdb en tant que root - si c'est le cas, vous devrez peut-être configurer sys.path pour qu'il corresponde à celui du code que vous déboguez.

Si vous ne pouvez pas copier libpython.py dans sys.path vous pouvez ajouter son emplacement à sys.path comme ceci:

(gdb) python
>import sys
>sys.path.append('/path/to/containing/dir/')
>import libpython
>
>end

Ceci est quelque peu mal documenté dans les docs de développement python , le wiki fedora et le wiki python

Si vous avez un gdb plus ancien ou que vous ne pouvez pas le faire, il y a aussi un gdbinit dans la source Python que vous pouvez copier dans ~/.gdbinit et ajouter des fonctionnalités similaires


J'ai quelques années de retard ici, mais:

Dans 'Modifier 4/5/6' du message original, vous utilisez la construction:

$ /usr/bin/time cat big_file | program_to_benchmark

C'est faux de plusieurs façons:

  1. Vous êtes en train de chronométrer l'exécution de `cat`, pas votre repère. L'utilisation de l'UC 'user' et 'sys' affichée par `time` est celle de` cat`, pas votre programme référencé. Pire encore, le temps «réel» n'est pas nécessairement exact. En fonction de l'implémentation de `cat` et des pipelines dans votre système d'exploitation local, il est possible que` cat` écrit un buffer géant final et qu'il se termine bien avant que le processus de lecture ne termine son travail.

  2. L'utilisation de `cat` est inutile et en fait contre-productive; vous ajoutez des pièces mobiles. Si vous étiez sur un système suffisamment ancien (c.-à-d. Avec un seul processeur et - dans certaines générations d'ordinateurs - E / S plus rapide que CPU) - le simple fait que `cat` fonctionnait pourrait considérablement colorer les résultats. Vous êtes également soumis à toute mise en mémoire tampon d'entrée et de sortie et à tout autre traitement que `cat` pourrait effectuer. (Cela vous mériterait probablement un prix «Useless Use of Cat» si j'étais Randal Schwartz: https://en.wikipedia.org/wiki/Cat_(Unix)#UUOC_(Useless_Use_Of_Cat) )

Une meilleure construction serait:

$ /usr/bin/time program_to_benchmark < big_file

Dans cette instruction c'est le shell qui ouvre big_file, en le passant à votre programme (enfin, en fait `time` qui exécute alors votre programme en tant que sous-processus) en tant que descripteur de fichier déjà ouvert. 100% de la lecture du fichier est strictement la responsabilité du programme que vous essayez de comparer. Cela vous donne une lecture réelle de ses performances sans complications parasites.

Je mentionnerai deux "correctifs" possibles, mais faux, qui pourraient aussi être considérés (mais je les "numérote" différemment car ce ne sont pas des choses qui étaient fausses dans le message original):

R. Vous pouvez "réparer" ceci en ne chronométrant que votre programme:

$ cat big_file | /usr/bin/time program_to_benchmark

B. ou en chronométrant l'ensemble du pipeline:

$ /usr/bin/time sh -c 'cat big_file | program_to_benchmark'

Ce sont faux pour les mêmes raisons que # 2: ils utilisent encore `cat` inutilement. Je les mentionne pour plusieurs raisons:

  • ils sont plus «naturels» pour les personnes qui ne sont pas entièrement à l'aise avec les fonctions de redirection d'E / S du shell POSIX

  • il peut y avoir des cas où `cat` est nécessaire (par exemple: le fichier à lire nécessite un certain privilège d'accès, et vous ne voulez pas accorder ce privilège au programme à tester:` sudo cat / dev / sda | / usr / bin / time mon_compression_test --no-output`)

  • en pratique , sur les machines modernes, le «chat» ajouté dans le pipeline n'a probablement aucune conséquence réelle

Mais je dis cette dernière chose avec une certaine hésitation. Si nous examinons le dernier résultat dans 'Edit 5' -

$ /usr/bin/time cat temp_big_file | wc -l
0.01user 1.34system 0:01.83elapsed 74%CPU ...

- ceci indique que `cat` a consommé 74% du CPU pendant le test; et en effet 1,34 / 1,83 est d'environ 74%. Peut-être une course de:

$ /usr/bin/time wc -l < temp_big_file

aurait pris seulement les 49 secondes restantes! Probablement pas: `cat` a dû payer pour les appels système read () (ou équivalent) qui ont transféré le fichier de 'disk' (en fait le cache buffer), ainsi que les pipes pour les envoyer à` wc`. Le test correct aurait toujours dû faire ces appels read (); seuls les appels write-to-pipe et read-from-pipe auraient été sauvegardés, et ceux-ci devraient être plutôt bon marché.

Pourtant, je prédis que vous seriez en mesure de mesurer la différence entre `fichier cat | wc -l` et `wc -l <fichier` et trouve une différence notable (pourcentage à 2 chiffres). Chacun des tests les plus lents aura payé une pénalité similaire en temps absolu; ce qui représenterait cependant une fraction plus petite de son temps total plus important.

En fait, j'ai fait quelques tests rapides avec un fichier de 1,5 gigaoctets sur un système Linux 3.13 (Ubuntu 14.04), obtenant ces résultats (ce sont en fait les résultats 'best of 3', après avoir amorcé le cache, bien sûr):

$ time wc -l < /tmp/junk
real 0.280s user 0.156s sys 0.124s (total cpu 0.280s)
$ time cat /tmp/junk | wc -l
real 0.407s user 0.157s sys 0.618s (total cpu 0.775s)
$ time sh -c 'cat /tmp/junk | wc -l'
real 0.411s user 0.118s sys 0.660s (total cpu 0.778s)

Notez que les deux résultats du pipeline indiquent avoir pris plus de temps CPU (user + sys) que de temps réel. C'est parce que j'utilise la commande intégrée 'time' du shell (Bash), qui est consciente du pipeline; et je suis sur une machine multi-core où des processus séparés dans un pipeline peuvent utiliser des cœurs séparés, en accumulant du temps CPU plus rapidement qu'en temps réel. En utilisant / usr / bin / time, je vois que le temps processeur est plus petit que le temps réel, ce qui montre qu'il ne peut que passer l'unique élément de pipeline sur sa ligne de commande. En outre, la sortie du shell donne des millisecondes alors que / usr / bin / time ne donne que des centièmes de seconde.

Donc, au niveau d'efficacité de `wc -l`, le` cat` fait une énorme différence: 409/283 = 1,453 ou 45,3% de plus en temps réel, et 775/280 = 2,768, ou un énorme 177% CPU en plus! Sur ma boîte de test aléatoire c'était-là-à-le-temps.

Je devrais ajouter qu'il y a au moins une autre différence significative entre ces styles de test, et je ne peux pas dire si c'est un avantage ou une faute; vous devez décider vous-même:

Lorsque vous exécutez `cat big_file | / usr / bin / time my_program`, votre programme reçoit une entrée d'un tuyau, exactement à la vitesse envoyée par `cat`, et en morceaux pas plus grand que écrit par` cat`.

Lorsque vous exécutez `/ usr / bin / time mon_programme <big_file`, votre programme reçoit un descripteur de fichier ouvert dans le fichier réel. Votre programme - ou dans la plupart des cas les bibliothèques d'E / S de la langue dans laquelle il a été écrit - peut prendre différentes actions lorsqu'il est présenté avec un descripteur de fichier faisant référence à un fichier normal. Il peut utiliser mmap (2) pour mapper le fichier d'entrée dans son espace d'adressage, au lieu d'utiliser les appels système read (2) explicites. Ces différences pourraient avoir un effet beaucoup plus important sur vos résultats de benchmark que le faible coût d'exécution du binaire `cat`.

Bien sûr, il s'agit d'un résultat de référence intéressant si le même programme fonctionne de manière sensiblement différente entre les deux cas. Cela montre que, en effet, le programme ou ses bibliothèques d'E / S font quelque chose d'intéressant, comme utiliser mmap (). Donc, dans la pratique, il peut être bon de faire fonctionner les repères dans les deux sens; peut-être en actualisant le résultat «chat» par un petit facteur pour «pardonner» le coût de l'exécution de «chat» lui-même.





python c debugging