[Python] gc.collect() 후에도 메모리 남아있는 문제 Develop Tip

우선 지난번에 guppy를 이용한 메모리 소비량 살펴보기를 올린 적 있습니다.

이것이 단순 소비 메모리만을 리눅스에서 살펴볼 때에는
guppy를 이용하는 것이 너무 프로그램을 지연시키는 문제가 말생하므로

/proc/[pid]/status
내용을 확인하여 VmRSS 내용을 확인하는 것이 더 빠르고 
나을 수 있었습니다.

예를 들어,

/proc$ cat 1/status 
Name: init
State: S (sleeping)
Tgid: 1
Ngid: 0
Pid: 1
PPid: 0
TracerPid: 0
Uid: 0 0 0 0
Gid: 0 0 0 0
FDSize: 64
Groups:
VmPeak:   33916 kB
VmSize:   33884 kB
VmLck:       0 kB
VmPin:       0 kB
VmHWM:    3156 kB
VmRSS:    3140 kB
VmData:    1656 kB
VmStk:     136 kB
VmExe:     248 kB
VmLib:    3076 kB
VmPTE:      88 kB
VmSwap:      16 kB
Threads: 1
SigQ: 0/15738
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000001000
SigCgt: 00000001a0016623
CapInh: 0000000000000000
CapPrm: 0000001fffffffff
CapEff: 0000001fffffffff
CapBnd: 0000001fffffffff
Seccomp: 0
Cpus_allowed: ffffffff,ffffffff,ffffffff,ffffffff
Cpus_allowed_list: 0-127
Mems_allowed: 00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 1804
nonvoluntary_ctxt_switches: 17042

와 같이 나오는데 여기서 VmRSS는 
Swap 되지 않고 유지되는 현재 프로세스가 사용하는 메모리를 나타내는 것이라 하더군요.

따라서 지난번 HeapMon 클래스를
이것에 맞게 수정한 것이 있습니다.


이제 본론으로 돌아와서 다음 프로그램을 살펴보겠습니다.

##########################################################################################
def main(hm):
print hm.check('Before allocating')
# print 'Before allocating ', rss(),

iterations = 1000000
l = {}
for i in xrange(iterations):
l[i] = ({})

# print 'After allocating  ', rss(),
print hm.check('After allocating')

# Ignore optimizations, just try to free whatever possible

# First kill
for i in xrange(iterations):
l[i] = None
print hm.check('After First kill')

# Second kill
l.clear()
print hm.check('After Second kill')

# Third kill
l = None
del l
print hm.check('After Third kill')

# Control shot
gc.collect()

# print 'After free ', rss(),
print hm.check('After free')


##########################################################################################
if __name__ == '__main__':
hm = HeapMon() # 여기에 글로벌로 hm 이라는 메모리 힙 모니터링을 위한 인스턴스를 만들어 놓았습니다.
for i in range(1):
print
main(hm)

일단 위의 프로그램을 돌리면,

Before allocating: Total 5.04 MB, 5.04 MB incresed
After allocating: Total 355.43 MB, 350.39 MB incresed
After First kill: Total 76.80 MB, 278.63 MB decresed
After Second kill: Total 28.80 MB, 48.00 MB decresed
After Third kill: Total 28.80 MB, not changed
After free: Total 28.80 MB, not changed

와 같은 결과가 나옵니다.

각 내용을 설명해 보면,

Before allocating: Total 5.04 MB, 5.04 MB incresed
>> 파이썬 인터프리터가 함수에 들어올 때의 메모리 사용양을 보여줍니다. 5MB 정도를 디폴트로 가지고 뜨는군요
After allocating: Total 355.43 MB, 350.39 MB incresed
>> 파이썬 dict 안에 100만개의 빈 {}를 넣었더니 350MB 정도가 채워 지네요...
After First kill: Total 76.80 MB, 278.63 MB decresed
>> 이제는 100만번을 돌면서 dic 하나 하나를 None으로 지운 것입니다. (약 72 MB 가 남아 있군요...)
After Second kill: Total 28.80 MB, 48.00 MB decresed
>> 파이썬 메뉴얼에 있는데로 clear() 메서드를 이용하여 지웠는데... 48MB 가 지워졌습니다.
After Third kill: Total 28.80 MB, not changed
>> dict 자체를 None으로 주거나, del 시켰는데도 메모리 변환가 없습니다. 
(위에 남은 72MB - 48MB = 24MB 가 아직 남아 있습니다)
After free: Total 28.80 MB, not changed
>> 혹시나... 싶어 gc.collecti() 를 해도 마찬가지 입니다.

위에 함수를 벗어나면 괜찮을까 싶어 
main 함수를 3번 호출해 보았는데,

Before allocating: Total 5.04 MB, 5.04 MB incresed
After allocating: Total 355.43 MB, 350.39 MB incresed
After First kill: Total 76.80 MB, 278.63 MB decresed
After Second kill: Total 28.80 MB, 48.00 MB decresed
After Third kill: Total 28.80 MB, not changed
After free: Total 28.80 MB, not changed

Before allocating: Total 28.80 MB, not changed
After allocating: Total 379.48 MB, 350.68 MB incresed
After First kill: Total 100.80 MB, 278.68 MB decresed
After Second kill: Total 52.79 MB, 48.00 MB decresed
After Third kill: Total 52.79 MB, not changed
After free: Total 52.79 MB, not changed

Before allocating: Total 52.79 MB, not changed
After allocating: Total 379.46 MB, 326.66 MB incresed
After First kill: Total 100.80 MB, 278.66 MB decresed
After Second kill: Total 52.79 MB, 48.00 MB decresed
After Third kill: Total 52.79 MB, not changed
After free: Total 52.79 MB, not changed

위와 같이 호출할 수록 더욱 더 메모리 낭비가 생겨버립니다.

몇번 이렇게 돌다가 프로세스가 모두 끝났다면 돌려줄까,
데몬처럼 서비스로 실행되는 것이라면 이제 어쩌지요?? 하게됩니다.


이렇게 dict를 이용해서 작업하다가 다 끝났는데도 메모리가 남아 있다면...
이것은, glibc의 free에서 fragmentation 문제로 trim 이 제대로 안된 문제가 남아 있다고 하더군요...
(헐! glibc 문제를 어떻게 해결해요?)
이런 문제는 비단 파이썬의 문제가 아니라 C로 malloc, free 를 대량으로 할 때 발생하는 문제군요...

이런 경우를 위해 특별히 나온 라이브러리가 있었으니
jemalloc 이라는 것입니다.

우선 우분투에서 설치 방법은 

$ sudo apt-get install libjemalloc1
이라고 설치합니다.

그 다음에 "LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1" 를 명령행 앞에 지정하고 실행시키면,

$ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 python memprofile2.py 

Before allocating: Total 10.20 MB, 10.20 MB incresed
After allocating: Total 361.05 MB, 350.86 MB incresed
After First kill: Total 82.39 MB, 278.66 MB decresed
After Second kill: Total 34.39 MB, 48.00 MB decresed
After Third kill: Total 34.39 MB, not changed
After free: Total 9.92 MB, 24.47 MB decresed

Before allocating: Total 9.92 MB, 4.00 KB incresed
After allocating: Total 359.24 MB, 349.32 MB incresed
After First kill: Total 80.68 MB, 278.56 MB decresed
After Second kill: Total 32.68 MB, 48.00 MB decresed
After Third kill: Total 32.68 MB, not changed
After free: Total 8.80 MB, 23.89 MB decresed

Before allocating: Total 8.80 MB, 4.00 KB incresed
After allocating: Total 359.30 MB, 350.50 MB incresed
After First kill: Total 80.68 MB, 278.62 MB decresed
After Second kill: Total 32.68 MB, 48.00 MB decresed
After Third kill: Total 32.68 MB, not changed
After free: Total 8.88 MB, 23.79 MB decresed

위의 결과를 확인하면,

gc.collect() 이후에 나머지 덜 해지되었던 메모리까지
해지 됨을 알 수 있습니다.

결국 파이썬 문제가 아닌 glibc 문제였는데,
엄한 파이썬이 의심을 받지 않았나 싶네요...

테스트하고 결과를 만들어 해결해 보았습니다.

감사드립니다.


어느분께는 또 도움이 되셨기를 바랍니다...


핑백

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

    ... 때보다 60여배 느려지는 상황이 발생합니다.(주어진 메모리 내에서 캐슁을 하는 등의 기능을 넣어볼 필요도 있겠습니다.) 지난번 살펴본 gc.collect() 후에도 메모리 남아있는 문제 와 더불어메모리에 관하여 살펴보면 볼 수록 흥미롭습니다. 어느 분께는 도움이 되셨기를... ... more

덧글

  • goddoe 2017/03/27 17:46 # 삭제 답글

    감사합니다! 회사솔루션을 파이썬으로 만들고있는데 메모리 누수 문제를 말씀해주신 방법으로 해결했습니다! 너무 감사합니다!
  • 지훈현서아빠 2017/03/27 18:25 #

    도움이 되셨다니 저의 보람입니다~ ^^
  • sesong 2017/09/11 17:24 # 삭제 답글

    윈도우 아나콘다 환경에서는 어떻게 해야 할까요?ㅠㅠ
댓글 입력 영역

구글애드텍스트