为什么numba比numpy更快?

Joh*_*hnE 20 python numpy numba

我无法弄清楚为什么numba在这里击败numpy(超过3x).我是否在这里进行基准测试时遇到了一些根本性的错误?看起来像numpy的完美情况,不是吗?请注意,作为一个检查,我还运行了一个组合numba和numpy(未显示)的变体,正如预期的那样,与没有numba的numpy相同.

(顺便说一下这是一个后续问题:数字处理2d阵列的最快方法:数据帧vs系列vs阵列vs numba)

import numpy as np
from numba import jit
nobs = 10000 

def proc_numpy(x,y,z):

   x = x*2 - ( y * 55 )      # these 4 lines represent use cases
   y = x + y*2               # where the processing time is mostly
   z = x + y + 99            # a function of, say, 50 to 200 lines
   z = z * ( z - .88 )       # of fairly simple numerical operations

   return z

@jit
def proc_numba(xx,yy,zz):
   for j in range(nobs):     # as pointed out by Llopis, this for loop 
      x, y = xx[j], yy[j]    # is not needed here.  it is here by 
                             # accident because in the original benchmarks 
      x = x*2 - ( y * 55 )   # I was doing data creation inside the function 
      y = x + y*2            # instead of passing it in as an array
      z = x + y + 99         # in any case, this redundant code seems to 
      z = z * ( z - .88 )    # have something to do with the code running
                             # faster.  without the redundant code, the 
      zz[j] = z              # numba and numpy functions are exactly the same.
   return zz

x = np.random.randn(nobs)
y = np.random.randn(nobs)
z = np.zeros(nobs)
res_numpy = proc_numpy(x,y,z)

z = np.zeros(nobs)
res_numba = proc_numba(x,y,z)
Run Code Online (Sandbox Code Playgroud)

结果:

In [356]: np.all( res_numpy == res_numba )
Out[356]: True

In [357]: %timeit proc_numpy(x,y,z)
10000 loops, best of 3: 105 µs per loop

In [358]: %timeit proc_numba(x,y,z)
10000 loops, best of 3: 28.6 µs per loop
Run Code Online (Sandbox Code Playgroud)

我在2012年macbook air(13.3),标准的anaconda发行版上运行了这个版本.如果相关,我可以提供有关我的设置的更多详细信息.

Nir*_*man 30

我认为这个问题(在某种程度上)强调了从更高级语言调用预编译函数的局限性.假设在C++中你写了类似的东西:

for (int i = 0; i != N; ++i) a[i] = b[i] + c[i] + 2 * d[i];
Run Code Online (Sandbox Code Playgroud)

编译器在编译时看到所有这些,整个表达式.它可以在这里做很多非常聪明的事情,包括优化临时(和循环展开).

然而,在python中,考虑一下发生了什么:当你使用numpy时,每个''+''在np数组类型上使用运算符重载(它们只是连续内存块的薄包装,即低级意义上的数组),并调用to fortran(或C++)函数,它可以超快速地添加.但它只是添加一个,并吐出一个临时的.

我们可以看到,在某种程度上,虽然numpy非常棒,方便且非常快,但它会减慢速度,因为虽然看起来它正在调用快速编译的语言以进行艰苦的工作,但编译器却看不到整个程序,它只是输入孤立的小位.这对编译器来说是非常不利的,尤其是现代编译器,它们非常智能,并且在编写代码时可以在每个周期退出多个指令.

另一方面,Numba使用了jit.因此,在运行时它可以确定不需要临时值,并优化它们.基本上,Numba有机会将程序编译为一个整体,numpy只能调用本身已经预编译的小原子块.

  • numba jit-compiler并没有智能地确定如何避免临时或使用任何类型的整个程序优化.不同之处在于,在循环中,通过将所有内容编码为标量操作,显式_instructs_编译器不会产生任何临时值.在Julia中也是如此,如果用"普通的"矢量化形式写出来,就会得到临时性的,因此速度很快.写作循环或点广播临时代表明确避免.如果编译器实际上是_is_聪明的,那么可以进行循环展开并在其上进行模拟. (3认同)
  • 在一般情况下,几乎没有语言可以优化中间数组分配,除非它可以证明每个子表达式都是纯粹的,这是非常困难的.此外,在某些情况下,中间数组会提供加速,例如,如果由于某种原因您正在排序一个子结果. (2认同)
  • @NirFriedman - 你的神螺栓代码不正确; 你在`add`函数中缺少`return output;`.它甚至警告过你!有了它,那么你可以看到`add3`分配两个向量,即使它内联`add`.除此之外,我希望你能理解阵列临时与标量临时的巨大成本差异; 迂腐的头发分裂是没有用的. (2认同)

Jai*_*ime 20

当你问numpy时:

x = x*2 - ( y * 55 )
Run Code Online (Sandbox Code Playgroud)

内部翻译为:

tmp1 = y * 55
tmp2 = x * 2
tmp3 = tmp2 - tmp1
x = tmp3
Run Code Online (Sandbox Code Playgroud)

每个temp都是必须分配,操作然后解除分配的数组.另一方面,Numba一次处理一件物品,而不必处理这些开销.

  • 感谢Jaime和其他所有人的见解.你的回答和Nir非常相似,我认为Nir可以比你更多地使用rep点,所以我会给他支票.;-) (2认同)

seb*_*bix 7

Numba通常比Numpy甚至Cython更快(至少在Linux上).

这是一个情节(从Numba vs. Cython中偷走:Take 2): Numpy,Cython和Numba的基准测试

在此基准测试中,已计算成对距离,因此这可能取决于算法.

请注意,在其他平台上可能会有所不同,请参阅Winpython(来自WinPython Cython教程):

使用Winpython在Numpy,Cython和Numba上进行基准测试


Joh*_*hnE 5

与其进一步混淆原始问题,我将在此处添加更多内容以回应 Jeff、Jaime 和 Veedrac:

def proc_numpy2(x,y,z):
   np.subtract( np.multiply(x,2), np.multiply(y,55),out=x)
   np.add( x, np.multiply(y,2),out=y)
   np.add(x,np.add(y,99),out=z) 
   np.multiply(z,np.subtract(z,.88),out=z)
   return z

def proc_numpy3(x,y,z):
   x *= 2
   x -= y*55
   y *= 2
   y += x
   z = x + y
   z += 99
   z *= (z-.88) 
   return z
Run Code Online (Sandbox Code Playgroud)

我的机器今天似乎比昨天运行得快一点,所以这里它们与 proc_numpy 相比(proc_numba 的计时与以前相同)

In [611]: %timeit proc_numpy(x,y,z)
10000 loops, best of 3: 103 µs per loop

In [612]: %timeit proc_numpy2(x,y,z)
10000 loops, best of 3: 92.5 µs per loop

In [613]: %timeit proc_numpy3(x,y,z)
10000 loops, best of 3: 85.1 µs per loop
Run Code Online (Sandbox Code Playgroud)

请注意,当我在编写 proc_numpy2/3 时,我开始看到一些副作用,所以我制作了 x,y,z 的副本并传递了这些副本,而不是重新使用 x,y,z。此外,不同的函数有时在精度上略有不同,所以它们中的一些没有通过相等性测试,但是如果你比较它们,它们真的很接近。我认为这是由于创建或(不创建)临时变量造成的。例如:

In [458]: (res_numpy2 - res_numba)[:12]
Out[458]: 
array([ -7.27595761e-12,   0.00000000e+00,   0.00000000e+00,
         0.00000000e+00,   0.00000000e+00,   0.00000000e+00,
         0.00000000e+00,   0.00000000e+00,   0.00000000e+00,
         0.00000000e+00,  -7.27595761e-12,   0.00000000e+00])
Run Code Online (Sandbox Code Playgroud)

此外,它非常小(大约 10 µs),但使用浮点文字(55。而不是 55)也会为 numpy 节省一点时间,但对 numba 没有帮助。