[Python] Memory Profiling

Eugene CHOI·2021년 10월 19일
0

Python

목록 보기
2/2

제가 사용해 본 메모리 프로파일링법은 두 가지가 있습니다.
경험 상의 장단점은 다음과 같았습니다.

  1. tracemalloc 내장 패키지 사용
    • 라이브러리 호출이 많은 경우 모든 호출 경로를 추적하여 명확히 알 수 없음.
    • 함수의 호출로 프로파일링이 이루어지기 때문에 런타임에 모니터링 가능
  2. memory_profiler 외부 패키지 사용
    • 데코레이션을 사용하기 때문에 추적을 원하는 부분이 함수화 되어 있어야 한다.
    • 데코레이션이 붙은 함수보다 깊은 호출 계층은 추적하지 않는다.

tracemalloc

대표적인 사용법은 python docs에 잘 설명이 되어 있고 이를 참조하였습니다.

start()함수를 실행한 순간부터 메모리 사용량을 추적하기 시작합니다.

tracemalloc.start()

take_snapshot() 함수를 사용하면 그 시점에서의 메모리 현황을 저장합니다.

tracemalloc.take_snapshot()

다음 함수를 사용하면 분석 데이터를 추출할 수 있습니다.

snapshot.statistics('lineno')

하지만 여기서 얻은 데이터를 그대로 출력하면 다음과 같이 불필요한 정보와 함께 단순히 나열되기 때문에 한 눈에 파악하기 어렵습니다.

<frozen importlib._bootstrap>:716: size=4855 KiB, count=39328, average=126 B
<frozen importlib._bootstrap>:284: size=521 KiB, count=3199, average=167 B
/usr/lib/python3.4/collections/__init__.py:368: size=244 KiB, count=2315, average=108 B
/usr/lib/python3.4/unittest/case.py:381: size=185 KiB, count=779, average=243 B
/usr/lib/python3.4/unittest/case.py:402: size=154 KiB, count=378, average=416 B
/usr/lib/python3.4/abc.py:133: size=88.7 KiB, count=347, average=262 B
<frozen importlib._bootstrap>:1446: size=70.4 KiB, count=911, average=79 B
<frozen importlib._bootstrap>:1454: size=52.0 KiB, count=25, average=2131 B
<string>:5: size=49.7 KiB, count=148, average=344 B
/usr/lib/python3.4/sysconfig.py:411: size=48.0 KiB, count=1, average=48.0 KiB

따라서 docs 에서는 다음과 같은 함수를 사용하여 깨끗하게 출력할 수 있는 예제를 제공합니다.

import linecache
import os
import tracemalloc

def display_top(snapshot, key_type='lineno', limit=10):
    snapshot = snapshot.filter_traces((
        tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
        tracemalloc.Filter(False, "<unknown>"),
    ))
    top_stats = snapshot.statistics(key_type)

    print("Top %s lines" % limit)
    for index, stat in enumerate(top_stats[:limit], 1):
        frame = stat.traceback[0]
        print("#%s: %s:%s: %.1f KiB"
              % (index, frame.filename, frame.lineno, stat.size / 1024))
        line = linecache.getline(frame.filename, frame.lineno).strip()
        if line:
            print('    %s' % line)

    other = top_stats[limit:]
    if other:
        size = sum(stat.size for stat in other)
        print("%s other: %.1f KiB" % (len(other), size / 1024))
    total = sum(stat.size for stat in top_stats)
    print("Total allocated size: %.1f KiB" % (total / 1024))

tracemalloc.start()

'''

... run your application ...


'''
snapshot = tracemalloc.take_snapshot()
display_top(snapshot)

memory_profiler

마찬가지로 PyPI의 documentation에 예제와 사용법이 잘 정리되어 있습니다.

터미널에 사용 중인 환경에서 다음과 같은 명령어를 입력하여 패키지를 설치하여 줍니다.

$ pip install memory_profiler

반드시 다음과 같이 프로파일링을 하고자 하는 코드를 함수로 만들고 앞에 @profile annotation을 추가하여야 추적이 가능합니다.

# main.py
@profile
def my_func():
    a = [1] * (10 ** 6)
    b = [2] * (2 * 10 ** 7)
    del b
    return a

if __name__ == '__main__':
    my_func()

파이썬 코드를 실행 시, -m memory_profiler 인수를 추가하면 데코레이션을 추가한 함수보다 깊은 호출 계층은 추적하지 않고, 코드별 메모리의 총 사용량과 증감을 계산하여줍니다.
하지만 반드시 정상적으로 코드가 종료 되어야 결과가 출력되기 때문에 런타임이 너무 긴 프로그램이나 대형 프로젝트에는 적절하지 않을 수 있습니다.

$ python -m memory_profiler main.py
ine #    Mem usage    Increment  Occurences   Line Contents
============================================================
     3   38.816 MiB   38.816 MiB           1   @profile
     4                                         def my_func():
     5   46.492 MiB    7.676 MiB           1       a = [1] * (10 ** 6)
     6  199.117 MiB  152.625 MiB           1       b = [2] * (2 * 10 ** 7)
     7   46.629 MiB -152.488 MiB           1       del b
     8   46.629 MiB    0.000 MiB           1       return a
profile
Hi, my name is Eugene CHOI the Automotive MCU FW developer.

0개의 댓글