为什么numpy.power比内衬慢60倍?

时间:2014-08-12 00:40:36

标签: python arrays performance numpy

也许我做了一些奇怪的事情,但是在使用numpy时可能会发现令人惊讶的性能损失,无论使用的功率如何都显得一致。例如,当x是随机的100x100阵列

x = numpy.power(x,3) 

慢约60倍
x = x*x*x

各种阵列尺寸的加速图显示了一个最佳尺寸10k左右的阵列和其他尺寸的5-10倍速度。

enter image description here

在自己的机器上测试下面的代码(有点乱):

import numpy as np
from matplotlib import pyplot as plt
from time import time

ratios = []
sizes = []
for n in np.logspace(1,3,20).astype(int):
    a = np.random.randn(n,n)

    inline_times = []
    for i in range(100):
        t = time()
        b = a*a*a
        inline_times.append(time()-t)
    inline_time = np.mean(inline_times)

    pow_times = []
    for i in range(100):
        t = time()
        b = np.power(a,3)
        pow_times.append(time()-t)
    pow_time = np.mean(pow_times)

    sizes.append(a.size)
    ratios.append(pow_time/inline_time)

plt.plot(sizes,ratios)
plt.title('Performance of inline vs numpy.power')
plt.ylabel('Nx speed-up using inline')
plt.xlabel('Array size')
plt.xscale('log')
plt.show()

有人有解释吗?

3 个答案:

答案 0 :(得分:18)

众所周知,处理器可以以非常快速的方式进行的双倍乘法非常非常快。 pow明显变慢了。

Some performance guides甚至建议人们为此做好计划,甚至可能在某些方面有时可能有点过于热心。

numpy特殊情况平直以确保它不会太慢,但它会立即将立方体发送到您的libc的pow,这几乎不是那么快作为几个乘法。

答案 1 :(得分:5)

我怀疑问题是np.power总是浮点运算,并且它不知道如何在你的平台(或者可能是大多数/所有平台)上优化或矢量化,而乘法很容易投入SSE,即使你不这样做也很快。

即使np.power足够智能分别进行整数取幂,除非将小值展开到重复乘法中,否则它仍然不会那么快。

通过比较int-to-int,int-to-float,float-to-int和float-to-float功率与小数组乘法的时间,你可以很容易地验证这一点。 int-to-int的速度是其他的5倍 - 但仍然比乘法慢4倍(尽管我使用PyPy测试了自定义的NumPy,所以对于那些在CPython上安装了正常NumPy的人来说,它可能会更好地给出真正的结果......)

答案 2 :(得分:5)

numpys幂函数的性能与指数非常非线性地成比例。用天真的方法来做到这一点。无论矩阵大小如何,都应存在相同类型的缩放。基本上,除非指数足够大,否则你不会看到任何实际的好处。

import matplotlib.pyplot as plt
import numpy as np
import functools
import time

def timeit(func):
    @functools.wraps(func)
    def newfunc(*args, **kwargs):
        startTime = time.time()
        res = func(*args, **kwargs)
        elapsedTime = time.time() - startTime
        return (res, elapsedTime)
    return newfunc

@timeit
def naive_power(m, n):
    m = np.asarray(m)
    res = m.copy()
    for i in xrange(1,n):
        res *= m
    return res

@timeit
def fast_power(m, n):
    # elementwise power
    return np.power(m, n)

m = np.random.random((100,100))
n = 400

rs1 = []
ts1 = []
ts2 = []
for i in xrange(1, n):
    r1, t1 = naive_power(m, i)
    ts1.append(t1)

for i in xrange(1, n):
    r2, t2 = fast_power(m, i)
    ts2.append(t2)

plt.plot(ts1, label='naive')
plt.plot(ts2, label='numpy')
plt.xlabel('exponent')
plt.ylabel('time')
plt.legend(loc='upper left')

performance plot