티스토리 뷰

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

이번 글에서는 파이썬 프로그램을 개발하면서 메모리 사용량을 추적하는 방법을 알아봅니다. 거창하게 시작했지만 파이썬에서 메모리 추적은 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

 

# 마무리

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

댓글
  • 프로필사진 ㅎㅎ 안녕하세요, 파이썬으로 여러가지 프로그램을 돌리다가 멀티 프로세스 개념으로 bash shell을 통해 다른 프로그램을 동작하게 되었습니다. 마침내 script랑 ps -f, awk, grep 등을 통해 프로세스를 종료하게끔 구현을 했습니다. 그러다가 우연히 psutil이라는 모듈의 존재를 알게 되었는데요, 고민이 되었습니다.

    psutil을 사용해 파이썬만으로 제어를 할 것인지, 아니면 파이썬과 배시를 왔다갔다 할 것인지... 본문에서 말씀하신 것처럼 그냥 파이썬으로만 하는 것이 나은 건가요?? 제가 생각한 장단점은 다음과 같습니다.

    psutil을 사용하면 조금 느리다. 대신 파이썬 문법만으로 프로세스 제어가 된다.

    반면에 bash를 사용하면 속도는 훨씬 빠르다. 하지만 따로 linux 커맨드나 script 작성법을 공부해야한다.

    혹시라도 조언을 주시면 감사하겠습니다 :)

    2021.07.23 03:56
  • 프로필사진 BlogIcon Jaeyeon Baek 하시려는 작업이 정확이 뭐냐에 따라 답변이 달라질 듯 합니다. 애드훅 스크립트 안에서 python 프로그램을 실행시키는 것도 때로는 필요하고, 관리 포인트를 한곳에 몰아주기 위해 python 코드 내에서 다른 스크립트를 실행하기도 합니다.

    내용을 잘 모르는 상태에서 말씀드리는 부분이지만 속도는 문제가 안되고 정답이 있는 부분이 아니라서(best practice는 있고) 적절히 사용하시라는 답변밖에 못하겠네요... :)

    --
    "하지만 따로 linux 커맨드나 script 작성법을 공부해야한다" 이 부분은 윈도우 개발자가 아닌 이상에는 말씀하신 문제를 해결하기 위한 목적이 아니더라도 어느정도 필수라고 생각됩니다. :)
    2021.07.23 13:02 신고
  • 프로필사진 BlogIcon ㅎㅎ 혹시 그럼 multiprocess 패키지를 사용하는 것과는 차이가 있나요?? 2021.07.24 00:43
  • 프로필사진 BlogIcon Jaeyeon Baek 앞선 댓글과 마찬가지로 multiprocess도 수단일 뿐이라고 생각됩니다. 대부분의 경우 무엇을 선택하든 속도에는 크게 지장이 없거든요. :) 2021.07.28 09:08 신고
  • 프로필사진 BlogIcon ㅎㅎ 사실 제가 tkinter랑 multiprocess로 병행으로 루프를 돌리려고 했었습니다. 근데 제가 함수를 잘 못쓰는 건지 잘 안되가지고 그냥 코드를 나눠서 bash 커맨드를 통해 다른 파일을 같이 실행시키는 방식으로 했었습니다.

    말씀하신대로라면 원래는 multiprocess로도 충분히 구현이 가능한 건가요??

    귀찮게 해드려서 죄송합니다
    2021.07.29 16:25
  • 프로필사진 BlogIcon Jaeyeon Baek 구현체가 뭔지 모르겠지만 안될 이유는 없지 않을까요? ㅎㅎ GitHub 같은 곳에 관련된 코드 올려주시면 도움드릴 수 있을지도 모르겠네요 :) 2021.07.30 02:08 신고
댓글쓰기 폼