내 Python 코드에서 메모리 누수 찾기
1단계 - 번식
모든 버그와 마찬가지로 안정적으로 수정하려면 먼저 재현해야 합니다.
이제 신뢰할 수 있는 재생산이 있었지만(결국 프로세스에는 OOM Killer와 정기적인 날짜가 있었습니다) 버그를 해결하려면 3일이 최적의 주기가 아닙니다. 그래서 우리는 코드로 이동합니다.
주요 아이디어는 메인 루프에서 시작하여 누수를 명시하기 위해 실행해야 하는 코드의 범위를 좁히는 것입니다. 이 프로세스에는 몇 가지 교육된 추측(프로세스에서 메모리 및 할당 호그가 발생할 가능성이 있는 위치? 누출 가능성이 높은 부분? 정리가 필요한 코드가 있습니까?), 대기, 좌절 및 도구가 포함됩니다.
트레이스말록
각 개발자와 코드베이스에는 고유한 추측과 좌절이 있지만 좋은 도구는 더 널리 적용됩니다. 이 부분에서는 Python의 tracemalloc module 을 사용했습니다.
무엇보다도
tracemalloc
를 사용하면 매우 낮은 오버헤드 방식으로 코드의 2개 지점 간의 메모리 사용량을 추적할 수 있습니다.tracemalloc.start() # Start the memory trace
code_suspected_of_leak()
current, peak = tracemalloc.get_traced_memory() # Get memory stats
이 코드를 실행한 후
peak
는 추적 기간 동안 최대 메모리 사용량을 유지하고 current
는 추적 시작부터 현재 상태까지의 차이를 유지합니다. current
가 0이 아닌 것으로 예상해야 합니다. 그러나 너무 높으면 코드가 누출될 수 있습니다.코드의 의심스러운 부분 주위에 이러한 추적을 배치하여 누출되는 부분을 찾을 수 있습니다. 기억하십시오 - 상태를 유지하지 않을 것으로 예상되는 함수에만 이 작업을 수행하십시오. 함수가 외부 객체를 변경하거나 멤버 함수인 경우 메모리 사용량의 변화를 매우 많이 나타냅니다.
2단계 - 분류
재생산(비교적 짧은 시간이 소요됨)이 발생하면 누출 코드를 찾고 싶습니다. 관련 라인을 찾을 때까지 측정된 코드의 범위를 좁히려고 시도하고 계속할 수 있지만 더 깊이 들어갈수록 누출을 정상 실행과 분리하기가 더 어려워집니다.
그래서 이 시점에서 우리는 할당된 메모리를 살펴보고 어떤 객체가 없어야 할 때 거기에 있는지 확인하려고 합니다.
핌플러
Python 프로세스에서 개체를 검사하려면
pympler
을 사용하는 것이 좋습니다.Pympler is a development tool to measure, monitor and analyze the memory behavior of Python objects in a running Python application.
우리는 그것을 2가지 일에 사용할 것입니다.
할당된 개체 검사
먼저
pympler
를 사용하여 재현 중에 할당되었고 여전히 할당된 개체를 보여줍니다.from pympler import tracker, muppy, refbrowser
from functools import lru_cache
# Naive code to trigger a leak
class Value:
def __init__(self, value):
self._value = value
def __repr__(self):
return f"Value({self._value})"
@lru_cache(maxsize=100)
def get_value(value):
return Value(value)
def code_suspected_of_leak():
for x in range(10):
print(get_value(x))
# Measuring code
def main():
tr = tracker.SummaryTracker()
code_suspected_of_leak()
tr.print_diff()
이것을 실행하면 생성되고 파괴된 객체에 대한 요약을 보여주는 멋진 테이블을 얻을 수 있습니다.
types | # objects | total size
======================= | =========== | ============
list | 4892 | 500.59 KB
str | 4887 | 341.45 KB
int | 1053 | 28.79 KB
dict | 13 | 1.90 KB
__main__.Value | 10 | 640 B
function (store_info) | 1 | 144 B
cell | 2 | 112 B
weakref | 1 | 88 B
tuple | -8 | -680 B
보시다시피, 생성된 원시 개체가 상당히 많고 일부
__main__.Value
개체도 있습니다. 내 경험상 프리미티브는 코드에서 의미가 없기 때문에 추적하기가 더 어렵습니다. 그러나 고유한 유형은 일반적으로 코드베이스의 특정 부분에서만 사용되므로 더 쉽게 이해할 수 있습니다.이제 10개의 새로운
Value
개체가 있음을 확인했으므로 메모리에 누가 해당 개체를 보유하고 있는지 알아낼 차례입니다.def output_function(o):
return str(type(o))
all_objects = muppy.get_objects()
root = muppy.filter(all_objects, Value)[0]
cb = refbrowser.ConsoleBrowser(root, maxdepth=2, str_func=output_function)
cb.print_tree()
그러면 다음이 인쇄됩니다.
<class '__main__.Value'>-+-<class 'list'>
+-<class 'functools._lru_cache_wrapper'>-+-<class 'list'>
+-<class 'dict'>
문제 포기 -
lru_cache
가 Value
개체를 유지합니다. 디자인한 그대로...나는 이것이 약간의 인위적인 예처럼 보이지만 메모리에 개체를 유지하는
lru_cache
정확히 내가 가진 문제였습니다. 훨씬 더 많은 코드 아래에 묻혔습니다.3단계 - 솔루션
현재 저는 상상할 수 있는 가장 추악한 솔루션을 사용하고 있습니다.
lru_cache
로 장식된 함수에는 cache_clear()
메서드가 있으며 코드의 특정 위치에서 이를 호출합니다. 추악하지만 작동합니다.더 깨끗한 솔루션에는 전용 캐시와 더 나은 정리 메커니즘이 필요합니다. 관련 토론을 읽을 수 있습니다here.
Reference
이 문제에 관하여(내 Python 코드에서 메모리 누수 찾기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/tmr232/finding-a-memory-leak-in-my-python-code-j73텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)