如何在Python中分析内存使用情况?

如何在Python中分析内存使用情况?,python,memory,profiling,Python,Memory,Profiling,我最近对算法产生了兴趣,并开始通过编写一个简单的实现,然后以各种方式对其进行优化来探索算法 我已经熟悉用于评测运行时的标准Python模块(对于大多数情况,我发现IPython中的timeit magic函数已经足够了),但我也对内存使用感兴趣,因此我可以探索这些折衷(例如,缓存以前计算的值的表与根据需要重新计算它们的成本)。是否有一个模块可以为我分析给定函数的内存使用情况?这里已经回答了这个问题: 基本上你是这样做的(引自: 来自guppy进口hpy的>;h=hpy() >>>h.堆() 一组

我最近对算法产生了兴趣,并开始通过编写一个简单的实现,然后以各种方式对其进行优化来探索算法


我已经熟悉用于评测运行时的标准Python模块(对于大多数情况,我发现IPython中的timeit magic函数已经足够了),但我也对内存使用感兴趣,因此我可以探索这些折衷(例如,缓存以前计算的值的表与根据需要重新计算它们的成本)。是否有一个模块可以为我分析给定函数的内存使用情况?

这里已经回答了这个问题:

基本上你是这样做的(引自:

来自guppy进口hpy的
>;h=hpy()
>>>h.堆()
一组48477个对象的分区。总大小=3265516字节。
索引计数%Size%累计%Kind(类/类的目录)
0 25773 53 1612820 49 1612820 49街
116992448396015209678064元组
2 174 0 241584 7 2338364 72模块目录
3 3478 7 222592 7 2560956 78类型。代码类型
4329671845766274553284功能
5 401 1 175112 5 2920644 89课堂记录
6 108 0 81888 3 3002532 92 dict(无所有者)
7 114 0 79632 2 3082164 94类型目录
81170513362313350096型
9 667 1 24012 1 3157512 97内置包装描述符
>>>h.iso(1,[],{})
一组3个对象的分区。总大小=176字节。
索引计数%Size%累计%Kind(类/类的目录)
01 33 136 77 136 77 dict(无所有者)
1 1 33 28 16 164 93名单
2 1 33 12 7 176 100国际单位
>>>x=[]
>>>h.iso(x).sp
0:h.Root.i0_模块['''u____'].__指令['x']
>>> 

披露:

  • 仅适用于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 )
只需使用(“标签”)将
插入您想要查看的地方。比如说

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

如果只想查看对象的内存使用情况,()

有一个名为的模块,其中包含
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

Python 3.4包含一个新模块:。它提供了关于哪个代码分配的内存最多的详细统计信息。下面的示例显示了分配内存的前三行

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)
什么时候内存泄漏不是泄漏? 当计算结束时内存仍保留在内存中时,该示例非常好,但有时您的代码会分配大量内存,然后将其全部释放。从技术上讲,这不是内存泄漏,但它使用的内存比您认为的要多。当全部释放时,如何跟踪内存使用情况?如果它是您的代码,您可能可以添加一些调试代码,以便在它运行时拍摄快照。如果没有,则可以启动后台线程,在主线程运行时监视内存使用情况

下面是上一个示例,其中代码已全部移动到
count\u 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()
现在有一个版本的灵感来自于它启动了第二个线程来监视内存使用情况

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()
如果您在Linux上,您可能会发现比
资源
模块更有用。

可能有帮助:


下面是一个简单的函数装饰器,它允许跟踪进程在函数调用之前、函数调用之后消耗的内存量以及区别:

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、VM和共享内存。我不太关心“before”和“after”的值,只关心delta,所以我删除了它们(如果你与ihorb.的答案进行比较的话)

分析代码 这将产生与以下类似的输出:

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
评测:RSS:5.07MB | VMS:4.91MB | SHR 73.73kB |时间:89.99ms
评测:RSS:1.06MB | VMS:1.35MB | SHR 0B |时间:8.43ms
最后的几个重要注意事项:
  • 请记住,这种分析方法只是一种近似方法,因为机器上可能会发生很多其他事情。由于垃圾收集和其他因素,增量甚至可能为零
  • 由于未知原因,非常短的函数调用(例如1或2毫秒) 显示为零内存使用率。我怀疑这是某种限制 硬件/操作系统(在使用Linux的基本笔记本电脑上测试)的频率 将更新内存统计信息
  • 为了保持示例的简单性,我没有使用任何函数参数,但它们应该按照预期工作,即。
    profile(my\u函数,arg)
    到profile
    my\u函数(arg)
  • 使用memory_profile计算代码块/函数的内存使用率,同时返回函数结果的简单示例:

    计算
    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()
    
    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
    
    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(...)
    
    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
    
    # 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)
    
    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
    
    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])