Guppy-PE로 Python Memory Leak 잡기
Development 2008/11/11 18:33(당시에 print 신공으로 디버깅했던 아픈 기억이.. ㅠㅠ)
그러다가 얼마 전에, 파이썬으로 작성한 서버 프로그램이 메모리를 무지막지하게 잡아먹는 문제가 발생해서 또 툴을 찾아 헤매이고 있었는데.. 다행히 팀장 횽아가 좋은 걸 하나 건져서 알려줬다.
이름하여 Guppy-PE (A Python Programming Environment) 란다.
사실 나도 헤매다가 이걸 발견하긴 했었는데, 너무 허잡한 documentation 때문에 '뭐야 이거? 어쩌라는겨?' (흥분해서 사투리 나온다. -_-) 하면서 버렸던 것 같다. 팀장 횽아가 알려줬을 때에도, 다시 살펴보고 '뭐야 이거? 어쩌라고?' 한 번 더.. -_-;
팀장 횽아가 힘들게 알아낸 사용법을 살짝 알려줘서 테스트에 성공한 후엔,
'우와~ 이거 물건이네!'로 평가가 살짝 바뀌었다. ㅎㅎ
암튼 사용하는 방법을 간략히 소개하면..
(패키지 다운로드해서 설치하는 것은 생략함)
메모리 누수가 발생하는 python source code 상단에 다음 import문을 삽입하여 실행한다.
이렇게 하면, 다른 터미널에서 아래와 같은 명령으로 위 python 프로그램의 상태를 확인할 수 있다.
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% 메모리를 차지하고 있는 것을 확인할 수 있다.
빨래 끝~ +_+;
