如何在循环中使用参数分析方法?
Posted
技术标签:
【中文标题】如何在循环中使用参数分析方法?【英文标题】:How to profile a method with arguments in a loop? 【发布时间】:2020-10-09 12:41:10 【问题描述】:我有一个类Foo
,它实现了一个方法compute
。我想看看compute
需要多长时间以及执行时大部分时间花在哪里。为了解决第一个问题,我在循环中使用了timeit.default_timer
并取平均值:
import numpy as np
from timeit import default_timer as Timer
foo = Foo()
n = int(10e8)
times = np.empty(n)
for i in range(n):
start = Timer()
foo.compute(i)
times[i] = Timer() - start
print(np.sum(times) / times.size)
这告诉我每个compute
调用的平均执行时间是多少,很简单;除了它比我想象的要慢。
现在我想分解compute
的执行配置文件,但以下似乎并不能解决问题:
import cProfile
for i in range(n):
cProfile.run(foo.compute(i))
我认为它无论如何都不会起作用,但文档似乎建议我必须将此循环放入一个方法中,然后分析该循环?所以我尝试了,但它没有显示compute
内部发生的事情,这是我想要的。这如何实现?
【问题讨论】:
是否需要为每次迭代获取cProfile
输出?
@tania 我不需要每次迭代都输出统计信息。我想在循环耗尽后输出总统计数据。 compute
里面有几个函数,所以我想确定在“现实世界”场景中哪个函数耗时最长。
您是否有理由要分析内部循环,而不是分析整个循环?分析依赖于同一执行的几个样本;制作多个配置文件,每个配置文件只有一个配置文件似乎适得其反。
@MisterMiyagi 我对配置文件的理解可能不正确,但我想分析在循环内运行的函数,而不是循环本身;也就是说,我不想计算每次迭代的执行速度有多快,而是计算每次迭代期间执行的内容。如果每次迭代需要 10 秒,我想知道该迭代的哪些部分构成了 10 秒,以便我可以优化。
@madeslurpy 档案记录了其中发生的一切。因此,循环的概况不仅仅是整个循环,它也是关于其中所有内容的统计数据。除非foo.compute
的行为因输入而大相径庭,否则分析循环并仅忽略最外层应该是合适的。
【参考方案1】:
您可以使用runctx()
,而不是使用run()
,它提供参数以提供全局和本地字典。
cProfile.runctx("foo.compute(n)", "n": n, "foo": Foo(), )
要汇总多个配置文件运行的结果,请参阅How to calculate the average result of several cProfile results?。
【讨论】:
【参考方案2】:要使用cProfile
,您需要将函数作为字符串参数或“命令”传递。因此,只需修改原始代码即可:
for i in range(n):
cProfile.run("foo.compute(i)")
但是,在您的案例中,您似乎想要分析“真实世界”案例的执行情况,并查看您可以优化的瓶颈所在。分析函数与对其进行基准测试不同。分析器上的Python documentation 还指出:
注意分析器模块旨在为给定程序提供执行配置文件,而不是用于基准测试目的(为此,存在合理准确结果的 timeit)。
在进行基准测试时,您希望在一个循环中多次执行命令,以获得平均执行时间 +- 标准差,这应该代表现实情况,并允许您比较不同的解决方案。但是,分析会分解命令的单次运行。这应该足以确定该方法的哪些特定组件比其他组件花费更多时间(与 它们实际花费的时间相比),并且这个数量级不应该在运行之间真正改变(尽管特定的所需时间可能会波动)。
如果您确实想计算平均运行的统计数据,您可以生成多次运行的统计数据,然后提取数据以进一步操作:
pr = cProfile.Profile()
pr.enable()
for i in range(n):
foo.compute(i)
pr.disable()
pr.create_stats()
stats = pr.stats
(pr.stats
是一个字典,它被格式化为您在分析时看到的打印语句)
【讨论】:
以上是关于如何在循环中使用参数分析方法?的主要内容,如果未能解决你的问题,请参考以下文章
Python 进阶之源码分析:如何将一个类方法变为多个方法?
Python 进阶之源码分析:如何将一个类方法变为多个方法?
JavaScript 循环中调用异步函数的三种方法,及为什么 forEach 无法工作的分析