【问题标题】:Are element-wise operations faster with NumPy functions than operators?NumPy 函数的元素操作是否比运算符更快?
【发布时间】:2014-11-07 03:51:00
【问题描述】:

我最近遇到了great SO post,其中用户建议numpy.sum 在处理 NumPy 数组时比 Python 的 sum 更快。

这让我想到,使用 NumPy 函数对 NumPy 数组进行元素操作是否比运算符更快?如果是这样,那为什么会这样呢?

考虑以下示例。

import numpy as np
a = np.random.random(1e10)
b = np.random.random(1e10)

np.subtract(a, b) 会可靠地比 a - b 快吗?

【问题讨论】:

    标签: python arrays performance numpy element


    【解决方案1】:

    不,不是很重要。

    np.sumsum 快的原因是 sum 被实现为“天真地”迭代可迭代对象(在本例中为 numpy 数组),调用元素的 __add__ 运算符(它强加了一个显着的开销),而 numpy 的 sum 实现已优化,例如利用它知道元素的类型 (dtype) 并且它们在内存中是连续的这一事实。

    np.subtract(arr1, arr2)arr1-arr2 不是这种情况。后者大致翻译为前者。

    不同之处在于可以覆盖 python 中的减法运算符,因此 numpy 数组会覆盖它以使用优化版本。但是,sum 操作是不可覆盖的,因此 numpy 提供了它的替代优化版本。

    【讨论】:

      【解决方案2】:

      不是真的。不过你可以很容易地检查时间。

      a = np.random.normal(size=1000)
      b = np.random.normal(size=1000)
      
      %timeit np.subtract(a, b)
      # 1000000 loops, best of 3: 1.57 µs per loop
      
      %timeit a - b
      # 1000000 loops, best of 3: 1.47 µs per loop
      
      %timeit np.divide(a, b)
      # 100000 loops, best of 3: 3.51 µs per loop
      
      %timeit a / b
      # 100000 loops, best of 3: 3.38 µs per loop
      

      numpy 函数实际上似乎有点慢。我不确定这是否重要,但我怀疑这可能是因为在同一实现之上有一些额外的函数调用开销。

      编辑:正如@unutbu 所说,这可能是因为np.add 和朋友有额外的类型检查开销来在必要时将类似数组的元素转换为数组,所以像np.add([1, 2], [3, 4]) 这样的东西可以工作。

      【讨论】:

      • np.subtract 有额外的代码将其参数转换为数组。因此np.subtract([1,2,3], [4,5,6]) 有效。 a-b 不需要这个额外的代码,所以它要快一点。 np.subtract 还处理 out 关键字参数...
      • 好点,@unutbu。 np.subtract 的这两个附加功能都是在函数的进入/退出时的一次性问题。如果您不使用它们,它们是O(1),因此随着数组越来越大,它们的开销将变得越来越微不足道。
      【解决方案3】:

      @shx2 的回答很好。

      我将稍微扩展一下sumnp.sum

      • 内置 sum 将遍历一个数组,逐一获取元素并将它们分别转换为 Python 对象,然后将它们作为 Python 对象添加到一起。
      • np.sum 将使用本机代码中的优化循环对数组求和,而不对单个值进行任何转换(正如 shx2 指出的那样,这至关重要地需要数组内容的同质性和连续性)

      到目前为止,将每个数组元素转换为 Python 对象是开销的主要来源。

      顺便说一句,这也解释了为什么使用 Python 的standard-library C array type 进行数学运算是愚蠢sum(list)sum(array.array)很多

      【讨论】:

        【解决方案4】:

        a-b 转换为函数调用a.__rsub__(b)。因此它使用属于变量的方法(例如,如果a 是一个数组,则编译 numpy 代码)。

        In [20]: a.__rsub__??
        Type:       method-wrapper
        String Form:<method-wrapper '__rsub__' of numpy.ndarray object at 0xad27a88>
        Docstring:  x.__rsub__(y) <==> y-x
        

        np.subtract(x1, x2[, out]) 的文档显示它是 ufuncufunc 经常使用 __rsub__ 这样的编译操作,但可能会增加一些开销以适应 ufunc 协议。

        在许多其他情况下,np.foo(x, args) 转换为 x.foo(args)

        一般来说,如果函数和操作符最终调用编译后的 numpy 代码来进行实际计算,时间会非常相似,尤其是对于大型数组。

        【讨论】:

          猜你喜欢
          • 2013-11-16
          • 2018-03-20
          • 2015-07-16
          • 2015-02-06
          • 2019-09-30
          • 2013-07-23
          • 1970-01-01
          • 1970-01-01
          • 2013-08-25
          相关资源
          最近更新 更多