-
[Python] Tip - tracemalloc으로 메모리 사용 현황과 누수를 파악언어/파이썬 & 장고 2017. 1. 15. 20:19
파이썬, 즉 CPython의 기본 구현은 참조 카운팅(reference counting)으로 메모리를 관리합니다. 참조 카운팅은 객체의 참조가 모두 해제되면 참조된 객체 역시 정리됨을 보장합니다. CPython은 자기 참조 객체가 결국 가비지 컬렉션되는 것을 보장하는 사이클 디텍터(cycle detector)도 갖추고 있습니다.
이론적으로는 대부분의 파이썬 프로그래머가 프로그램에서 일어나는 메모리 할당과 해제를 걱정할 필요가 없다는 의미입니다. 즉, 언어와 CPython 런타임이 자동으로 처리합니다. 그러나 실제로 프로그램은 결국 참조 때문에 메모리 부족에 처합니다. 파이썬 프로그램이 어디서 메모리를 사용하거나 누수를 일으키는지 알아내는 건 힘든 도전과제입니다.
메모리 사용을 디버깅하는 첫 번째 방법은 내장 모듈 gc에 요청하여 가비지 컬렉터에 알려진 모든 객체를 나열하는 것입니다. gc가 그렇게 정확한 도구는 아니지만 이 방법을 이용하면 프로그램의 메모리가 어디서 사용되는지 금방 알 수 있습니다.
다음과 같이 참조를 유지하여 메모리를 낭비하는 프로그램을 실행해보겠습니다. 이 프로그램은 실행 중에 생성된 객체의 수와 할당된 객체들의 샘플을 소량 출력합니다.
# using_gc.py import gc found_objects = gc.get_objects() print('%d objects before' % len(found_objects)) import waste_memory x = waste_memory.run() found_objects = gc.get_objects() print('%d objects after' % len(found_objects)) for obj in found_objects[:3]: print(repr(obj)[:100]) # 결과 # 4756 objects before # 14873 objects after # <waste_memory.MyObject at 0x1063f6940> # <waste_memory.MyObject at 0x1063f6954> # <waste_memory.MyObject at 0x1063f6921>
gc.get_objects를 사용할 때 문제는 객체가 어떻게 할당되는지 아무런 정보도 제공하지 않는다는 점입니다. 복잡한 프로그램에서는 객체의 특정 클래스가 여러 방법으로 할당될 수 있습니다. 객체의 전체 개수는 메모리 누수가 있는 객체 할당 코드를 찾는 것만큼은 중요하지 않습니다.
파이썬 3.4에서는 새 내장 모듈 tracemalloc으로 이 문제를 해결합니다. tracemalloc은 객체가 할당된 위치에 연결할 수 있도록 해줍니다. 다음은 tracemalloc을 사용하여 프로그램에서 메모리를 가장 많이 사용하는 세 부분을 출력한 예제입니다.
# top_n.py import tracemalloc tracemalloc.start(10) # 스택 프레임을 최대 10개까지 저장 time1 = tracemalloc.take_snapshot() import waste_memory x = waste_memory.run() time2 = tracemalloc.take_snapshot() stats = time2.compare_to(time1, 'lineno') for stat in stats[:3]: print(stat) # 결과 # waste_memory.py:6: size=2235 KiB (+2235KiB), count=29981 (+29981), average=76 B # waste_memory.py:7: size=869 KiB (+869KiB), count=10000 (+10000), average=89 B # waste_memory.py12: size=547 KiB (+547KiB), count=10000 (+10000), average=56 B
어떤 객체들이 프로그램 메모리 사용량을 주로 차지하고, 소스 코드의 어느 부분에서 할당되는지를 쉽게 알 수 있습니다.
tracemalloc 모듈은 각 할당의 전체 스택 트레이스(stack trace)도 출력할 수 있습니다(start 메서드에 넘긴 프레임 개수까지). 다음 코드는 프로그램에서 메모리 사용량의 가장 큰 근원이 되는 부분의 스택 트레이스를 출력합니다.
# with_trace.py import tracemalloc tracemalloc.start(10) # 스택 프레임을 최대 10개까지 저장 time1 = tracemalloc.take_snapshot() import waste_memory x = waste_memory.run() time2 = tracemalloc.take_snapshot() stats = time2.compare_to(time1, 'traceback') top = stats[0] print('\n'.join(top.traceback.format())) # 결과 # File "waste_memory.py", line 6 # self.x = os.urandom(100) # File "waste_memory.py", line 12 # obj = MyObject() # File "waste_memory.py", line 19 # deep_values.append(get_data()) # File "with_trace.py", line 10 # x = waste_memory.run()
이와 같은 스택 트레이스는 공통 함수의 어느 부분이 프로그램의 메모리를 많이 소비하는지 알아내는 데 가장 중요한 정보입니다.
요약
파이썬 프로그램이 메모리를 어떻게 사용하고, 메모리 누수를 일으키는지를 이해하기는 어려움
gc 모듈은 어떤 객체가 존재하는지를 이해하는 데 도움을 주지만, 해당 객체가 어떻게 할당되었는지에 대한 정보는 제공하지 않음
내장 모듈 tracemalloc은 메모리 사용량의 근원을 이해하는 데 필요한 강력한 도구를 제공
tracemalloc은 파이썬 3.4 이후 버전에서만 사용 가능