[python] 메모리 소비 크기 구하기 Develop Tip

C, C++로 개발을 하다가 Java, C#, 파이썬 등을 사용하다 보면
그 지긋지긋한 memory segfault 오류가 발생하지 않아 
마치 메모리 지옥을 빠져나온 듯한 느낌을 받을 수 있습니다.

그러나 그 댓가도 만만치 않습니다.
바로 메모리 Heap을 과도하게 사용함으로써 
메모리 부족 등이 발생할 수 있기 때문이지요.

뭐 요즘처럼 8G, 16G, 32G 등 메모리 걱정을 안하면
별 상관은 없을 수 있지만 아직도 임베디드 시스템 등에서는
그런 메모리 관리를 잘 해야 할 필요가 있습니다.

프로그래밍 언어론을 공부하다보면 C 언어의 함수 안에서
static 하게 선언해 놓은 정보는 코드의 stack 메모리에 잡히고
malloc 한 포인터는 heap 메모리에 잡히는 것을 알 수 있습니다.

아마도 파이썬은 list 나 dict 등이 모두 heap 에 잡히는 것 같은데
(동적으로 메모리 할당을 하여 그 포인터를 갖고 있는 형식으로 CPython에 
 구현되어 있으리라 미루어 짐작해 봅니다) 구현되어 있을 것 같습니다.

암튼 메모리 프로파일링이 필요한데 그 방법입니다.

우선 해당 모듈은 guppy 라는 모듈이고 설치를 하면
native C 모듈을 wrapping 해 놓은 것을 알 수 있습니다.

다음과 같이 pip를 이용하여 간단하게 설치합니다.

sudo apt-get install python-dev
sudo pip install guppy

그리고 아래와 같이 아주 간단하게 현재 프로그램의 Memory 상태를 구할 수 있습니다.

from guppy import hpy
h = hpy()
print h.heap()

위와 같은 파이썬 프로그램을 수행시키면,

Partition of a set of 25931 objects. Total size = 3325808 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0  11817  46   936776  28    936776  28 str
     1   5837  23   470320  14   1407096  42 tuple
     2    324   1   277728   8   1684824  51 dict (no owner)
     3     69   0   213624   6   1898448  57 dict of module
     4    199   1   210856   6   2109304  63 dict of type
     5   1632   6   208896   6   2318200  70 types.CodeType
     6   1597   6   191640   6   2509840  75 function
     7    199   1   177008   5   2686848  81 type
     8    124   0   135328   4   2822176  85 dict of class
     9   1045   4    83600   3   2905776  87 __builtin__.wrapper_descriptor
<90 more rows. Type e.g. '_.more' to view.>

와 같은 결과를 보입니다.

다만, 일반 프로그램 프로파일러 처럼 어느 사용자 함수에서 
얼마큼 많이 수행되고 시간이 걸렸다는 것이 아니라
어느 class 또는 class의 dict, function 등의 type이 많이 메모리를 잡고 있고
가를 보여줍니다.

어떻게 현재 메모리의 가감을 잘 나타내 줄까 잠깐의 고민을 해 보니
결국 위의 프로파일링에서 메모리의 Total size를 가지고 가감을
계산하는 것이 제일 나은 방법 같아 그 것을 처리하는 것을 만들어 보았습니다.


클래스는 다음과 같은 모습입니다.

##########################################################################################
class HeapMon:
#=====================================================================================
def __init__(self):
try:
from guppy import hpy
self.enabled = True
except:
self.enabled = False
if self.enabled:
self._h = hpy()
self.hsize = 0L
self.hdiff = 0L
#=====================================================================================
@staticmethod
def getReadableSize(lv):
if not isinstance(lv, (int, long)):
return '0'
if lv >= 1024*1024*1024*1024:
s = "%4.2f TB" % (float(lv)/(1024*1024*1024*1024))
elif lv >= 1024*1024*1024:
s = "%4.2f GB" % (float(lv)/(1024*1024*1024))
elif lv >= 1024*1024:
s = "%4.2f MB" % (float(lv)/(1024*1024))
elif lv >= 1024:
s = "%4.2f KB" % (float(lv)/1024)
else:
s = "%d B" % lv
return s
#=====================================================================================
def __repr__(self):
if not self.enabled:
return 'Not enabled. guppy module not found!'
if self.hdiff > 0:
s = 'Total %s, %s incresed' % \
   (self.getReadableSize(self.hsize), self.getReadableSize(self.hdiff))
elif self.hdiff < 0:
s = 'Total %s, %s decresed' % \
   (self.getReadableSize(self.hsize), self.getReadableSize(-self.hdiff))
else:
s = 'Total %s, not changed' % self.getReadableSize(self.hsize)
return s
#=====================================================================================
def getHeap(self):
if not self.enabled:
return None
return str(self._h.heap())
#=====================================================================================
def check(self, msg=''):
if not self.enabled:
return 'Not enabled. guppy module not found!'
hdr = self.getHeap().split('\n')[0]
chsize = long(hdr.split()[-2])
self.hdiff = chsize - self.hsize
self.hsize = chsize
return '%s: %s'% (msg, str(self))


이제 이것을 간단히 사용하는 법을 살펴보겠습니다.

##########################################################################################
hm = HeapMon() # 여기에 글로벌로 hm 이라는 메모리 힙 모니터링을 위한 인스턴스를 만들어 놓았습니다.

##########################################################################################
def do_main():
print hm.check('start do_main') # 함수를 들어와서 메모리 가감을 찍어봅니다
biglist = []
for i in xrange(1000000):
biglist.append(i) # 백만개의 정수를 담고있는 list를 생성해봅니다
print hm.check('end do_main') # 함수를 종료하기 전에 메모리 가감을 찍어봅니다

##########################################################################################
if __name__ == '__main__':
print hm.check('before do_main') # 메인에서 do_main() 함수 호출전에 최초 메모리 가감을 찍어봅니다
do_main()
print hm.check('after do_main') # do_main() 함수 호출 후에 메모리 가감을 찍어봅니다 

위와 같이 돌려보면 그 결과는

before do_main: Total 6.86 MB, 6.86 MB incresed
start do_main: Total 6.86 MB, 2.80 KB incresed
end do_main: Total 37.50 MB, 30.63 MB incresed
after do_main: Total 6.86 MB, 30.64 MB decresed

이구요, 설명을 각각 달아보면...

before do_main: Total 6.86 MB, 6.86 MB incresed 
 => 처음 프로그램을 시작하자마자 6.86MB 를 잡고 시작하는 군요
start do_main: Total 6.86 MB, 2.80 KB incresed 
 => 함수 호출만 하고 들어가는데 2.80KB 메모리가 증가합니다. 
     C 같으면 stack에 return 주소만 넣을 텐데, 역시 C 보다는 소비가 심하네요...
end do_main: Total 37.50 MB, 30.63 MB incresed 
=> 백만개의 int 형을 추가한 목록을 만들어 놓았더니 30.63MB가 증가했습니다. 
    C의 long (8byte long long 이라 하더라도) 이라면 단순 8M 정도만 소비할텐데
    아무래도 파이썬은 모든 객체 앞에 Object 포인터가 달려 있어서 그런가
    생각보다 메모리 소비가 좀 되네요...
    결국은 프로파일링을 하면서 이렇게 갑자기 많이 증가한 곳을 줄일 수록
    run-time 메모리 소비를 줄일 수 있을 것입니다.
    dict 대신 leveldb 등을 사용할 수 있지요.
after do_main: Total 6.86 MB, 30.64 MB decresed
=> 이전 잡아놓았던 백만개의 목록과 함수 호출을 모두 종료하고 처음 메모리인 
    6.86MB로 되돌아왔습니다.


큰 클래스로 작업하다보면 PyObject의 Reference Counter 등에 의해서
메모리를 바로 바로 해지하지 않고 계속 들고 있을 수도 있는데

import gc
...
gc.collect()

와 같이 강제적으로 해지시키는 것도 방법인데
그 overhead는 얼마나 될지까지는 살펴보지 못했습니다.


어느분께는 도움이 되셨기를....


핑백

  • 지훈현서 : [Python] gc.collect() 후에도 메모리 남아있는 문제 2015-07-10 16:09:09 #

    ... 우선 지난번에 guppy를 이용한 메모리 소비량 살펴보기를 올린 적 있습니다. 이것이 단순 소비 메모리만을 리눅스에서 살펴볼 때에는guppy를 이용하는 것이 너무 프로그램을 지연시키는 문제가 말생하므 ... more

  • 지훈현서 : [Python] 메모리 사용 및 persistent dict, list 2016-06-14 19:55:01 #

    ... 하면 됩니다.(처음에는 킬로바이트라고 읽은 것 같은데 나중에 보니 바이트 단위더군요) 2) 현재 프로그램이 사용하고 있는 메모리를 모니터링 합니다. 지난번 올렸던 메모리소비 구하기 와 비슷하지만 다른 방법으로 psutil 모듈을 이용해 보았습니다. def getReadableSize(lv): if not isinstance(lv, ( ... more

덧글

  • 지나다가 2016/03/10 19:14 # 삭제 답글

    좋은 정보 감사합니다.^^
  • 지훈현서아빠 2016/03/11 09:42 #

    도움되셨다니 저의 보람입니다~ ^^
  • 파이썬입문자 2018/02/25 22:31 # 삭제 답글

    좋은 코드와, 좋은 소개입니다. :D 감사합니다 !!
  • 지훈현서아빠 2018/02/26 13:03 #

    도움이 되셨다니 저의 보람입니다~ ^^
  • 학생 2019/05/17 11:07 # 삭제 답글

    안녕하세요 파이썬 gdb로 segfault 추적하는 글에도 (방금)댓글을 달았는데 또 이곳으로 흘러오게 됐네요.. 저는 의료데이터라 꽤 큰 파일들을 다루다보니 자꾸 segfault오류가 났고 그걸 해결중에 있습니다ㅠㅠ RAM은 64G로 충분한데 파이썬의 접근 가능한 메모리영역을 늘리려면 어떤 방법이 있는지 혹시 아실까요?
댓글 입력 영역

구글애드텍스트