我读过很多“避免与numpy循环”。所以,我试过了。我用的是这个代码(简化版)。一些辅助数据:
In[1]: import numpy as np
resolution = 1000 # this parameter varies
tim = np.linspace(-np.pi, np.pi, resolution)
prec = np.arange(1, resolution + 1)
prec = 2 * prec - 1
values = np.zeros_like(tim)
我的第一个实现是使用for
循环:
然后,我摆脱了显式的for
循环,实现了这一点:
In[3]: values = np.sum(np.sin(tim[:, np.newaxis] * prec), axis=1)
这种解决方案对于小型阵列更快,但当我扩大规模时,我得到了时间依赖性:
我错过了什么还是正常行为?如果不是,去哪里挖?在
编辑:根据评论,以下是一些附加信息。用IPython的%timeit
和{
这是正常和预期的行为。在任何地方都应用“avoid for loops with numpy”语句太简单了。如果你处理的是内部循环(几乎)总是正确的。但是在外循环的情况下(就像你的例子一样),有更多的例外。尤其是如果另一种选择是使用广播,因为这会通过使用更多的内存来加快操作速度。在
为了给避免for loops with numpy“语句添加一点背景:
NumPy数组存储为具有c类型的连续数组。Python
int
与Cint
不同!因此,每当迭代数组中的每个项时,都需要将该项从数组中插入,将其转换为Pythonint
,然后对其执行任何操作,最后可能需要再次将其转换为c整数(称为对值进行装箱和取消装箱)。例如,您希望使用Pythonsum
数组中的项:你最好用numpy:
^{pr2}$即使将循环推到Python C代码中,也离numpy性能相差甚远:
这条规则可能会有例外,但这些例外会非常稀少。至少只要有一些等效的numpy功能。因此,如果您要迭代单个元素,那么应该使用numpy。在
有时候一个简单的python循环就足够了。虽然没有广为人知,但是numpy函数与Python函数相比有着巨大的开销。例如,考虑一个3元素数组:
哪个更快?在
解决方案:Python函数的性能优于numpy解决方案:
但这和你的例子有什么关系?事实上并不是很多,因为您总是在数组上使用numpy函数(不是单个元素,甚至不是很少的元素),所以您的内部循环已经使用了优化的函数。这就是为什么两者的性能大致相同(+/-只有很少元素的因子10到大约500个元素的因子2)。但这不是真正的循环开销,而是函数调用开销!在
你的循环解决方案
使用line-profiler和
resolution = 100
:95%都花在了循环内部,我甚至把循环体分成几个部分来验证这一点:
这里的时间消耗者是
np.multiply
,np.sin
,np.sum
,通过比较他们的每次通话时间和他们的开销,可以很容易地进行检查:因此,一旦可计算函数调用开销与计算运行时相比很小,您将拥有类似的运行时。即使有100件物品,你的开销也很接近。诀窍是知道他们在哪一点上收支平衡。对于1000个项目,呼叫开销仍然很大:
但是使用
resolution = 5000
时,与运行时相比,开销相当低:当你在每个
np.sin
电话中花费500美元时,你不再关心20美元的开销了。在需要注意的是:
line_profiler
每行可能包含一些额外的开销,也可能是每个函数调用的开销,因此函数调用开销变为可忽略的点可能会更低!!!在你的广播解决方案
我从分析第一个解决方案开始,让我们对第二个解决方案进行相同的分析:
再次使用带有
resolution=100
的line_profiler:这已经大大超过了开销时间,因此我们比循环快了10倍。在
我还对
resolution=1000
进行了分析:使用
precision=5000
:1000大小仍然更快,但是正如我们看到的,在循环解决方案中,呼叫开销仍然是不可忽视的。但是对于
resolution = 5000
来说,每一步所花费的时间几乎是相同的(有些慢一些,有些更快,但总体上非常相似)另一个影响是>广播当你做乘法运算时,它变得有意义。即使有非常聪明的纽比解决方案,这仍然包括一些额外的计算。对于
resolution=10000
您可以看到广播乘法相对于循环解决方案开始占用更多的“%time”:但除了实际花费的时间之外,还有一件事:内存消耗。循环解决方案需要
O(n)
内存,因为您总是处理n
元素。然而,广播解决方案需要O(n*n)
内存。如果在循环中使用resolution=20000
,则可能需要等待一段时间,但它仍然只需要8bytes/element * 20000 element ~= 160kB
,但对于广播,则需要~3GB
。而这忽略了常数因子(比如临时数组又称中间数组)!如果你再往前走,你的记忆会很快用完的!在再次总结要点:
但优化最重要的一点仍然是:
只有当代码太慢时才进行优化!如果速度太慢,那么只有在分析代码之后才进行优化。
不要盲目地相信简化的语句,同样,永远不要在没有分析的情况下进行优化。
最后一个想法是:
如果numpy或scipy中没有现有的解决方案,则可以使用cython、numba或numexpr轻松实现此类需要循环或广播的函数。在
例如,numba函数将循环解决方案的内存效率与低
resolutions
的广播解决方案的速度结合起来,如下所示:正如评论中所指出的,
numexpr
还可以非常快速地评估广播计算,,而不需要内存O(n*n)
内存:相关问题 更多 >
编程相关推荐