Python에서 메모리 사용량을 프로파일하려면 어떻게 해야 합니까?
최근 알고리즘에 관심을 갖게 되었고, 간단한 구현을 작성하고 다양한 방법으로 최적화함으로써 알고리즘을 탐색하기 시작했습니다.
프로파일링 런타임에 사용되는 표준 Python 모듈(대부분의 경우 IPython의 타임릿 매직 함수로 충분하다는 것을 알게 되었습니다)은 이미 익숙하지만 메모리 사용에도 관심이 있습니다(예: 이전에 계산된 값의 테이블을 캐싱하고 필요에 따라 재계산하는 비용).특정 기능의 메모리 사용량을 프로파일링하는 모듈이 있습니까?
Python 3.4에는 새로운 모듈이 포함되어 있습니다.가장 많은 메모리를 할당하고 있는 코드에 대한 자세한 통계 정보를 제공합니다.다음으로 메모리를 할당하는 상위3 행의 예를 나타냅니다.
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, "<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]
# 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
메모리 누수가 누수가 아닌 경우는 언제입니까?
이 예는 계산의 마지막에 메모리가 유지되고 있는 경우에 매우 좋지만, 경우에 따라서는 대량의 메모리를 할당하고 나서 모든 메모리를 해방하는 코드가 있는 경우가 있습니다.엄밀히 말하면 메모리 누수는 아니지만 생각보다 많은 메모리를 사용하고 있습니다.메모리 사용량이 모두 공개되었을 때 어떻게 추적할 수 있습니까?사용자 코드인 경우 실행 중에 스냅샷을 생성하기 위해 몇 가지 디버깅 코드를 추가할 수 있습니다.그렇지 않으면 기본 스레드가 실행되는 동안 백그라운드 스레드를 시작하여 메모리 사용량을 모니터링할 수 있습니다.
에서는 코드를 이이 the the the the the the the the the the the the the the 로 옮긴 예를 보여 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, "<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]
# 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
다음으로 메모리 사용량을 감시하기 위한 두 번째 스레드를 시작하는 또 다른 답변에서 영감을 얻은 버전을 나타냅니다.
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, "<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]
# 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
module을 사용하면 현재 메모리 사용량을 확인하고 피크 메모리 사용량에서 스냅샷을 저장할 수 있습니다.큐를 사용하면 메인스레드가 메모리모니터 스레드에 리포트를 인쇄하고 종료할 타이밍을 지시할 수 있습니다.때 알 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수 등.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
Linux 를 사용하고 있는 경우는, 보다 편리할 가능성이 있습니다.resource
★★★★★★ 。
Python 메모리 프로파일러는 이미 여기에 답변되어 있습니다.
기본적으로 다음과 같은 작업을 수행합니다(Guppy-PE에서 인용).
>>> from guppy import hpy; h=hpy()
>>> h.heap()
Partition of a set of 48477 objects. Total size = 3265516 bytes.
Index Count % Size % Cumulative % Kind (class / dict of class)
0 25773 53 1612820 49 1612820 49 str
1 11699 24 483960 15 2096780 64 tuple
2 174 0 241584 7 2338364 72 dict of module
3 3478 7 222592 7 2560956 78 types.CodeType
4 3296 7 184576 6 2745532 84 function
5 401 1 175112 5 2920644 89 dict of class
6 108 0 81888 3 3002532 92 dict (no owner)
7 114 0 79632 2 3082164 94 dict of type
8 117 0 51336 2 3133500 96 type
9 667 1 24012 1 3157512 97 __builtin__.wrapper_descriptor
<76 more rows. Type e.g. '_.more' to view.>
>>> h.iso(1,[],{})
Partition of a set of 3 objects. Total size = 176 bytes.
Index Count % Size % Cumulative % Kind (class / dict of class)
0 1 33 136 77 136 77 dict (no owner)
1 1 33 28 16 164 93 list
2 1 33 12 7 176 100 int
>>> x=[]
>>> h.iso(x).sp
0: h.Root.i0_modules['__main__'].__dict__['x']
>>>
오브젝트의 메모리 사용량만 보고 싶은 경우 (다른 질문에 대한 답변)
Pympler라는 모듈이 있습니다.
asizeof
★★★★★★ 。다음과 같이 사용합니다.
from pympler import asizeof asizeof.asizeof(my_object)
★★★★★★★★★★★★★★★와 달리
sys.getsizeof
, 이것은 사용자가 직접 작성한 오브젝트에 대해 작동합니다.>>> asizeof.asizeof(tuple('bcd')) 200 >>> asizeof.asizeof({'foo': 'bar', 'baz': 'bar'}) 400 >>> asizeof.asizeof({}) 280 >>> asizeof.asizeof({'foo':'bar'}) 360 >>> asizeof.asizeof('foo') 40 >>> asizeof.asizeof(Bar()) 352 >>> asizeof.asizeof(Bar().__dict__) 280
>>> help(asizeof.asizeof)
Help on function asizeof in module pympler.asizeof:
asizeof(*objs, **opts)
Return the combined size in bytes of all objects passed as positional arguments.
공개:
- Linux에만 적용 가능
- 내부 개별 기능이 아닌 현재 프로세스에서 사용되는 메모리를 전체적으로 보고합니다.
단, 그 심플함 때문에 좋다.
import resource
def using(point=""):
usage=resource.getrusage(resource.RUSAGE_SELF)
return '''%s: usertime=%s systime=%s mem=%s mb
'''%(point,usage[0],usage[1],
usage[2]/1024.0 )
삽입만 하면 됩니다.using("Label")
를 들면, 「」입니다.
print(using("before"))
wrk = ["wasting mem"] * 1000000
print(using("after"))
>>> before: usertime=2.117053 systime=1.703466 mem=53.97265625 mb
>>> after: usertime=2.12023 systime=1.70708 mem=60.8828125 mb
다음은 함수 호출 전, 함수 호출 후 프로세스가 소비한 메모리 용량 및 차이점을 추적할 수 있는 간단한 함수 데코레이터입니다.
import time
import os
import psutil
def elapsed_since(start):
return time.strftime("%H:%M:%S", time.gmtime(time.time() - start))
def get_process_memory():
process = psutil.Process(os.getpid())
mem_info = process.memory_info()
return mem_info.rss
def profile(func):
def wrapper(*args, **kwargs):
mem_before = get_process_memory()
start = time.time()
result = func(*args, **kwargs)
elapsed_time = elapsed_since(start)
mem_after = get_process_memory()
print("{}: memory before: {:,}, after: {:,}, consumed: {:,}; exec time: {}".format(
func.__name__,
mem_before, mem_after, mem_after - mem_before,
elapsed_time))
return result
return wrapper
여기 모든 세부사항을 설명하는 제 블로그가 있습니다.(클라이언트 링크)
받아들여진 답변과 다음으로 투표율이 높은 답변이 있기 때문에 Ihor B.의 답변에 근거해 작지만 중요한 수정을 가하여 한 가지 더 답변을 드리고 싶습니다.
이 솔루션에서는 함수 호출을 다음 중 하나로 정리하여 프로파일링을 실행할 수 있습니다.profile
기능 및 호출, 또는 기능/장식을 사용하여@profile
데코레이터
첫 번째 기법은 소스에 영향을 주지 않고 서드파티 코드를 프로파일링할 때 유용하며, 두 번째 기법은 프로파일링할 함수/메서드의 소스를 수정하는 것을 꺼리지 않을 때 더 잘 작동합니다.
RSS, VMS 및 공유 메모리를 얻을 수 있도록 출력도 수정했습니다.나는 "전"과 "후" 값은 별로 신경 쓰지 않고 델타만 신경 써서 그것들을 제거했다(Ihor B.의 답변과 비교한다면).
프로파일링 코드
# profile.py
import time
import os
import psutil
import inspect
def elapsed_since(start):
#return time.strftime("%H:%M:%S", time.gmtime(time.time() - start))
elapsed = time.time() - start
if elapsed < 1:
return str(round(elapsed*1000,2)) + "ms"
if elapsed < 60:
return str(round(elapsed, 2)) + "s"
if elapsed < 3600:
return str(round(elapsed/60, 2)) + "min"
else:
return str(round(elapsed / 3600, 2)) + "hrs"
def get_process_memory():
process = psutil.Process(os.getpid())
mi = process.memory_info()
return mi.rss, mi.vms, mi.shared
def format_bytes(bytes):
if abs(bytes) < 1000:
return str(bytes)+"B"
elif abs(bytes) < 1e6:
return str(round(bytes/1e3,2)) + "kB"
elif abs(bytes) < 1e9:
return str(round(bytes / 1e6, 2)) + "MB"
else:
return str(round(bytes / 1e9, 2)) + "GB"
def profile(func, *args, **kwargs):
def wrapper(*args, **kwargs):
rss_before, vms_before, shared_before = get_process_memory()
start = time.time()
result = func(*args, **kwargs)
elapsed_time = elapsed_since(start)
rss_after, vms_after, shared_after = get_process_memory()
print("Profiling: {:>20} RSS: {:>8} | VMS: {:>8} | SHR {"
":>8} | time: {:>8}"
.format("<" + func.__name__ + ">",
format_bytes(rss_after - rss_before),
format_bytes(vms_after - vms_before),
format_bytes(shared_after - shared_before),
elapsed_time))
return result
if inspect.isfunction(func):
return wrapper
elif inspect.ismethod(func):
return wrapper(*args,**kwargs)
예: 위의 가 「」로 것을 전제로 하고 .profile.py
:
from profile import profile
from time import sleep
from sklearn import datasets # Just an example of 3rd party function call
# Method 1
run_profiling = profile(datasets.load_digits)
data = run_profiling()
# Method 2
@profile
def my_function():
# do some stuff
a_list = []
for i in range(1,100000):
a_list.append(i)
return a_list
res = my_function()
이것에 의해, 다음과 같은 출력이 됩니다.
Profiling: <load_digits> RSS: 5.07MB | VMS: 4.91MB | SHR 73.73kB | time: 89.99ms
Profiling: <my_function> RSS: 1.06MB | VMS: 1.35MB | SHR 0B | time: 8.43ms
몇 가지 중요한 최종 메모:
- 이 프로파일링 방법은 기계에서 다른 많은 일들이 발생할 수 있기 때문에 대략적인 수준에 불과합니다.가비지 수집 및 기타 요인으로 인해 델타는 0일 수도 있습니다.
- 알 수 없는 이유로 매우 짧은 함수 호출(1 또는 2ms 등)이 메모리 사용량이 0인 상태로 표시됩니다.이것은 메모리 통계 정보의 갱신 빈도에 관한 하드웨어/OS(Linux 탑재 기본 노트북에서 테스트)의 제한이라고 생각됩니다.
- 예를 간단히 하기 위해 함수 인수를 사용하지 않았지만 예상대로 작동해야 합니다.
profile(my_function, arg)
프로파일링하다my_function(arg)
함수 결과를 반환하면서 memory_profile을 사용하여 코드/함수 블록의 메모리 사용량을 계산하는 간단한 예:
import memory_profiler as mp
def fun(n):
tmp = []
for i in range(n):
tmp.extend(list(range(i*i)))
return "XXXXX"
코드를 실행하기 전에 메모리 사용량을 계산하고 코드 실행 중 최대 사용량을 계산합니다.
start_mem = mp.memory_usage(max_usage=True)
res = mp.memory_usage(proc=(fun, [100]), max_usage=True, retval=True)
print('start mem', start_mem)
print('max mem', res[0][0])
print('used mem', res[0][0]-start_mem)
print('fun output', res[1])
함수를 실행하는 동안 샘플링 포인트의 사용량을 계산합니다.
res = mp.memory_usage((fun, [100]), interval=.001, retval=True)
print('min mem', min(res[0]))
print('max mem', max(res[0]))
print('used mem', max(res[0])-min(res[0]))
print('fun output', res[1])
크레딧: @skept
도움이 될 수도 있습니다.
<추가 참조>
pip install gprof2dot
sudo apt-get install graphviz
gprof2dot -f pstats profile_for_func1_001 | dot -Tpng -o profile.png
def profileit(name):
"""
@profileit("profile_for_func1_001")
"""
def inner(func):
def wrapper(*args, **kwargs):
prof = cProfile.Profile()
retval = prof.runcall(func, *args, **kwargs)
# Note use of name from outer scope
prof.dump_stats(name)
return retval
return wrapper
return inner
@profileit("profile_for_func1_001")
def func1(...)
언급URL : https://stackoverflow.com/questions/552744/how-do-i-profile-memory-usage-in-python
'programing' 카테고리의 다른 글
MODIFY는 MariaDB에 다른 열 속성을 유지합니까? (0) | 2022.09.16 |
---|---|
다른 도커에서 도커 내의 MariaDB에 액세스 (0) | 2022.09.16 |
Rails 5 이행 데이터 유형의 문제 (0) | 2022.09.16 |
http를 사용하여 VUE의 CORS 문제를 해결하려면 어떻게 해야 합니까? (0) | 2022.09.16 |
JavaScript 물음표 및 콜론 (0) | 2022.09.16 |