如何在循环中使用参数分析方法?

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 无法工作的分析

JavaScript 循环中调用异步函数的三种方法,及为什么 forEach 无法工作的分析

进行循环分析的最佳方法