PEP 454 – Python 메모리 할당을 추적하는 새로운 tracemalloc 모듈을 추가합니다
- Author:
- Victor Stinner <vstinner at python.org>
- BDFL-Delegate:
- Charles-François Natali <cf.natali at gmail.com>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 03-Sep-2013
- Python-Version:
- 3.4
- Resolution:
- Python-Dev message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP에서는 Python이 할당한 메모리 블록을 추적하는 새로운 tracemalloc 모듈의 추가를 제안합니다.
근거
Valgrind와 같은 고전적인 범용 도구는 메모리 블록이 할당된 C 트레이스백을 가져올 수 있습니다. 이러한 도구를 사용하여 Python 메모리 할당을 분석해도 도움이 되지 않습니다. 대부분의 메모리 블록이 예를 들어 PyMem_Malloc()에서와 같이 동일한 C 함수에서 할당되기 때문입니다. 또한 Python에는 효율성을 위해 사용 가능한 블록을 유지하는 “pymalloc”이라는 작은 객체용 할당자가 있습니다. 이러한 도구는 이를 제대로 처리하지 못합니다.
Python 언어 전용 디버그 도구로는 Heapy, Pympler 및 Meliae가 있으며, 이 도구들은 가비지 수집기 모듈(gc.get_objects(), gc.get_referrers() 및 gc.get_referents()와 같은 함수)을 사용하여 살아 있는 모든 객체를 나열하고, 객체의 크기(예: sys.getsizeof()를 사용하여)를 계산하며, 객체를 유형별로 그룹화합니다. 이러한 도구는 애플리케이션의 메모리 사용량을 더 잘 추정합니다. 대부분의 메모리 누수가 동일한 유형의 인스턴스이고 이 유형이 소수의 함수에서만 인스턴스화될 때 유용합니다. str 또는 tuple처럼 객체 유형이 매우 일반적인 경우 문제가 발생하며, 이러한 객체가 어디에서 인스턴스화되었는지 식별하기 어렵습니다.
참조 순환을 찾는 것 또한 어려운 문제입니다. 모든 참조의 다이어그램을 그리는 다양한 도구가 있습니다. 수천 개의 객체가 있는 대규모 애플리케이션에서는 다이어그램이 너무 커서 수동으로 분석할 수 없기 때문에 이러한 도구를 사용할 수 없습니다.
제안
사용자 지정 할당 API(PEP 445)를 사용하면 파이썬 메모리 할당자에 훅을 쉽게 설정할 수 있습니다. 후크는 Python 내부를 검사하여 Python 트레이스백을 가져올 수 있습니다. 현재 트레이스백을 가져온다는 아이디어는 faulthandler 모듈에서 비롯되었습니다. faulthandler는 충돌이 발생하면 모든 Python 스레드의 트레이스백을 덤프합니다. 여기서의 아이디어는 Python이 메모리 블록을 할당할 때 현재 Python 스레드의 트레이스백을 가져오는 것입니다.
이 PEP에서는 Python이 할당한 메모리 블록을 추적하는 디버그 도구인 새로운 tracemalloc 모듈의 추가를 제안합니다. 이 모듈은 다음 정보를 제공합니다.
- 객체가 할당된 위치의 트레이스백
- 파일 이름 및 줄 번호별로 할당된 메모리 블록에 대한 통계: 할당된 메모리 블록의 총 크기, 개수 및 평균 크기
- 메모리 누수를 감지하기 위해 두 스냅샷 간의 차이를 계산한 결과
tracemalloc 모듈의 API는 faulthandler 모듈의 API와 유사합니다. enable() / start(), disable() / stop() 및 is_enabled() / is_tracing() 함수, 환경 변수(PYTHONFAULTHANDLER 및 PYTHONTRACEMALLOC), 그리고 -X 명령줄 옵션(-X faulthandler 및 -X tracemalloc)을 제공합니다. faulthandler 모듈 문서를 참조하십시오.
메모리 할당을 추적한다는 아이디어는 새로운 것이 아닙니다. 2005년에 PySizer 프로젝트에서 처음 구현되었습니다. PySizer는 다르게 구현되었습니다. 트레이스백은 프레임 객체에 저장되었고 일부 Python 유형은 트레이스를 객체 유형의 이름과 연결했습니다. CPython용 PySizer 패치는 PySizer를 사용하지 않았더라도 성능과 메모리 사용 공간에 오버헤드를 추가합니다. tracemalloc은 하위 계층인 메모리 블록에 트레이스백을 연결하며, 모듈이 메모리 할당을 추적하지 않을 때는 오버헤드가 없습니다.
tracemalloc 모듈은 CPython용으로 작성되었습니다. 다른 Python 구현에서는 이를 제공하지 못할 수 있습니다.
API
Python이 할당하는 대부분의 메모리 블록을 추적하려면 PYTHONTRACEMALLOC 환경 변수를 1로 설정하거나 -X tracemalloc명령줄 옵션을 사용하여 모듈을 가능한 한 일찍 시작해야 합니다. tracemalloc.start() 함수는 런타임에 호출하여 Python 메모리 할당 추적을 시작할 수 있습니다.
기본적으로 할당된 메모리 블록의 추적 정보에는 가장 최근 프레임 하나(1개 프레임)만 저장됩니다. 시작 시 프레임 25개를 저장하려면 PYTHONTRACEMALLOC 환경 변수를 25로 설정하거나 -X tracemalloc=25명령줄 옵션을 사용하십시오. set_traceback_limit() 함수는 런타임에 제한을 설정하는 데 사용할 수 있습니다.
함수
clear_traces() 함수:
Python이 할당한 메모리 블록의 추적을 지웁니다.
stop()도 참조하십시오.
get_object_traceback(obj) 함수:
Python 객체 obj가 할당된 위치의 트레이스백을 가져옵니다.Traceback인스턴스를 반환하거나,tracemalloc모듈이 메모리 할당을 추적하고 있지 않거나 해당 객체의 할당을 추적하지 않았다면None을 반환합니다.
gc.get_referrers()및sys.getsizeof()함수도 참조하십시오.
get_traceback_limit() 함수:
추적 정보의 트레이스백에 저장되는 최대 프레임 수를 가져옵니다.제한을 가져오려면
tracemalloc모듈이 메모리 할당을 추적하고 있어야 하며, 그렇지 않으면 예외가 발생합니다.제한은
start()함수로 설정됩니다.
get_traced_memory() 함수:
tracemalloc모듈이 추적하는 메모리 블록의 현재 크기와 최대 크기를 튜플(size: int, max_size: int)로 가져옵니다.
get_tracemalloc_memory() 함수:
메모리 블록의 추적 정보를 저장하는 데 사용되는tracemalloc모듈의 메모리 사용량을 바이트 단위로 가져옵니다.int를 반환합니다.
is_tracing() 함수:
tracemalloc모듈이 Python 메모리 할당을 추적하고 있으면True를, 그렇지 않으면False를 반환합니다.
start()및stop()함수도 참조하십시오.
start(nframe: int=1) 함수:
Python 메모리 할당 추적을 시작합니다. Python 메모리 할당자에 후크를 설치합니다. 수집되는 추적 정보의 트레이스백은 nframe 프레임으로 제한됩니다. 기본적으로 메모리 블록의 추적 정보에는 가장 최근 프레임만 저장되므로 제한은1입니다. nframe은1이상이어야 합니다.
1개를 초과하는 프레임을 저장하는 것은'traceback'별로 그룹화된 통계를 계산하거나 누적 통계를 계산할 때만 유용합니다.Snapshot.compare_to()및Snapshot.statistics()메서드를 참조하십시오.더 많은 프레임을 저장하면
tracemalloc모듈의 메모리 및 CPU 오버헤드가 증가합니다.get_tracemalloc_memory()함수를 사용하여tracemalloc모듈이 사용하는 메모리 양을 측정하십시오.
PYTHONTRACEMALLOC환경 변수(PYTHONTRACEMALLOC=NFRAME)와-Xtracemalloc=NFRAME명령줄 옵션을 사용하여 시작 시 추적을 시작할 수 있습니다.
stop(),is_tracing()및get_traceback_limit()함수도 참조하십시오.
stop() 함수:
Python 메모리 할당 추적을 중지하십시오. Python 메모리 할당자의 훅을 제거하십시오. Python이 할당한 메모리 블록의 추적도 지우십시오.추적을 지우기 전에 스냅샷을 생성하려면
take_snapshot()함수를 호출하십시오.
start()및is_tracing()함수도 참조하십시오.
take_snapshot() 함수:
Python이 할당한 메모리 블록 추적의 스냅샷을 생성하십시오. 새Snapshot인스턴스를 반환합니다.스냅샷에는
tracemalloc모듈이 메모리 할당 추적을 시작하기 전에 할당된 메모리 블록이 포함되지 않습니다.추적의 트레이스백은
get_traceback_limit()프레임으로 제한됩니다. 더 많은 프레임을 저장하려면start()함수의 nframe 매개변수를 사용하십시오.스냅샷을 생성하려면
tracemalloc모듈이 메모리 할당을 추적하고 있어야 합니다.start()함수를 참조하십시오.
get_object_traceback()함수도 참조하십시오.
필터
Filter(inclusive: bool, filename_pattern: str, lineno: int=None, all_frames: bool=False) 클래스:
메모리 블록의 추적을 필터링합니다.filename_pattern의 구문은
fnmatch.fnmatch()함수를 참조하십시오.'.pyc'및'.pyo'파일 확장자는'.py'로 대체됩니다.예:
Filter(True, subprocess.__file__)는subprocess모듈의 추적만 포함합니다.Filter(False, tracemalloc.__file__)는tracemalloc모듈의 추적을 제외합니다.Filter(False, "<unknown>")는 빈 트레이스백을 제외합니다.
inclusive 특성:
inclusive가True(포함)이면filename_pattern과 일치하는 이름을 가진 파일의lineno번째 줄에서 할당된 메모리 블록의 추적만 포함합니다.inclusive가
False(제외)이면filename_pattern과 일치하는 이름을 가진 파일의lineno번째 줄에서 할당된 메모리 블록을 무시합니다.
lineno 특성:
필터의 줄 번호 (int)입니다. lineno이None이면 필터는 모든 줄 번호와 일치합니다.
filename_pattern 특성:
필터의 파일 이름 패턴 (str)입니다.
all_frames 특성:
all_frames이True이면 트레이스백의 모든 프레임을 검사합니다. all_frames이False이면 가장 최근 프레임만 검사합니다.트레이스백 제한이
2보다 작으면 이 특성은 무시됩니다.get_traceback_limit()함수와Snapshot.traceback_limit특성을 참조하십시오.
프레임
Frame 클래스:
트레이스백의 프레임입니다.
Traceback클래스는Frame인스턴스의 시퀀스입니다.
filename 특성:
파일 이름 (str)입니다.
lineno 특성:
줄 번호 (int)입니다.
스냅샷
Snapshot 클래스:
Python이 할당한 메모리 블록의 트레이스 스냅샷입니다.
take_snapshot()함수는 스냅샷 인스턴스를 생성합니다.
compare_to(old_snapshot: Snapshot, group_by: str, cumulative: bool=False) 메서드:
이전 스냅샷과의 차이를 계산합니다. group_by로 그룹화한StatisticDiff인스턴스의 통계를 정렬된 목록으로 가져옵니다.group_by 및 cumulative 매개변수에 대해서는
statistics()메서드를 참조하십시오.결과는
StatisticDiff.size_diff의 절댓값,StatisticDiff.size,StatisticDiff.count_diff의 절댓값,Statistic.count, 그리고 마지막으로StatisticDiff.traceback을 기준으로 가장 큰 값부터 가장 작은 값 순으로 정렬됩니다.
dump(filename) 메서드:
스냅샷을 파일에 기록합니다.스냅샷을 다시 로드하려면
load()를 사용하십시오.
filter_traces(filters) 메서드:
필터링된traces시퀀스를 사용하는 새Snapshot인스턴스를 생성합니다. filters는Filter인스턴스의 목록입니다. filters가 빈 목록이면 트레이스의 복사본을 사용하는 새Snapshot인스턴스를 반환합니다.모든 포함 필터는 동시에 적용되며, 어떤 포함 필터와도 일치하지 않는 추적은 무시됩니다. 하나 이상의 제외 필터와 일치하는 추적은 무시됩니다.
load(filename) 클래스 메서드:
파일에서 스냅샷을 로드합니다.
dump()도 참조하십시오.
statistics(group_by: str, cumulative: bool=False) 메서드:
group_by를 기준으로 그룹화된Statistic인스턴스의 정렬된 목록으로 통계를 가져옵니다.
group_by 설명 'filename'파일 이름 'lineno'파일 이름 및 줄 번호 'traceback'트레이스백 cumulative가
True이면 추적에서 가장 최근 프레임뿐만 아니라 트레이스백의 모든 프레임에 대한 메모리 블록의 크기와 개수를 누적합니다. 누적 모드는 group_by가'filename'및'lineno'와 같고traceback_limit이1보다 큰 경우에만 사용할 수 있습니다.결과는
Statistic.size,Statistic.count및Statistic.traceback순으로 큰 값에서 작은 값으로 정렬됩니다.
traceback_limit 속성:
traces의 트레이스백에 저장되는 최대 프레임 수입니다. 스냅샷을 생성할 때의get_traceback_limit()호출 결과입니다.
traces 속성:
Python이 할당한 모든 메모리 블록의 추적입니다.Trace인스턴스의 시퀀스입니다.시퀀스의 순서는 정의되지 않습니다. 정렬된 통계 목록을 가져오려면
Snapshot.statistics()메서드를 사용하십시오.
Statistic
Statistic 클래스:
메모리 할당에 대한 통계입니다.
Snapshot.statistics()는Statistic인스턴스의 목록을 반환합니다.
StatisticDiff클래스도 참조하십시오.
count 속성:
메모리 블록의 개수(int)입니다.
size 속성:
메모리 블록의 총 크기(바이트 단위)(int)입니다.
traceback속성:
메모리 블록이 할당된 트레이스백(Traceback인스턴스)입니다.
StatisticDiff
StatisticDiff클래스:
이전 인스턴스와 새로운Snapshot인스턴스 간 메모리 할당 통계 차이입니다.
Snapshot.compare_to()는StatisticDiff인스턴스의 목록을 반환합니다.Statistic클래스도 참조하십시오.
count속성:
새 스냅샷의 메모리 블록 수(int)입니다: 새 스냅샷에서 메모리 블록이 해제된 경우0입니다.
count_diff속성:
이전 스냅샷과 새 스냅샷 간 메모리 블록 수의 차이(int)입니다: 새 스냅샷에서 메모리 블록이 할당된 경우0입니다.
size속성:
새 스냅샷의 메모리 블록 총 크기(바이트 단위)(int)입니다: 새 스냅샷에서 메모리 블록이 해제된 경우0입니다.
size_diff속성:
이전 스냅샷과 새 스냅샷 간 메모리 블록 총 크기(바이트 단위)의 차이(int)입니다: 새 스냅샷에서 메모리 블록이 할당된 경우0입니다.
traceback속성:
메모리 블록이 할당된 트레이스백(Traceback인스턴스)입니다.
Trace
Trace클래스:
메모리 블록의 트레이스입니다.
Snapshot.traces속성은Trace인스턴스의 시퀀스입니다.
size속성:
메모리 블록의 크기(바이트 단위)(int)입니다.
traceback속성:
메모리 블록이 할당된 트레이스백(Traceback인스턴스)입니다.
Traceback
Traceback클래스:
가장 최근 프레임부터 가장 오래된 프레임 순으로 정렬된Frame인스턴스의 시퀀스입니다.트레이스백에는 하나 이상의
1프레임이 포함됩니다.tracemalloc모듈이 프레임을 가져오지 못한 경우 줄 번호0의 파일 이름"<unknown>"이 사용됩니다.스냅샷을 생성하면 트레이스의 트레이스백은
get_traceback_limit()프레임으로 제한됩니다.take_snapshot()함수를 참조하십시오.
Trace.traceback속성은Traceback인스턴스의 인스턴스입니다.
거부된 대안
메모리 할당자 호출 기록
다른 접근 방식은 malloc(), realloc() 및 free() 함수 호출을 기록하는 것입니다. 호출을 파일에 기록하거나 네트워크를 통해 다른 컴퓨터로 보낼 수 있습니다. 로그 항목의 예: 함수 이름, 메모리 블록의 크기, 메모리 블록의 주소, 할당이 발생한 Python 트레이스백, 타임스탬프입니다.
로그는 직접 사용할 수 없으며, 메모리의 현재 상태를 확인하려면 이전 로그를 파싱해야 합니다. 예를 들어, 트레이스를 사용할 때 get_object_traceback(obj)가 수행하는 것처럼 Python 객체의 트레이스백을 직접 가져올 수는 없습니다.
Python은 수명이 매우 짧은 객체를 사용하므로 메모리 할당자를 광범위하게 사용합니다. 수명이 짧은 작은 객체(512바이트 미만)에 최적화된 할당자를 사용합니다. 예를 들어, Python 테스트 모음은 초당 평균 270,000회 malloc(), realloc() 또는 free() 를 호출합니다. 로그 항목의 크기가 32바이트라면 로깅으로 초당 8.2MB, 시간당 29.0GB가 생성됩니다.
이 대안은 효율성이 낮고 기능도 적기 때문에 거부되었습니다. 다른 프로세스나 다른 컴퓨터에서 로그를 파싱하는 것은 동일한 프로세스에서 할당된 메모리 블록의 트레이스를 유지하는 것보다 느립니다.
선행 작업
- Python Memory Validator (2005-2013): Software Verification에서 개발한 상용 Python 메모리 검사기입니다. Python Reflection API를 사용합니다.
- PySizer: Nick Smallbone이 개발한 2005년 Google Summer of Code 프로젝트입니다.
- Heapy (2006-2013): Sverker Nilsson이 작성한 Guppy-PE 프로젝트의 일부입니다.
- 초안 PEP: Support Tracking Low-Level Memory Usage in CPython (Brett Canon, 2006)
- Muppy: Robert Schuppenies가 2008년에 개발한 프로젝트입니다.
- asizeof: Jean Brouwers가 작성한 객체 크기 추정용 순수 Python 모듈입니다(2008년).
- Heapmonitor: 개별 객체의 크기를 측정하고 특정 클래스의 모든 객체를 추적할 수 있는 기능을 제공합니다. Ludwig Haehne가 2008년에 개발했습니다.
- Pympler (2008-2011): asizeof, muppy 및 HeapMonitor를 기반으로 한 프로젝트입니다.
- objgraph (2008-2012)
- Dozer: Marius Gedminas가 작성한 CherryPy 메모리 누수 디버거의 WSGI 미들웨어 버전입니다(2008-2013).
- Meliae: John A Meinel이 2009년부터 개발한 Python 메모리 사용량 분석기입니다.
- gdb-heap: 힙 메모리 사용량을 분석하기 위해 Dave Malcolm이 Python으로 작성한 gdb 스크립트입니다(2010-2011).
- memory_profiler: Fabian Pedregosa가 작성했습니다(2011-2013).
- caulk: 2012년 Ben Timby가 작성했습니다.
Pympler Related Work도 참고하십시오.
링크
tracemalloc:
Copyright
This document has been placed in the public domain.