通过 Python 函数跟踪*最大*内存使用情况

Posted

技术标签:

【中文标题】通过 Python 函数跟踪*最大*内存使用情况【英文标题】:Tracking *maximum* memory usage by a Python function 【发布时间】:2012-04-08 17:01:22 【问题描述】:

我想知道在调用函数期间分配的最大 RAM 量是多少(在 Python 中)。还有其他关于跟踪 RAM 使用情况的问题:

Which Python memory profiler is recommended?

How do I profile memory usage in Python?

但这些似乎允许您在调用 heap() 方法(在 guppy 的情况下)时更多地跟踪内存使用情况。但是,我要跟踪的是我无法修改的外部库中的一个函数,它会增长到使用大量 RAM,但一旦函数执行完成就会释放它。有什么方法可以查出函数调用期间使用的 RAM 总量是多少?

【问题讨论】:

【参考方案1】:

这个问题似乎很有趣,它让我有理由研究 Guppy / Heapy,为此我感谢你。

我尝试了大约 2 个小时来让 Heapy 监视函数调用/进程,而不用 运气修改其源。

我确实找到了一种使用内置 Python 库 resource 完成任务的方法。请注意,文档并未说明 RU_MAXRSS 值返回的内容。另一个 SO 用户 noted 以 kB 为单位。运行 Mac OSX 7.3 并在下面的测试代码期间观察我的系统资源攀升,我相信返回的值以 Bytes 为单位,而不是 kBytes。

关于我如何使用resource 库来监视库调用的 10000 英尺视图是在单独的(可监视的)线程中启动函数并在主线程中跟踪该进程的系统资源。下面我有两个文件,你需要运行它们来测试它。

图书馆资源监视器 - whatever_you_want.py

import resource
import time

from stoppable_thread import StoppableThread


class MyLibrarySniffingClass(StoppableThread):
    def __init__(self, target_lib_call, arg1, arg2):
        super(MyLibrarySniffingClass, self).__init__()
        self.target_function = target_lib_call
        self.arg1 = arg1
        self.arg2 = arg2
        self.results = None

    def startup(self):
        # Overload the startup function
        print "Calling the Target Library Function..."

    def cleanup(self):
        # Overload the cleanup function
        print "Library Call Complete"

    def mainloop(self):
        # Start the library Call
        self.results = self.target_function(self.arg1, self.arg2)

        # Kill the thread when complete
        self.stop()

def SomeLongRunningLibraryCall(arg1, arg2):
    max_dict_entries = 2500
    delay_per_entry = .005

    some_large_dictionary = 
    dict_entry_count = 0

    while(1):
        time.sleep(delay_per_entry)
        dict_entry_count += 1
        some_large_dictionary[dict_entry_count]=range(10000)

        if len(some_large_dictionary) > max_dict_entries:
            break

    print arg1 + " " +  arg2
    return "Good Bye World"

if __name__ == "__main__":
    # Lib Testing Code
    mythread = MyLibrarySniffingClass(SomeLongRunningLibraryCall, "Hello", "World")
    mythread.start()

    start_mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
    delta_mem = 0
    max_memory = 0
    memory_usage_refresh = .005 # Seconds

    while(1):
        time.sleep(memory_usage_refresh)
        delta_mem = (resource.getrusage(resource.RUSAGE_SELF).ru_maxrss) - start_mem
        if delta_mem > max_memory:
            max_memory = delta_mem

        # Uncomment this line to see the memory usuage during run-time 
        # print "Memory Usage During Call: %d MB" % (delta_mem / 1000000.0)

        # Check to see if the library call is complete
        if mythread.isShutdown():
            print mythread.results
            break;

    print "\nMAX Memory Usage in MB: " + str(round(max_memory / 1000.0, 3))

可停止线程 - stoppable_thread.py

import threading
import time

class StoppableThread(threading.Thread):
    def __init__(self):
        super(StoppableThread, self).__init__()
        self.daemon = True
        self.__monitor = threading.Event()
        self.__monitor.set()
        self.__has_shutdown = False

    def run(self):
        '''Overloads the threading.Thread.run'''
        # Call the User's Startup functions
        self.startup()

        # Loop until the thread is stopped
        while self.isRunning():
            self.mainloop()

        # Clean up
        self.cleanup()

        # Flag to the outside world that the thread has exited
        # AND that the cleanup is complete
        self.__has_shutdown = True

    def stop(self):
        self.__monitor.clear()

    def isRunning(self):
        return self.__monitor.isSet()

    def isShutdown(self):
        return self.__has_shutdown


    ###############################
    ### User Defined Functions ####
    ###############################

    def mainloop(self):
        '''
        Expected to be overwritten in a subclass!!
        Note that Stoppable while(1) is handled in the built in "run".
        '''
        pass

    def startup(self):
        '''Expected to be overwritten in a subclass!!'''
        pass

    def cleanup(self):
        '''Expected to be overwritten in a subclass!!'''
        pass

【讨论】:

@astrofrog 很高兴为您提供帮助。我将来也可以从中受益。 我把这段代码作为一个要点,以便下载文件:gist.github.com/b54fafd87634f017d50d @Tom 你确定数据是 GB 吗?在 OSX Lion 上跟踪它使它看起来好像是 MB。不管怎样,谢谢你把它放在github上! @AdamLewis 我想我不是 100% 确定,但我得到的数字太小了(在 Linux 上),如果乘以 1000 则更合理。似乎与我在系统监视器。如果 getrusage 不考虑操作系统差异,那就太可惜了,但这个数字将用于在优化时进行相对比较,所以这没什么大不了的。 @Tom 我同意相对比较。我真的希望你能找到更好的文档:D。【参考方案2】:

使用memory_profiler 可以做到这一点。函数memory_usage 返回一个值列表,这些值表示一段时间内的内存使用情况(默认情况下超过 0.1 秒的块)。如果您需要最大值,只需取该列表中的最大值即可。小例子:

from memory_profiler import memory_usage
from time import sleep

def f():
    # a function that with growing
    # memory consumption
    a = [0] * 1000
    sleep(.1)
    b = a * 100
    sleep(.1)
    c = b * 100
    return a

mem_usage = memory_usage(f)
print('Memory usage (in chunks of .1 seconds): %s' % mem_usage)
print('Maximum memory usage: %s' % max(mem_usage))

在我的情况下(memory_profiler 0.25)如果打印以下输出:

Memory usage (in chunks of .1 seconds): [45.65625, 45.734375, 46.41015625, 53.734375]
Maximum memory usage: 53.734375

【讨论】:

出色的 Windows 解决方案。 pypi.python.org/pypi/memory_profiler 但还需要卸载 psutil 你使用的是什么系统@Wajahat? Windows 8,带有 Python 2.7 如果函数f有参数args和关键字参数kw,你可以用memory_usage((f, args, kw))调用它。【参考方案3】:

这似乎在 Windows 下工作。不知道其他操作系统。

In [50]: import os

In [51]: import psutil

In [52]: process = psutil.Process(os.getpid())

In [53]: process.get_ext_memory_info().peak_wset
Out[53]: 41934848

【讨论】:

我认为应该是memory_info_ex.peak_set 查看文档。 pythonhosted.org/psutil/#psutil.Process.memory_info 应该是process.memory_info().rss 跨平台 不,不应该。 .rss 报告 /current/ 驻留集大小,而不是进程生命周期内的最大 rss。不幸的是,最大 rss 似乎无法通过 psutil 用于其他平台的 windows,请参阅psutil.readthedocs.io/en/latest/#psutil.Process.memory_info 这个解决方案的问题是,如果在要监控的函数调用之前内存使用率较高,它只会报告更高的级别。【参考方案4】:

也一直在努力完成这项任务。在尝试了 Adam 的 psutil 和方法之后,我编写了一个函数(感谢 Adam Lewis)来测量特定函数使用的内存。人们可能会发现它更容易抓取和使用。

1)measure_memory_usage

2)test measure_memory_usage

我发现有关线程和覆盖超类的材料对于理解 Adam 在他的脚本中所做的工作非常有帮助。抱歉,由于我的“2 个链接”最大限制,我无法发布链接。

【讨论】:

【参考方案5】:

您可以使用 python 库资源来获取内存使用情况。

import resource
resource.getrusage(resource.RUSAGE_SELF).ru_maxrss

它将以千字节为单位给出内存使用量,以 MB 为单位除以 1000。

【讨论】:

我认为它返回字节而不是千字节 似乎 linux 提供千字节,但 osx 提供字节。我现在很困惑【参考方案6】:

标准 Unix 实用程序 time 跟踪进程的最大内存使用量以及您程序的其他有用统计信息。

示例输出(maxresident 是最大内存使用量,以千字节为单位。):

> time python ./scalabilty_test.py
45.31user 1.86system 0:47.23elapsed 99%CPU (0avgtext+0avgdata 369824maxresident)k
0inputs+100208outputs (0major+99494minor)pagefaults 0swaps

【讨论】:

对我来说,它是用 $ /usr/bin/time --verbose ./myscript.py 唤醒的 在suse中只写time -v ..是不行的,需要/usr/bin/time -v。 time 是内置的 bash,因此如果运行 bash(或其他智能 shell),则需要运行 command time 来告诉 shell 运行程序而不是功能较少的内置【参考方案7】:

改进@Vader B 的答案(因为它对我来说开箱即用):

$ /usr/bin/time --verbose  ./myscript.py
        Command being timed: "./myscript.py"
        User time (seconds): 16.78
        System time (seconds): 2.74
        Percent of CPU this job got: 117%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:16.58
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 616092   # WE NEED THIS!!!
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 0
        Minor (reclaiming a frame) page faults: 432750
        Voluntary context switches: 1075
        Involuntary context switches: 118503
        Swaps: 0
        File system inputs: 0
        File system outputs: 800
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 0
        Page size (bytes): 4096
        Exit status: 0

【讨论】:

【参考方案8】:

在linux系统上读取free的信息来源,/proc/meminfo

~ head /proc/meminfo
MemTotal:        4039168 kB
MemFree:         2567392 kB
MemAvailable:    3169436 kB
Buffers:           81756 kB
Cached:           712808 kB
SwapCached:            0 kB
Active:           835276 kB
Inactive:         457436 kB
Active(anon):     499080 kB
Inactive(anon):    17968 kB

我创建了一个装饰器类来测量函数的内存消耗。

class memoryit:

    def FreeMemory():
        with open('/proc/meminfo') as file:
            for line in file:
                if 'MemFree' in line:
                    free_memKB = line.split()[1]
                    return (float(free_memKB)/(1024*1024))    # returns GBytes float

    def __init__(self, function):    # Decorator class to print the memory consumption of a 
        self.function = function     # function/method after calling it a number of iterations

    def __call__(self, *args, iterations = 1, **kwargs):
        before = memoryit.FreeMemory()
        for i in range (iterations):
            result = self.function(*args, **kwargs)
        after = memoryit.FreeMemory()
        print ('%r memory used: %2.3f GB' % (self.function.__name__, (before - after) / iterations))
        return result

测量消耗的函数:

@memoryit
def MakeMatrix (dim):
    matrix = []   
    for i in range (dim):
        matrix.append([j for j in range (dim)])
    return (matrix)

用法:

print ("Starting memory:", memoryit.FreeMemory()) 
m = MakeMatrix(10000)    
print ("Ending memory:", memoryit.FreeMemory() )

打印输出:

Starting memory: 10.58599853515625
'MakeMatrix' memory used: 3.741 GB
Ending memory: 6.864116668701172

【讨论】:

以上是关于通过 Python 函数跟踪*最大*内存使用情况的主要内容,如果未能解决你的问题,请参考以下文章

在 PHP 中跟踪内存使用情况

跟踪 C++ 内存分配

跟踪 java profiler 中的最大内存使用量

Python全栈开发--递归函数

JVM中的本机内存跟踪

是否可以通过单个查询跟踪 DynamoDB 表中的最小值/最大值?