Blog: Python profilering efter hukommelsesforbrug?

Jeg holder meget af at programmere i Python. Det er klart det bedste programmeringssprog, jeg har arbejdet med.
Det er to ting jeg jævnligt har brug for – at finde ud af hvor i min kode, jeg bruger mest CPU-kraft hhv. mest hukommelse.
Til C/C++ kode har jeg meget godt styr på det men til Python kan jeg ikke finde ud af at finde ud af at profilere efter hukommelsesforbrug.
Det kan være at I seje geeks ved mere :-)

Hvis jeg f.eks. har en stump Python-kode som denne (test.py), allokerer mere og mere hukommelse hvert sekund.
Det kræver nok ikke mange sekunder for at fatte at a-variablen går amok og de andre variable er småting.

Jeg kan ret nemt følge hukommelsesforløbet på en Linux-maskine med Valgrind:

Det giver en graf, som hvert sekund kravler opad som forventet.

Hver gang Valgrinds Massif laver en lodret “@” kommer der i udskriften fra ms_print yderligere en smøre om hvor hukommelsen blev brugt.
Havde det været et C/C++ program oversat med “gcc/g++ -g” så ville den kalde-hierarkiet vise funktionskald, kode-filnavne og linie numre, men
jeg kan ikke få dette frem når det er Python

 55  5,680,784,924    1,360,907,216    1,360,894,241        12,975            0
100.00% (1,360,894,241B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->99.93% (1,360,001,160B) 0x508B85: PyList_New (in /usr/bin/python2.7)
| ->99.93% (1,360,000,000B) 0x509034: ??? (in /usr/bin/python2.7)
| | ->99.93% (1,360,000,000B) 0x52D671: PyEval_EvalFrameEx (in /usr/bin/python2.7)
| |   ->99.93% (1,360,000,000B) 0x52CF30: PyEval_EvalFrameEx (in /usr/bin/python2.7)
| |   | ->99.93% (1,360,000,000B) 0x55C592: PyEval_EvalCodeEx (in /usr/bin/python2.7)
| |   |   ->99.93% (1,360,000,000B) 0x5B7390: PyEval_EvalCode (in /usr/bin/python2.7)
| |   |     ->99.93% (1,360,000,000B) 0x469661: ??? (in /usr/bin/python2.7)
| |   |       ->99.93% (1,360,000,000B) 0x4699E1: PyRun_FileExFlags (in /usr/bin/python2.7)
| |   |         ->99.93% (1,360,000,000B) 0x469F1A: PyRun_SimpleFileExFlags (in /usr/bin/python2.7)
| |   |           ->99.93% (1,360,000,000B) 0x46AB7F: Py_Main (in /usr/bin/python2.7)
| |   |             ->99.93% (1,360,000,000B) 0x506DEC3: (below main) (libc-start.c:287)

Grrrrrr – dummy interface :) Valgrind kan umiddelbart ikke fange de rette symboler. Der kommer ingen referencer til min kode.

Ny taktik! Jeg oversætter Python-koden til C via Cython (I eksemplet er det Python 2.7 og C – det virker også fint med Python 3.x og C++ kode)

Nu er der noget relevant f.eks. for det sidste snapshot, hvor der er godt 1,5 GB allokeret

--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
 54 11,014,947,111    1,530,916,888    1,530,907,294         9,594            0
100.00% (1,530,907,294B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->99.94% (1,530,000,096B) 0x4E81306: ??? (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
| ->99.94% (1,530,000,096B) 0x4EAE6B1: PyNumber_InPlaceMultiply (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
|   ->99.94% (1,530,000,096B) 0x40237C: __pyx_pf_4test_fun (test.c:885)
|     ->99.94% (1,530,000,096B) 0x401E4A: __pyx_pw_4test_1fun (test.c:772)
|       ->99.94% (1,530,000,096B) 0x4034D8: __Pyx_PyObject_Call (test.c:1243)
|         ->99.94% (1,530,000,096B) 0x40317A: inittest (test.c:1170)
|           ->99.94% (1,530,000,096B) 0x404D5D: main (test.c:1887)

Som det ses er bla. test.c linie 885 vist som et sted hvor der allokeres 1,5 GB. Og det er første sted i kalde-hierarkiet af min kode.
Og kigger jeg blot et par linier over linie 885 står der – ta da… En reference til netop den linie 6, hvor jeg lavede min gigantiske
hukommelsesallokering – det er a-variablen som bliver STOR!

Naturligvis er kodeeksemplet latterligt småt, men teknikken er meget relevant, når man har 10000 linier Python-kode :-)

Pt. er dette den eneste vej jeg har fundet igennem til at detektere hvor jeg har meget Python-hukommelse allokeret.
Har du en smartere og mere direkte måde, så skriv meget gerne nedenfor om dette.
Jeg har også prøvet “heapy”, men den kan umiddelbart ikke lokalisere de store hukommelsessyndere (ofte de interessante).
Alternativet “memory_profiler” er umiddelbart håbløst, fordi man skal i forvejen have indsat kode til dette overalt, hvor man forventer noget interessant.
En god intro er: http://stackoverflow.com/questions/110259/which-python-memory-profiler-i…

/pto

Posted in computer.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>