【问题标题】:Why is numpy.power 60x slower than in-lining?为什么 numpy.power 比 in-lining 慢 60 倍?
【发布时间】:2014-10-04 22:42:47
【问题描述】:

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

x = numpy.power(x,3) 

慢大约 60 倍
x = x*x*x

各种阵列大小的加速图显示了阵列大小约为 10k 的最佳点,而其他大小的阵列速度一致地提高了 5-10 倍。

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

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()

有人解释一下吗?

【问题讨论】:

  • 附带说明,您真的,真的不应该使用time 作为计时码。 time 的文档甚至会告诉你这一点。在某些平台上,在某些情况下,它可能已经足够好了……但还是使用timeit 更好。
  • 一般来说,一个通用的幂方法,接受基数和指数的任意值(尤其是非整数)将需要更复杂的工作。然而,一个简单的x*x*x 可以很容易地使用操作码来表达,操作码本身可以很容易地翻译成实际的机器代码。
  • @MikeGraham:我不明白你的回答。 timeit 是您分析代码的方式;它实际上比time 更容易使用,而且更可能是正确的。 (如果你使用 IPython 和它的魔力 %timeit,更是如此。)
  • @MikeGraham:你是否回应了其他人的评论(后来被删除了)建议围绕power 展开小整数的包装函数,并且只输入我的名字而不是那个人的名字?如果是这样,那么是的,你可能是对的。
  • @abarnert,我是想回复 Newmu 对我的帖子的评论……我不知道我是怎么搞得这么混乱的。

标签: python arrays performance numpy


【解决方案1】:

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

即使np.power 足够聪明,可以单独进行整数幂运算,除非它将小值展开为重复乘法,否则它仍然不会那么快。

您可以通过比较整数到整数、整数到浮点数、浮点数到整数和浮点数到浮点幂的时间与小数组的乘法来轻松验证这一点; int-to-int 的速度大约是其他方法的 5 倍,但仍然比乘法慢 4 倍(尽管我使用 PyPy 和定制的 NumPy 进行了测试,所以对于在 CPython 上安装普通 NumPy 的人来说,给出真实结果可能会更好……)

【讨论】:

    【解决方案2】:

    众所周知,您的处理器可以以非常奇特的方式执行双精度数的乘法,速度非常非常快。 pow 明显变慢了。

    Some performance guides 甚至建议人们为此做好计划,甚至有时可能有点过分热心。

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

    【讨论】:

    • 感谢您的反馈,我对 numpy 的内部结构了解不多,但性能差距如此之大,(并解释为什么 numpy.square 存在)您认为添加它会是一件好事当第二个操作是整数或文档中的警告时,numpy.power 的案例?
    • @Newmu,可能是这样,尤其是因为 xxx 可能不是立方 x 的最快实现。话虽如此,如果有任何代码需要优化的话,这个操作可能不是瓶颈
    【解决方案3】:

    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')
    

    【讨论】:

    • naive_power 似乎有几个错误。
    • 另外,吹毛求疵,这是关于元素幂,而不是“矩阵幂”,这确实是一种不同的操作。
    • @Andrew:只是想传递timeit decorator you are using is unreliable 的警告。您需要使您正在计时的函数运行更长的时间,以便乘法所花费的时间支配被测量的时间,并抑制可能导致锯齿的 OS 进程调度侥幸之类的事情。 timeit 模块为您完成所有这些工作(以及关闭垃圾收集。)这里是 a version of your code 使用 timeit.timeit
    猜你喜欢
    • 2015-02-20
    • 1970-01-01
    • 2015-01-02
    • 2018-01-16
    • 2015-04-18
    • 2015-08-03
    • 2021-01-15
    • 1970-01-01
    • 2012-10-07
    相关资源
    最近更新 更多