2010-04-10 22 views
30

Ich habe eine Python C-Erweiterung entwickelt, die Daten von Python empfängt und einige CPU-intensive Berechnungen berechnet. Es ist möglich, die C-Erweiterung zu profilieren? Das Problem hier ist, dass das Schreiben eines Beispieltests in C, der profiliert werden soll, eine Herausforderung darstellen würde, da der Code auf bestimmten Eingaben und Datenstrukturen beruht (generiert durch den Python-Steuercode).Profiling Python C Erweiterungen

Haben Sie Vorschläge?

Antwort

17

Ich habe meinen Weg mit google-perftools gefunden. Der Trick bestand darin, die Funktionen StartProfiler und StopProfiler in Python (in meinem Fall durch Cython) zu packen.

Zum Profilieren der C-Erweiterung reicht es aus, den Python-Code innerhalb der Aufrufe von StartProfiler und StopProfiler zu umbrechen.

from google_perftools_wrapped import StartProfiler, StopProfiler 
impor c_extension # extension to profile c_extension.so 

StartProfiler("output.prof") 
... calling the interesting functions from the C extension module ... 
StopProfiler() 

Dann zum Beispiel zur Analyse können Sie in Callgrind-Format exportieren und das Ergebnis in kcachegrind sehen:

pprof --callgrind c_extension.so output.prof > output.callgrind 
kcachegrind output.callgrind 
+0

Vielen Dank für diesen Hinweis !! Ich habe tatsächlich nach dem gleichen gesucht. Ich werde es versuchen. – ThR37

+0

EDIT: Es funktioniert in der Tat perfekt! Ein einfacher Wrapper mit Ctypes ist OK, auch wenn ich manchmal während der CPU Profilerstellung Segdefaults bekomme (aber das ist "normal" und im Doc erklärt ... Ich benutze x86_64 :() – ThR37

+0

Vielen Dank für dieses kleine Nugget sehr, sehr nützlich :-) Was ich sehe, ist, dass pprof (oder eher, google-pprof im Paket für Debian) ist, dass ich nicht so viele Symbole wie beim Profiling des gleichen Codes entmagnetisiert bekomme mit Valgrind. Kann es sein, dass ich beim Kompilieren -pg angeben muss? – miquelramirez

4

Mit gprof, können Sie jedes Programm profilieren, die properly compiled und verknüpft war (gcc -pg etc, in gprof ‚s Fall). Wenn Sie eine Python-Version verwenden, die nicht mit gcc erstellt wurde (z. B. die vorkompilierte Windows-Version der PSF), müssen Sie untersuchen, welche gleichwertigen Tools für diese Plattform und Toolchain existieren (im Windows PSF-Fall kann möglicherweise mingw helfen)). Dort kann es "irrelevante" Daten geben (interne C-Funktionen in der Python-Laufzeit), und wenn ja, sind die Prozentsätze von gprof möglicherweise nicht anwendbar - aber die absoluten Nummern (von Aufrufen und deren Dauer) sind immer noch gültig, und Sie können die Ausgabe gprof nachbearbeiten (zB mit einem kleinen Python-Skript ;-), um die irrelevanten Daten auszuschließen und die gewünschten Prozentsätze zu berechnen.

+1

Ich habe immer noch einige Probleme mit dieser, aber vielleicht ist es nur meine Schuld. Nach dem Kompilieren und Verknüpfen (gcc) der Python-Quelle erzeugt die ausführbare Datei korrekt die Datei gmon.out. Wenn ich die Skripte, die die C-Erweiterungen (* .so) mit -pg-Flags geladen, lädt die Profiling-Ausgabe (gprof/path/custom/python, noch gprof-Erweiterung.So) zeigt die Funktionsaufrufe in der C-Bibliothek nicht an. Ich vermisse etwas? – pygabriel

+2

gprof spielt nicht gut mit dlopen(), vermutlich, weil es seine Speicherkarten initialisiert, bevor die Bibliothek geladen wird. Lediglich das Kompilieren mit dem Flag -pg hilft gprof nicht, wenn der exektable Host (in diesem Fall Python) nicht direkt mit der .so-Datei verknüpft ist. –

22

Nach dem Kommentar von pygabriel habe ich beschlossen, ein Paket zu laden, um PyPI dass ein Profiler implementiert für Python-Erweiterungen mit dem cpu-Profiler von google-perftools: http://pypi.python.org/pypi/yep

+0

Danke, ich fand das sehr nützlich und ganz einfach damit! – robince

+0

Danke Fabian, hoffentlich macht das den Trick! –

+0

Funktioniert yep auch den Profilspeicher? –

0

Einer meiner Kollegen sagte mir ltrace(1). Es hat mir in der gleichen Situation sehr geholfen.

Angenommen, das gemeinsame Objekt Name Ihres C extention ist myext.so und Sie wollen benchmark.py auszuführen, dann

ltrace -x @myext.so -c python benchmark.py 

Sein Ausgang ist wie

% time  seconds usecs/call  calls  function 
------ ----------- ----------- --------- -------------------- 
24.88 30.202126  7550531   4 ldap_result 
12.46 15.117625  7558812   2 l_ldap_result4 
12.41 15.059652  5019884   3 ldap_chase_v3referrals 
12.41 15.057678  3764419   4 ldap_new_connection 
12.40 15.050310  3762577   4 ldap_int_open_connection 
12.39 15.042360  3008472   5 ldap_send_server_request 
12.38 15.029055  3757263   4 ldap_connect_to_host 
    0.05 0.057890  28945   2 ldap_get_option 
    0.04 0.052182  26091   2 ldap_sasl_bind 
    0.03 0.030760  30760   1 l_ldap_get_option 
    0.03 0.030635  30635   1 LDAP_get_option 
    0.02 0.029960  14980   2 ldap_initialize 
    0.02 0.027988  27988   1 ldap_int_initialize 
    0.02 0.026722  26722   1 l_ldap_simple_bind 
    0.02 0.026386  13193   2 ldap_send_initial_request 
    0.02 0.025810  12905   2 ldap_int_select 
.... 

Besondere Sorgfalt ist erforderlich, wenn Ihr gemeinsames Objekt hat - oder + in seinem Dateinamen. Diese Zeichen werden nicht so behandelt, wie sie sind (Details siehe).

Der Platzhalter * kann eine Problemumgehung wie -x @myext* anstelle von -x @myext-2.so sein.