Guppy-PE로 Python Memory Leak 잡기

Development 2008/11/11 18:33
파이썬에서 메모리 누수를 찾아낼 수 있는 좋은 툴이 없을까 헤매다가 포기한 적이 여러 번 있었다.
(당시에 print 신공으로 디버깅했던 아픈 기억이.. ㅠㅠ)

그러다가 얼마 전에, 파이썬으로 작성한 서버 프로그램이 메모리를 무지막지하게 잡아먹는 문제가 발생해서 또 툴을 찾아 헤매이고 있었는데..  다행히 팀장 횽아가 좋은 걸 하나 건져서 알려줬다.

이름하여 Guppy-PE (A Python Programming Environment) 란다.

사실 나도 헤매다가 이걸 발견하긴 했었는데, 너무 허잡한 documentation 때문에 '뭐야 이거? 어쩌라는겨?' (흥분해서 사투리 나온다. -_-) 하면서 버렸던 것 같다.  팀장 횽아가 알려줬을 때에도, 다시 살펴보고 '뭐야 이거? 어쩌라고?' 한 번 더.. -_-;

팀장 횽아가 힘들게 알아낸 사용법을 살짝 알려줘서 테스트에 성공한 후엔,
'우와~ 이거 물건이네!'로 평가가 살짝 바뀌었다. ㅎㅎ

암튼 사용하는 방법을 간략히 소개하면..
(패키지 다운로드해서 설치하는 것은 생략함)

메모리 누수가 발생하는 python source code 상단에 다음 import문을 삽입하여 실행한다.
from guppy.heapy import RM

이렇게 하면, 다른 터미널에서 아래와 같은 명령으로 위 python 프로그램의 상태를 확인할 수 있다.
python -c "from guppy import hpy; hpy().monitor()"
위 명령을 실행하면 Monitor 상태로 들어가는데, 'h'를 입력하면 help 화면이 나온다.
help를 적당히 하면서 이것저것 해 보면 대충 감을 잡을 수 있다.
주의할 것은 그냥 Enter 키를 누르면, 바로 전에 실행한 명령이 다시 실행되니까 조심하자.

여기까지 내용만 미리 알아도 몇 시간의 삽질을 줄일 수 있다.

그럼 이제 간단히 실례를 통해 확인해 보자.
(터미널 두 개를 사용해서 테스트를 진행하는데, 터미널 구분을 배경색으로 구분한다.)

$ python
Python 2.4.3 (#6, Jun 27 2007, 20:02:37)
[GCC 3.4.6 20060404 (Red Hat 3.4.6-8)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from guppy.heapy import RM
>>> a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> b = 'B:'+a*1000
>>> c = 'C:'+b*1000
>>> d = 'D:'+c*10
위와 같이 하면, 260 MByte 가까이 소모가 된다.
그럼 다른 터미널에서 메모리 상황을 확인해 보자.

$ python -c "from guppy import hpy; hpy().monitor()"
<Monitor>
*** Connection 1 opened ***
<Monitor> h 
Documented commands (type help <topic>):
========================================
exit  h  help  int  ki  lc  q  sc
<Monitor> lc
CID PID   ARGV
  1 29082 ['']
<Monitor> sc 1
Remote connection 1. To return to Monitor, type <Ctrl-C> or .<RETURN>
<Annex> h
Documented commands (type help <topic>):
========================================
close  h  help  int  isolatest  q  reset  stat
<Annex> int
Remote interactive console. To return to Annex, type '-'.
>>>
여기까지 하면 모니터링을 하는 파이썬 콘솔모드로 들어 간다.
그럼 실제 모습을 확인해 보자.

>>> hp.heap()
Partition of a set of 11404 objects. Total size = 287543688 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0   5587  49 286532048 100 286532048 100 str
     1     62   1   206560   0 286738608 100 dict of module
     2   2145  19   191384   0 286929992 100 tuple
     3     78   1   124896   0 287054888 100 dict of type
     4     37   0    75344   0 287130232 100 dict (no owner)
     5     79   1    66656   0 287196888 100 type
     6    552   5    66240   0 287263128 100 function
     7    571   5    63952   0 287327080 100 types.CodeType
     8    709   6    56720   0 287383800 100 __builtin__.wrapper_descriptor
     9     96   1    49920   0 287433720 100 dict of class
<44 more rows. Type e.g. '_.more' to view.>
메모리 사용상태를 보니, 문자열(str)이 100%를 차지하고 있다.
이 내용을 object 단위로 출력하자.

>>> hp.heap()[0].byid
Set of 5587 <str> objects. Total size = 286532048 bytes.
 Index     Size   %   Cumulative  %   Representation (limited)
     0 260020064  90.7 260020064  90.7 'D:C:B:ABCDEF...NOPQRSTUVWXYZ'
     1 26002048   9.1 286022112  99.8 'C:B:ABCDEFGH...NOPQRSTUVWXYZ'
     2    26048   0.0 286048160  99.8 'B:ABCDEFGHIJ...NOPQRSTUVWXYZ'
     3     7480   0.0 286055640  99.8 'The class Bi... copy of S.\n'
     4     4384   0.0 286060024  99.8 "Support for ... 'error'.\n\n"
     5     3832   0.0 286063856  99.8 'The type of ...call order.\n'
     6     3432   0.0 286067288  99.8 'This module ...ng function\n'
     7     3112   0.0 286070400  99.8 't\x00\x00|\x...\x01W|\r\x00S'
     8     2992   0.0 286073392  99.8 'HeapView(roo... size, etc.\n'
     9     2640   0.0 286076032  99.8 'The class No... otherwise.\n'
<5577 more rows. Type e.g. '_.more' to view.>
90% 이상의 메모리를 차지하는 것이 'D:C:B:ABC...' 문자열인 것을 알 수 있다.
이번에는 변수명(dictionary key값)별로 확인해 보자.

>>> hp.heap()[0].byvia
Partition of a set of 5587 objects. Total size = 286532048 bytes.
 Index  Count   %     Size   % Cumulative  % Referred Via:
     0      1   0 260020064  91 260020064  91 "['d']"
     1      1   0 26002048   9 286022112 100 "['c']"
     2    571  10    82568   0 286104680 100 '.co_code'
     3    156   3    78864   0 286183544 100 "['__doc__']"
     4    211   4    58040   0 286241584 100 '.func_doc', '[0]'
     5    554  10    33040   0 286274624 100 '.co_lnotab'
     6      1   0    26048   0 286300672 100 "['b']"
     7    101   2     9664   0 286310336 100 '[1]'
     8     75   1     4680   0 286315016 100 '[2]'
     9     51   1     4368   0 286319384 100 "['__file__']"
<2457 more rows. Type e.g. '_.more' to view.>
변수 d가 91% 정도를 차지하는 것을 볼 수 있다.
메모리를 잡아먹고 있는 괘씸한 변수 d에 빈 문자열을 대입해 보자.

>>> d = ''
그리고 나서, 다시 모니터링 화면을 보자.
>>> hp.heap()[0].byid
Set of 5586 <str> objects. Total size = 26511984 bytes.
 Index     Size   %   Cumulative  %   Representation (limited)
     0 26002048  98.1  26002048  98.1 'C:B:ABCDEFGH...NOPQRSTUVWXYZ'
     1    26048   0.1  26028096  98.2 'B:ABCDEFGHIJ...NOPQRSTUVWXYZ'
     2     7480   0.0  26035576  98.2 'The class Bi... copy of S.\n'
     3     4384   0.0  26039960  98.2 "Support for ... 'error'.\n\n"
     4     3832   0.0  26043792  98.2 'The type of ...call order.\n'
     5     3432   0.0  26047224  98.2 'This module ...ng function\n'
     6     3112   0.0  26050336  98.3 't\x00\x00|\x...\x01W|\r\x00S'
     7     2992   0.0  26053328  98.3 'HeapView(roo... size, etc.\n'
     8     2640   0.0  26055968  98.3 'The class No... otherwise.\n'
     9     2536   0.0  26058504  98.3 "Python's sta...FutureWarning"
<5576 more rows. Type e.g. '_.more' to view.>
>>> hp.heap()[0].byvia
Partition of a set of 5586 objects. Total size = 26511984 bytes.
 Index  Count   %     Size   % Cumulative  % Referred Via:
     0      1   0 26002048  98  26002048  98 "['c']"
     1    571  10    82568   0  26084616  98 '.co_code'
     2    156   3    78864   0  26163480  99 "['__doc__']"
     3    211   4    58040   0  26221520  99 '.func_doc', '[0]'
     4    554  10    33040   0  26254560  99 '.co_lnotab'
     5      1   0    26048   0  26280608  99 "['b']"
     6    101   2     9664   0  26290272  99 '[1]'
     7     75   1     4680   0  26294952  99 '[2]'
     8     51   1     4368   0  26299320  99 "['__file__']"
     9     16   0     3416   0  26302736  99 '.func_doc'
<2456 more rows. Type e.g. '_.more' to view.>
d가 차지하고 있던 메모리가 사라지고, 이제 c가 98% 메모리를 차지하고 있는 것을 확인할 수 있다.


빨래 끝~ +_+;
tags : ,
Trackback 0 : Comment 0
◀ PREV : [1] : [2] : [3] : [4] : [5] : ... [42] : NEXT ▶