티스토리 뷰

프로그래밍을 하다 보면 필연적으로 메모리 사용량을 디버깅해야 하는 일이 생깁니다. 메모리 누수가 발생해서 디버깅을 해야 하는 경우도 있고 메모리 사용량이 비정상인 경우도 디버깅이 필요해요. 일부 언어는 힙 메모리 공간을 통째로 덤프 떠서 확인하는 일도 비일비재하죠.

이번 글에서는 파이썬 프로그램을 개발하면서 메모리 사용량을 추적하는 방법을 알아봅니다. 거창하게 시작했지만 파이썬에서 메모리 추적은 psutil 이 대표적입니다. 일단 사용 방법부터 바로 알아보고 조금 더 상세한 이야기를 해봅니다. 사용법은 심플합니다. 다음 공식 예제를 살펴보시죠.

>>> import psutil
>>> p = psutil.Process()
>>> p.memory_info()
pmem(rss=15491072, vms=84025344, shared=5206016, text=2555904, lib=0, data=9891840, dirty=0)

여기서 물리 메모리에 해당하는 RSS( resident set size )를 살펴보면 좋습니다. 단위는 bytes입니다. RSS는 프로그램의 현재 상태를 나타내기 때문에 아래와 같이 함수로 만들어두면 주기적인 호출을 통해 메모리의 변화를 살펴볼 수 있습니다.

def memory_usage(message: str = 'debug'):
    # current process RAM usage
    p = psutil.Process()
    rss = p.memory_info().rss / 2 ** 20 # Bytes to MB
    print(f"[{message}] memory usage: {rss: 10.5f} MB")

아래처럼 동작시킬 수 있겠네요.

memory_usage('#1')

# TODO
work_1()
memory_usage('#2')
work_2()

memory_usage('#3')

 

psutil의 원리는 간단합니다. 리눅스에서 프로세스의 정보를 확인할 때 주로 procfilesystem을 사용하는데요, 경로는 /proc/{PID}/status입니다.

$ cat /proc/3778/status
Name:	dhclient
Umask:	0022
State:	S (sleeping)
Tgid:	3778
Ngid:	0
Pid:	3778
PPid:	1
TracerPid:	0
Uid:	0	0	0	0
Gid:	0	0	0	0
FDSize:	64
Groups:
NStgid:	3778
NSpid:	3778
NSpgid:	3778
NSsid:	3778
VmPeak:	  100664 kB
VmSize:	  100664 kB
VmLck:	       0 kB
VmPin:	       0 kB
VmHWM:	    4424 kB
VmRSS:	    4292 kB
RssAnon:	    2080 kB
RssFile:	    2212 kB
RssShmem:	       0 kB
VmData:	    1948 kB
VmStk:	     132 kB
VmExe:	     384 kB
VmLib:	   13912 kB
VmPTE:	     192 kB
VmPMD:	      12 kB
VmSwap:	       0 kB
HugetlbPages:	       0 kB
Threads:	1
SigQ:	0/30446
SigPnd:	0000000000000000
ShdPnd:	0000000000000000
SigBlk:	0000000000000000
SigIgn:	0000000000001000
SigCgt:	0000000180000000
CapInh:	0000000000000000
CapPrm:	0000000000203402
CapEff:	0000000000203402
CapBnd:	0000003fffffffff
CapAmb:	0000000000000000
NoNewPrivs:	0
Seccomp:	0
Speculation_Store_Bypass:	vulnerable
Cpus_allowed:	7fff
Cpus_allowed_list:	0-14
Mems_allowed:	00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list:	0
voluntary_ctxt_switches:	3488
nonvoluntary_ctxt_switches:	977

물리 메모리 사용량은 VmRSS를 보시면 됩니다. 위에서는 4282KB네요. 그리고 커널에서 해당 proc을 처리할 때와 같은 변수로 처리되는 /proc/{PID}/statm 을 살펴보세요. 여기서 두 번째 필드, 이게 바로 psutil에서 사용되는 값입니다. 실제로 살펴보면 약간 차이가 있는데요.

리눅스에서 메모리를 처리하는 페이지 단위가 반영되지 않은 값이라 그렇습니다. 일반적인 시스템에서 잡히는 페이지 크기인 4096을 곱해주면 바이트 단위로 확인됩니다. 여기에 다시 1024를 곱해서 KB로 변환해주면 위에서 확인한 4282와 같아지죠. (1024는 KiB 단위지만 오랜 관습에 따라 아직 맞춰지지 않은 부분입니다)

예전에는 아래처럼 셸 스크립트로 구현해서 사용하기도 했는데 프로그램 내에서 처리하는 게 아무래도 훨씬 보기 좋겠죠. 코드의 실행 위치가 같이 확인되니까요.

while true; do cat /proc/{PID}/status | grep rss;sleep 1;done

 

# 마무리

가벼운 내용이지만 원리를 알고 사용하면 조금 더 유연한 프로그래밍을 할 수 있게 될 겁니다. 메모리 사용량으로부터 고통받는 누군가에게는 도움이 되겠죠 :)

댓글
댓글쓰기 폼