numpy中的最佳(广播)矩阵划分.是否避免使用临时数组?

Ian*_*ore 6 python performance numpy

如果遵循某些广播规则,Numpy允许添加/倍增/分割不同大小的矩阵.此外,临时阵列的创建是numpy的主要速度障碍.

下面的timit结果让我感到惊讶......发生了什么事?

In [41]: def f_no_dot(mat,arr):
   ....:     return mat/arr

In [42]: def f_dot(mat,arr):
   ....:     denominator = scipy.dot(arr, scipy.ones((1,2)))
   ....:     return mat/denominator

In [43]: mat = scipy.rand(360000,2)

In [44]: arr = scipy.rand(360000,1)

In [45]: timeit temp = f_no_dot(mat,arr)
10 loops, best of 3: 44.7 ms per loop

In [46]: timeit temp = f_dot(mat,arr)
100 loops, best of 3: 10.1 ms per loop
Run Code Online (Sandbox Code Playgroud)

我认为f_dot会慢,因为它必须创建临时数组分母,我假设f_no_dot跳过了这一步.我应该注意,这些时间对于f_no_dot线性地(对于数组大小,长达10亿),并且对于f_dot稍微比线性更差.

Joe*_*ton 5

我认为f_dot会慢,因为它必须创建临时数组分母,我假设f_no_dot跳过了这一步.

对于它的价值,创建临时数组跳过,这就是为什么f_no_dot会更慢(但使用较少的内存).

对相同大小的数组进行元素操作的速度更快,因为numpy不必担心数组的跨度(尺寸,大小等).

使用广播的操作通常比不必要的操作慢一点.

如果你有足够的内存,创建一个临时副本可以给你一个加速,但会使用更多的内存.

例如,比较这三个函数:

import numpy as np
import timeit

def f_no_dot(x, y):
    return x / y

def f_dot(x, y):
    denom = np.dot(y, np.ones((1,2)))
    return x / denom

def f_in_place(x, y):
    x /= y
    return x

num = 3600000
x = np.ones((num, 2))
y = np.ones((num, 1))


for func in ['f_dot', 'f_no_dot', 'f_in_place']:
    t = timeit.timeit('%s(x,y)' % func, number=100,
            setup='from __main__ import x,y,f_dot, f_no_dot, f_in_place')
    print func, 'time...'
    print t / 100.0
Run Code Online (Sandbox Code Playgroud)

这会产生与结果类似的时间:

f_dot time...
0.184361531734
f_no_dot time...
0.619203259945
f_in_place time...
0.585789341927
Run Code Online (Sandbox Code Playgroud)

但是,如果我们比较一下内存使用情况,事情会变得更加清晰......

我们xy数组的总大小约为27.5 + 55 MB,即82 MB(64位整数).导入numpy还有额外的~11 MB的开销等.

x / y作为新阵列返回(即不做x /= y)将需要另一个55 MB阵列.

100次运行f_dot: 我们在这里创建一个临时数组,所以我们期望看到11 + 82 + 55 + 55 MB或~203 MB的内存使用量.而且,这就是我们所看到的...... 在此输入图像描述

100次运行f_no_dot: 如果没有创建临时数组,我们预计峰值内存使用量为11 + 82 + 55 MB,或148 MB ......
在此输入图像描述 ......这正是我们所看到的.

因此,x / y产生额外的num x 2临时数组做除法.

因此,除非在相同大小的两个阵列上操作,否则划分需要相当长的时间.

100次运行f_in_place: 如果我们可以x就地修改,我们可以节省更多的内存,如果这是主要关注点. 在此输入图像描述

基本上,在某些情况下,numpy试图以牺牲速度为代价来节省内存.


pv.*_*pv. 2

您看到的很可能是小(2,)维度上的迭代开销。Numpy(版本 < 1.6)仅能有效处理涉及连续数组(相同形状)的操作。随着最后一个维度的大小增加,这种影响就会消失。

查看连续性的效果:

In [1]: import numpy
In [2]: numpy.__version__
Out[2]: '1.5.1'
In [3]: arr_cont1 = numpy.random.rand(360000, 2)
In [4]: arr_cont2 = numpy.random.rand(360000, 2)
In [5]: arr_noncont = numpy.random.rand(360000, 4)[:,::2]
In [6]: arr_bcast = numpy.random.rand(360000, 1)
In [7]: %timeit arr_cont1 / arr_cont2
100 loops, best of 3: 5.75 ms per loop
In [8]: %timeit arr_noncont / arr_cont2
10 loops, best of 3: 54.4 ms per loop
In [9]: %timeit arr_bcast / arr_cont2
10 loops, best of 3: 55.2 ms per loop
Run Code Online (Sandbox Code Playgroud) 然而,这种情况在 Numpy >= 1.6.0 中得到了很大改善:(
In [1]: import numpy
In [2]: numpy.__version__
Out[2]: '1.6.1'
In [3]: arr_cont1 = numpy.random.rand(360000, 2)
In [4]: arr_cont2 = numpy.random.rand(360000, 2)
In [5]: arr_noncont = numpy.random.rand(360000, 4)[:,::2]
In [6]: arr_bcast = numpy.random.rand(360000, 1)
In [7]: %timeit arr_cont1 / arr_cont2
100 loops, best of 3: 5.37 ms per loop
In [8]: %timeit arr_noncont / arr_cont2
100 loops, best of 3: 6.12 ms per loop
In [9]: %timeit arr_bcast / arr_cont2
100 loops, best of 3: 7.81 ms per loop
上面的所有计时可能只精确到 1 毫秒。)

另请注意,临时工并不那么昂贵:

In [82]: %timeit arr_cont1.copy()
1000 loops, best of 3: 778 us per loop
Run Code Online (Sandbox Code Playgroud)

编辑:请注意,上面的 isarr_noncont与 stride of 是连续的2*itemsize,因此可以解开内部循环 --- Numpy 可以使其与连续数组一样快。使用广播(或使用真正不连续的数组,例如numpy.random.rand(360000*2, 2)[::2,:],内部循环无法解开,并且这些情况会稍微慢一些。如果 Numpy 为每个循环动态发出定制的机器代码,则仍然可以改进这一点,但它不会这样做(至少现在是这样:)