일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 자료구조
- 데이터구조
- 웹개발
- 네트워크보안
- 버전관리
- 소프트웨어
- 보안
- 인공지능
- 알고리즘
- 딥러닝
- 머신러닝
- 데이터베이스
- 사이버보안
- 데이터분석
- 데이터과학
- 프로그래밍언어
- 소프트웨어공학
- 자바스크립트
- I'm Sorry
- 컴퓨터공학
- 2
- Yes
- 코딩
- 컴퓨터비전
- 파이썬
- 클라우드컴퓨팅
- 프로그래밍
- 컴퓨터과학
- 네트워크
- 빅데이터
- Today
- Total
스택큐힙리스트
파이썬에서 메모리 사용량을 프로파일링하는 방법은 무엇인가요? 본문
최근에 알고리즘에 관심을 가지게 되어 기초적인 구현부터 최적화를 위한 방법을 탐구하고 있습니다.
저는 이미 대부분의 경우에는 IPython의 timeit 매직 함수만으로 충분하다 생각하여 런타임 프로파일링을 위한 표준 Python 모듈에 익숙합니다. 그러나 메모리 사용량 또한 관심이 있어서 이러한 트레이드오프를 탐색할 수 있게 됩니다(예: 이전에 계산된 값들의 테이블 캐싱 방식 대신 필요한 경우 다시 계산하는 방식에 따른 비용 등). 제게 주어진 함수의 메모리 사용률을 프로파일링 할 수 있는 모듈이 있을까요?
답변 1
파이썬 3.4는 새로운 모듈을 포함하고 있습니다: tracemalloc. 이 모듈은 어떤 코드가 가장 많은 메모리를 할당하고 있는지에 대한 상세한 통계를 제공합니다. 다음은 메모리를 할당하는 상위 세 줄을 표시하는 예시입니다.
from collections import Counter
import linecache
import os
import tracemalloc
def display_top(snapshot, key_type='lineno', limit=3):
snapshot = snapshot.filter_traces((
tracemalloc.Filter(False,
tracemalloc.Filter(False,
))
top_stats = snapshot.statistics(key_type)
print(Top %s lines % limit)
for index, stat in enumerate(top_stats[:limit], 1):
frame = stat.traceback[0]
# replace /path/to/module/file.py with module/file.py
filename = os.sep.join(frame.filename.split(os.sep)[-2:])
print(#%s: %s:%s: %.1f KiB
% (index, 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()
counts = Counter()
fname = '/usr/share/dict/american-english'
with open(fname) as words:
words = list(words)
for word in words:
prefix = word[:3]
counts[prefix] += 1
print('Top prefixes:', counts.most_common(3))
snapshot = tracemalloc.take_snapshot()
display_top(snapshot)
그리고 여기 결과물입니다:
Top prefixes: [('con', 1220), ('dis', 1002), ('pro', 809)]
Top 3 lines
#1: scratches/memory_test.py:37: 6527.1 KiB
words = list(words)
#2: scratches/memory_test.py:39: 247.7 KiB
prefix = word[:3]
#3: scratches/memory_test.py:40: 193.0 KiB
counts[prefix] += 1
4 other: 4.3 KiB
Total allocated size: 6972.1 KiB
메모리 누수가 누수가 아닐 때는 언제입니까?
계산이 끝날 때 메모리가 여전히 유지될 때 그 예는 훌륭하지만 때로는 많은 양의 메모리를 할당하고 모두 해제하는 코드가 있습니다. 이는 기술적으로 메모리 누수가 아니지만 사용하는 메모리가 예상보다 많은 것입니다. 모든 것이 해제될 때 메모리 사용량을 어떻게 추적할 수 있나요? 코드가 당신의 것이라면 실행 중에 스냅샷을 찍기 위해 일부 디버깅 코드를 추가할 수 있습니다. 그렇지 않으면 메인 스레드가 실행될 때 메모리 사용량을 모니터링하기 위해 백그라운드 스레드를 시작할 수 있습니다.
여기 이전 예제가 있습니다. 코드가 모두 count_prefixes() 함수로 이동되었습니다. 그 함수가 반환되면 모든 메모리가 해제됩니다. 또한 장시간 실행되는 계산을 시뮬레이션하기 위해 몇 가지 sleep() 호출을 추가했습니다.
from collections import Counter
import linecache
import os
import tracemalloc
from time import sleep
def count_prefixes():
sleep(2) # Start up time.
counts = Counter()
fname = '/usr/share/dict/american-english'
with open(fname) as words:
words = list(words)
for word in words:
prefix = word[:3]
counts[prefix] += 1
sleep(0.0001)
most_common = counts.most_common(3)
sleep(3) # Shut down time.
return most_common
def main():
tracemalloc.start()
most_common = count_prefixes()
print('Top prefixes:', most_common)
snapshot = tracemalloc.take_snapshot()
display_top(snapshot)
def display_top(snapshot, key_type='lineno', limit=3):
snapshot = snapshot.filter_traces((
tracemalloc.Filter(False,
tracemalloc.Filter(False,
))
top_stats = snapshot.statistics(key_type)
print(Top %s lines % limit)
for index, stat in enumerate(top_stats[:limit], 1):
frame = stat.traceback[0]
# replace /path/to/module/file.py with module/file.py
filename = os.sep.join(frame.filename.split(os.sep)[-2:])
print(#%s: %s:%s: %.1f KiB
% (index, 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))
main()
그 버전을 실행하면, 함수가 완료되면 모든 메모리를 해제했기 때문에 메모리 사용량이 6MB에서 4KB로 감소했습니다.
Top prefixes: [('con', 1220), ('dis', 1002), ('pro', 809)]
Top 3 lines
#1: collections/__init__.py:537: 0.7 KiB
self.update(*args, **kwds)
#2: collections/__init__.py:555: 0.6 KiB
return _heapq.nlargest(n, self.items(), key=_itemgetter(1))
#3: python3.6/heapq.py:569: 0.5 KiB
result = [(key(elem), i, elem) for i, elem in zip(range(0, -n, -1), it)]
10 other: 2.2 KiB
Total allocated size: 4.0 KiB
이제 여기에는 두 번째 스레드를 시작하여 메모리 사용량을 모니터링하는 것에 영감을 받은 another answer 버전이 있습니다.
from collections import Counter
import linecache
import os
import tracemalloc
from datetime import datetime
from queue import Queue, Empty
from resource import getrusage, RUSAGE_SELF
from threading import Thread
from time import sleep
def memory_monitor(command_queue: Queue, poll_interval=1):
tracemalloc.start()
old_max = 0
snapshot = None
while True:
try:
command_queue.get(timeout=poll_interval)
if snapshot is not None:
print(datetime.now())
display_top(snapshot)
return
except Empty:
max_rss = getrusage(RUSAGE_SELF).ru_maxrss
if max_rss > old_max:
old_max = max_rss
snapshot = tracemalloc.take_snapshot()
print(datetime.now(), 'max RSS', max_rss)
def count_prefixes():
sleep(2) # Start up time.
counts = Counter()
fname = '/usr/share/dict/american-english'
with open(fname) as words:
words = list(words)
for word in words:
prefix = word[:3]
counts[prefix] += 1
sleep(0.0001)
most_common = counts.most_common(3)
sleep(3) # Shut down time.
return most_common
def main():
queue = Queue()
poll_interval = 0.1
monitor_thread = Thread(target=memory_monitor, args=(queue, poll_interval))
monitor_thread.start()
try:
most_common = count_prefixes()
print('Top prefixes:', most_common)
finally:
queue.put('stop')
monitor_thread.join()
def display_top(snapshot, key_type='lineno', limit=3):
snapshot = snapshot.filter_traces((
tracemalloc.Filter(False,
tracemalloc.Filter(False,
))
top_stats = snapshot.statistics(key_type)
print(Top %s lines % limit)
for index, stat in enumerate(top_stats[:limit], 1):
frame = stat.traceback[0]
# replace /path/to/module/file.py with module/file.py
filename = os.sep.join(frame.filename.split(os.sep)[-2:])
print(#%s: %s:%s: %.1f KiB
% (index, 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))
main()
resource 모듈을 사용하여 현재 메모리 사용량을 확인하고 최대 메모리 사용량에서 스냅샷을 저장할 수 있습니다. 대기열은 주 스레드가 메모리 모니터 스레드가 보고서를 인쇄하고 종료할 때까지 기다릴 때 사용됩니다. 실행되면, list() 호출에 의해 사용되는 메모리가 표시됩니다.
2018-05-29 10:34:34.441334 max RSS 10188
2018-05-29 10:34:36.475707 max RSS 23588
2018-05-29 10:34:36.616524 max RSS 38104
2018-05-29 10:34:36.772978 max RSS 45924
2018-05-29 10:34:36.929688 max RSS 46824
2018-05-29 10:34:37.087554 max RSS 46852
Top prefixes: [('con', 1220), ('dis', 1002), ('pro', 809)]
2018-05-29 10:34:56.281262
Top 3 lines
#1: scratches/scratch.py:36: 6527.0 KiB
words = list(words)
#2: scratches/scratch.py:38: 16.4 KiB
prefix = word[:3]
#3: scratches/scratch.py:39: 10.1 KiB
counts[prefix] += 1
19 other: 10.8 KiB
Total allocated size: 6564.3 KiB
리눅스에서는, resource 모듈보다 /proc/self/statm 가 더 유용할 수 있습니다.
답변 2
파이썬은 아주 유연하기 때문에 때때로 프로그램 실행 시 메모리 소비량이 관건이 될 때가 있습니다. 특히 빅 데이터를 다룰 때는 매우 중요한 문제가 됩니다. 이러한 경우 파이썬에서 메모리 사용량을 프로파일링(profiling)하여 어떤 부분에서 메모리를 많이 사용하는지 분석할 필요가 있습니다.우선 파이썬 내장 모듈인 `memory_profiler`를 사용하여 메모리 사용량을 측정해볼 수 있습니다. `memory_profiler`는 파이썬 코드에서 메모리 누수(memory leak)를 식별하는 데 도움이 됩니다. 패키지는 `pip`를 통해 설치할 수 있습니다.
파이썬 코드에서 메모리 사용량을 측정할 때는 일반적으로 다음과 같은 방법을 사용합니다.
```python
from memory_profiler import profile
@profile
def foo():
bar = []
for i in range(1000000):
bar.append(i)
print(fbar: {bar})
if __name__ == '__main__':
foo()
```
`profile` 데코레이터를 사용하여 `foo` 함수가 호출될 때 메모리 사용량을 측정합니다. 프로그램을 실행하면 다음과 같은 출력이 나타납니다.
```
Line # Mem usage Increment Line Contents
================================================
3 36.6 MiB 0.0 MiB @profile
4 def foo():
5 36.6 MiB 0.0 MiB bar = []
6
7 45.6 MiB 9.0 MiB for i in range(1000000):
8 45.6 MiB 0.0 MiB bar.append(i)
9
10 53.6 MiB 8.0 MiB print(fbar: {bar})
```
해당 출력 내용은 코드의 각 라인에서 사용 중인 메모리 양을 결정합니다. 기본적으로 `memory_profiler`는 프로그램 시작 시점부터 메모리 사용량을 추적합니다. 따라서 파일의 시작 부분에 `@profile` 데코레이터를 추가하여 다른 함수에서도 메모리 사용량을 측정할 수 있습니다.
메모리 사용량이 갑자기 증가하는 코드 부분을 빨리 찾고 수정하면 프로그램 실행 시간을 크게 단축할 수 있습니다. `memory_profiler`는 메모리 누수가 발생하는 함수나 메서드도 식별합니다. 따라서 함수가 끝난 후에 메모리를 해제하거나 더 이상 필요하지 않은 변수를 삭제하는 것이 중요합니다.
메모리 사용량을 프로파일링하는 것은 성능 향상 및 더 나은 메모리 관리를 위해 매우 중요합니다. 파이썬 코드에서 메모리를 프로파일링하려면 `memory_profiler` 패키지를 사용하고 프로그램이 진행 중인 동안 메모리 사용량을 살펴보십시오. 이를 위해 `@profile` 데코레이터를 함수나 메서드에 추가하면 됩니다.