我最近对算法感兴趣,并开始通过编写一个简单的实现,然后以各种方式优化它来探索它们.
我已经熟悉用于分析运行时的标准Python模块(对于大多数事情我已经发现IPython中的timeit魔术功能已足够),但我也对内存使用感兴趣,所以我也可以探索这些权衡(例如,缓存先前计算的值表的成本与根据需要重新计算它们的成本.是否有一个模块可以为我分析给定函数的内存使用情况?
这个已经在这里得到了解答: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'] >>>
Python 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
现在,这是一个受另一个答案启发的版本,它启动了第二个线程来监控内存使用情况.
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
如果你在Linux上,你会发现/proc/self/statm
比resource
模块更有用.
如果你只想查看一个对象的内存使用情况,(回答其他问题)
有一个名为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.
对于一个非常简单的方法尝试:
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]*resource.getpagesize())/1000000.0 )
只需插入using("Label")
您想要查看正在发生的事情的位置即可.
我认为既然已接受的答案以及投票数第二高的答案都存在一些问题,所以我想再提供一个基于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:RSS: 5.07MB | VMS: 4.91MB | SHR 73.73kB | time: 89.99ms Profiling: RSS: 1.06MB | VMS: 1.35MB | SHR 0B | time: 8.43ms
请记住,这种分析方法仅是近似的,因为计算机上可能会发生很多其他事情。由于垃圾收集和其他因素,增量甚至可能为零。
由于某些未知的原因,出现非常短的函数调用(例如1或2 ms),而内存使用量为零。我怀疑这是硬件/操作系统(在装有Linux的基本笔记本电脑上测试过)在内存统计信息更新频率方面的一些限制。
为了使示例简单,我没有使用任何函数参数,但是它们应该像预期的那样工作,即
profile(my_function, arg)
进行概要分析my_function(arg)
下面是一个简单的函数装饰器,它可以跟踪函数调用之前,函数调用之后进程消耗的内存量以及它们之间的区别:
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()) return process.get_memory_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
这是我的博客,描述了所有详细信息。(已归档的链接)