Python : 在分析内存时增加递归函数调用
Python : Increasing recursion function call while profiling memory
我正在尝试研究递归函数。我正在使用 Colaboratory 运行 我的代码。
这是我的问题:为什么使用内存分析器时函数调用的数量会增加?我用一个全局变量来统计这个函数被调用了多少次。
这是我没有分析器的原始代码:
# imports and other things...
accm = 0
def sum_num(n):
global accm
accm += 1
if n == 1:
return 1
return n + sum_num(n - 1)
call_list = []
for i in range(0, 6000, 100):
accm = 0
if i == 0:
i = 1
sum_num(i)
call_list.append(accm)
# visualization things...
并将递归限制设置为 10000。
图形可视化 accm
从 1 到 6000 是线性的:
我预计这在我使用探查器时不会改变,但它确实改变了。
这是配置文件版本:
# imports and other things...
from memory_profiler import memory_usage
accm = 0
def sum_num(n):
global accm
accm += 1
if n == 1:
return 1
return n + sum_num(n - 1)
call_list = []
for i in range(0, 6000, 100):
accm = 0
if i == 0:
i = 1
_ = max(memory_usage((sum_num, (i,))))
call_list.append(accm)
# visualization things...
而我得到的是一个从1到16000左右的非线性图;它的最大值约为 20000,它随噪声线性增加,直到 x = 25 和 y = 16000,减小,然后再次随噪声线性增加:
我想知道为什么会发生这种情况,以及如何解决它。
您正在使用的探查器将 运行 您传递给 memory_usage
的函数多次,因为它会尝试确定内存采样间隔,使其获得足够的样本以获得准确的读数.如果您的代码 运行 速度太快,则需要进一步缩小样本量,因此该函数将被更频繁地调用。
您可以在 source code of memory_profiler
中看到它是如何工作的。这是重要的循环(删除了不相关的内容):
while True:
child_conn, parent_conn = Pipe() # this will store MemTimer's results
p = MemTimer(os.getpid(), interval, child_conn, backend,
timestamps=timestamps,
max_usage=max_usage,
include_children=include_children)
#...
returned = f(*args, **kw)
#...
n_measurements = parent_conn.recv()
#...
if n_measurements > 4 or interval < 1e-6:
break
interval /= 10.
我对你的输出图的解释是,当你的函数被小输入调用时,interval
需要 sh运行k 三次,然后分析器才有足够的分辨率来测量内存使用准确,因此递归函数的调用次数是正常水平的四倍。迭代 22 次左右后,间隔只需要 sh运行k 两次,因此图形值变为正常水平的三倍。尽管异常快或慢的代码 运行 导致需要完成异常数量的间隔更改,但也有一些噪音。这就是为什么您会看到一些需要较少调整大小的谷值,以及一个发生额外调整大小的峰值。
如果将适当的 interval
参数传递给 memory_usage
,您或许可以避免调整大小。默认值为 0.1
,因此您可以通过将 0.0001
作为初始值传递来避免所有调整大小(但考虑进行试验,您可能能够执行 0.0005
或更多):memory_usage((sum_num, (i,)), 1e-4)
我正在尝试研究递归函数。我正在使用 Colaboratory 运行 我的代码。
这是我的问题:为什么使用内存分析器时函数调用的数量会增加?我用一个全局变量来统计这个函数被调用了多少次。
这是我没有分析器的原始代码:
# imports and other things...
accm = 0
def sum_num(n):
global accm
accm += 1
if n == 1:
return 1
return n + sum_num(n - 1)
call_list = []
for i in range(0, 6000, 100):
accm = 0
if i == 0:
i = 1
sum_num(i)
call_list.append(accm)
# visualization things...
并将递归限制设置为 10000。
图形可视化 accm
从 1 到 6000 是线性的:
我预计这在我使用探查器时不会改变,但它确实改变了。
这是配置文件版本:
# imports and other things...
from memory_profiler import memory_usage
accm = 0
def sum_num(n):
global accm
accm += 1
if n == 1:
return 1
return n + sum_num(n - 1)
call_list = []
for i in range(0, 6000, 100):
accm = 0
if i == 0:
i = 1
_ = max(memory_usage((sum_num, (i,))))
call_list.append(accm)
# visualization things...
而我得到的是一个从1到16000左右的非线性图;它的最大值约为 20000,它随噪声线性增加,直到 x = 25 和 y = 16000,减小,然后再次随噪声线性增加:
我想知道为什么会发生这种情况,以及如何解决它。
您正在使用的探查器将 运行 您传递给 memory_usage
的函数多次,因为它会尝试确定内存采样间隔,使其获得足够的样本以获得准确的读数.如果您的代码 运行 速度太快,则需要进一步缩小样本量,因此该函数将被更频繁地调用。
您可以在 source code of memory_profiler
中看到它是如何工作的。这是重要的循环(删除了不相关的内容):
while True:
child_conn, parent_conn = Pipe() # this will store MemTimer's results
p = MemTimer(os.getpid(), interval, child_conn, backend,
timestamps=timestamps,
max_usage=max_usage,
include_children=include_children)
#...
returned = f(*args, **kw)
#...
n_measurements = parent_conn.recv()
#...
if n_measurements > 4 or interval < 1e-6:
break
interval /= 10.
我对你的输出图的解释是,当你的函数被小输入调用时,interval
需要 sh运行k 三次,然后分析器才有足够的分辨率来测量内存使用准确,因此递归函数的调用次数是正常水平的四倍。迭代 22 次左右后,间隔只需要 sh运行k 两次,因此图形值变为正常水平的三倍。尽管异常快或慢的代码 运行 导致需要完成异常数量的间隔更改,但也有一些噪音。这就是为什么您会看到一些需要较少调整大小的谷值,以及一个发生额外调整大小的峰值。
如果将适当的 interval
参数传递给 memory_usage
,您或许可以避免调整大小。默认值为 0.1
,因此您可以通过将 0.0001
作为初始值传递来避免所有调整大小(但考虑进行试验,您可能能够执行 0.0005
或更多):memory_usage((sum_num, (i,)), 1e-4)