NumPy函数比运算符执行逐元素操作更快吗?

12
我最近阅读了一个很棒的SO帖子,其中一位用户建议在处理NumPy数组时,使用numpy.sum比Python的sum更快。
这使我思考,对NumPy数组进行逐元素操作时,使用NumPy函数是否比运算符更快?如果是这样,为什么呢?
考虑以下示例。
import numpy as np
a = np.random.random(1e10)
b = np.random.random(1e10)
< p > np.subtract(a, b) 是否比 a - b 更可靠地快?< /p >
4个回答

17

不,没有显著的区别。

np.sum更快的原因是sum被实现为“简单地”迭代可迭代对象(在这种情况下,是numpy数组),调用元素的__add__运算符(这会带来显著的开销),而numpy的sum实现则经过了优化,例如利用它知道元素类型(dtype)以及它们在内存中是连续的等特点。

但对于np.subtract(arr1,arr2)arr1-arr2,情况并非如此。后者大致相当于前者。

差异在于,可以在Python中重写减法运算符,所以numpy数组覆盖它以使用优化版本。 然而,sum操作是不可重写的,因此numpy提供了替代的优化版本。


10

不完全是。但你可以很容易地检查时间。

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])这样的操作。


6
np.subtract包含将其参数转换为数组的额外代码,因此np.subtract([1,2,3],[4,5,6])是可行的。a-b不需要这个额外的代码,所以它稍微快一点。np.subtract还可以处理out关键字参数... - unutbu
很好的观点,@unutbu。np.subtract的这两个附加功能都是函数进入/退出时的一次性问题。如果您不使用它们,则它们是O(1),因此随着数组越来越大,它们将变得越来越微不足道。 - Dan Lenski

3

@shx2的回答很好。

我稍微解释一下sumnp.sum的不同:

  • 内置的sum会遍历数组,逐个将元素转换为Python对象,然后将它们作为Python对象相加。
  • np.sum使用本地代码中的优化循环对数组进行求和,而不需要转换单个值(正如shx2所指出的那样,这要求数组内容具有同质性和连续性)。

每个数组元素的转换为Python对象是开销最大的来源。

顺便说一下,这也说明为什么使用Python的标准库C数组类型进行数学运算是愚蠢的。 sum(list)sum(array.array)快得多


1

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代码来执行实际计算,则计时将非常相似,特别是对于大数组。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接