如何实现 NumPy 的就地运算符来解释显著的性能提升



我知道在Python中,就地运算符使用__iadd__方法作为就地运算符。对于不可变类型,__iadd__是使用__add__的变通方法,例如,像tmp = a + b; a = tmp一样,但是可变类型(如列表)是就地修改的,这会导致速度的轻微提高。

然而,如果我有一个NumPy数组,我修改它包含的不可变类型,例如整数或浮点数,也有一个更显着的速度提升。这是如何工作的呢?我在下面做了一些示例基准测试:

import numpy as np
def inplace(a, b):
    a += b
    return a
def assignment(a, b):
    a = a + b
    return a
int1 = 1
int2 = 1
list1 = [1]
list2 = [1]
npary1 = np.ones((1000,1000))
npary2 = np.ones((1000,1000))
print('Python integers')
%timeit inplace(int1, 1)
%timeit assignment(int2, 1)
print('nPython lists')
%timeit inplace(list1, [1])
%timeit assignment(list2, [1])
print('nNumPy Arrays')
%timeit inplace(npary1, 1)
%timeit assignment(npary2, 1)

当我在NumPy数组上使用就地运算符时,我所期望的是与Python整数类似的差异,但是结果完全不同:

Python integers
1000000 loops, best of 3: 265 ns per loop
1000000 loops, best of 3: 249 ns per loop
Python lists
1000000 loops, best of 3: 449 ns per loop
1000000 loops, best of 3: 638 ns per loop
NumPy Arrays
100 loops, best of 3: 3.76 ms per loop
100 loops, best of 3: 6.6 ms per loop

每次调用assignment(npary2, 1)都需要创建一个新的100万元素数组。考虑分配一个(1000,1000)形状的数组需要多少时间:

In [21]: %timeit np.ones((1000, 1000))
100 loops, best of 3: 3.84 ms per loop

在我的机器上分配一个新的临时数组需要大约3.84 ms,并且在正确的数量级上解释了inplace(npary1, 1)assignment(nparay2, 1)之间的整个差异:

In [12]: %timeit inplace(npary1, 1)
1000 loops, best of 3: 1.8 ms per loop
In [13]: %timeit assignment(npary2, 1)
100 loops, best of 3: 4.04 ms per loop

因此,考虑到分配是一个相对较慢的过程,就地添加明显快于对新数组的赋值是有道理的。


对NumPy数组的NumPy操作可能很快,但创建NumPy数组相对较慢。例如,考虑创建一个NumPy数组比创建一个Python列表要多花多少时间:

In [14]: %timeit list()
10000000 loops, best of 3: 106 ns per loop
In [15]: %timeit np.array([])
1000000 loops, best of 3: 563 ns per loop

这就是为什么通常使用一个大的NumPy数组(分配一次)比使用数千个小的NumPy数组更好的原因之一。

最新更新