我只是在重新审视我的一些代码以改善性能,并对一些奇怪的事情感到困惑:
a = np.linspace(10,1000,1000000).reshape(1000,1000)
%timeit np.square(a)
100 loops, best of 3: 8.07 ms per loop
%timeit a*a
100 loops, best of 3: 8.18 ms per loop
%timeit a**2
100 loops, best of 3: 8.32 ms per loop
好吧,在使用power-operator(**
)时似乎有一些开销,但是看起来它们看起来完全相同(我猜NumPy正在这样做)但是后来很奇怪:
In [46]: %timeit np.power(a, 2)
10 loops, best of 3: 121 ms per loop
所以没有问题,但是对于魔法战队的后备而言似乎有点不一致,但对于UFUNC则没有。但后来我感兴趣,因为我经常使用第三种力量:
%timeit a*a*a
100 loops, best of 3: 18.1 ms per loop
%timeit a**3
10 loops, best of 3: 121 ms per loop
%timeit np.power(a, 3)
10 loops, best of 3: 121 ms per loop
在第三种力量中似乎没有“捷径”,UFUNC和'magic-pow'的工作方式相同(至少在性能方面)。
但是那不是那么好,因为我想要在我的代码中使用幂的一致方法,而我不太确定如何包装numpy的__pow__
。
有没有办法可以包含numpys __pow__
方法?因为我想在我的脚本中写一致的写作方式,而不是写a**2
和另一个地方power(a, 3)
。简单地写a**3
,并将其重定向到我的幂函数,将是首选(但为此我需要以某种方式包装ndarrays __pow__
或?)。
目前我正在使用一个快捷方式但不是那么漂亮(我甚至必须声明exponent == 2 case,因为np.power
在那里执行不是最优的):
def power(array, exponent):
if exponent == 2: #catch this, or it calls the slow np.power(array, exponent)
return np.square(array)
if exponent == 3:
return array * array * array
#As soon as np.cbrt is avaiable catch the exponent 4/3 here too
return np.power(array, exponent)
%timeit power(a, 3)
100 loops, best of 3: 17.8 ms per loop
%timeit a**3
10 loops, best of 3: 121 ms per loop
我正在使用NumPy v1.9.3,我不想仅仅为np.ndarray
方法包装__pow__
。 : - )
编辑:我重写了我的问题部分。澄清一下:我不是在问NumPy为什么这样做 - 这只是为了解释我为什么提出这个问题。
答案 0 :(得分:3)
这是一个很好的捕捉。我也很想知道为什么会这样。但要简短而简洁地回答这个问题,我会这样做:
def mypower(array, exponent):
return reduce(lambda x,y: x*y, [array for _ in range(exponent)])
%timeit mypower(a,2)
100 loops, best of 3: 3.68 ms per loop
%timeit mypower(a,3)
100 loops, best of 3: 8.09 ms per loop
%timeit mypower(a,4)
100 loops, best of 3: 12.6 ms per loop
很明显,开销随着指数的增加而增加,但是对于低指数来说,优于10倍的时间。
注意这与原始的numpy实现不同,后者不是特定于数字指数并且支持指数数组作为第二个参数(check it out here)。
重载运算符
执行所需操作的方法是子类化ndarray并使用视图。请参阅以下示例:
import numexpr
import numpy as np
class MyArray(np.ndarray):
def __pow__(self, other):
return reduce(lambda x,y: x*y, [self for _ in range(other)])
class NumExprArray(np.ndarray):
def __pow__(self, other):
return numexpr.evaluate("self**%f" % other)
#This implies extra overhead, is as much as 4x slower:
#return numexpr.evaluate("self**other")
a = np.linspace(10,1000,1000000).reshape(1000,1000).view(MyArray)
na = np.linspace(10,1000,1000000).reshape(1000,1000).view(NumExprArray)
%timeit a**2
1000 loops, best of 3: 1.2 ms per loop
%timeit na**2
1000 loops, best of 3: 1.14 ms per loop
%timeit a**3
100 loops, best of 3: 4.69 ms per loop
%timeit na**3
100 loops, best of 3: 2.36 ms per loop
%timeit a**4
100 loops, best of 3: 6.59 ms per loop
%timeit na**4
100 loops, best of 3: 2.4 ms per loop
有关此方法的详细信息,请按此link进行操作。另一种方法是使用custom infix operator,但出于可读性目的不太好。可以看出,numexpr应该是最佳选择。
答案 1 :(得分:3)
如果我正确读取source,当numpy
执行电源时,它会检查指数的数值是否是特殊情况之一(-0.5,0,0.5,1和2) )。如果是,则使用特殊例程完成操作。指数的所有其他数值都被认为是“通用”,并且将被输入到通用幂函数中,这可能很慢(特别是如果指数被提升为浮点类型,但我不确定这是否是案例a ** 3
)。