programing

Python에서 메모리 사용량을 프로파일하려면 어떻게 해야 합니까?

yoursource 2022. 9. 16. 21:16
반응형

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()

resourcemodule을 사용하면 현재 메모리 사용량을 확인하고 피크 메모리 사용량에서 스냅샷을 저장할 수 있습니다.큐를 사용하면 메인스레드가 메모리모니터 스레드에 리포트를 인쇄하고 종료할 타이밍을 지시할 수 있습니다.때 알 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수, 수 등.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

몇 가지 중요한 최종 메모:

  1. 이 프로파일링 방법은 기계에서 다른 많은 일들이 발생할 수 있기 때문에 대략적인 수준에 불과합니다.가비지 수집 및 기타 요인으로 인해 델타는 0일 수도 있습니다.
  2. 알 수 없는 이유로 매우 짧은 함수 호출(1 또는 2ms 등)이 메모리 사용량이 0인 상태로 표시됩니다.이것은 메모리 통계 정보의 갱신 빈도에 관한 하드웨어/OS(Linux 탑재 기본 노트북에서 테스트)의 제한이라고 생각됩니다.
  3. 예를 간단히 하기 위해 함수 인수를 사용하지 않았지만 예상대로 작동해야 합니다.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

반응형