30

我不明白为什么 numba 在这里击败 numpy(超过 3 倍)。我在这里进行基准测试时是否犯了一些基本错误?似乎是 numpy 的完美情况,不是吗?请注意,作为检查,我还运行了一个结合 numba 和 numpy 的变体(未显示),正如预期的那样,它与在没有 numba 的情况下运行 numpy 相同。

(顺便说一句,这是一个后续问题:Fastest way to numericly process 2d-array: dataframe vs series vs array 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)

结果:

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

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

4

4 回答 4

42

我认为这个问题(在某种程度上)突出了从高级语言调用预编译函数的局限性。假设您在 C++ 中编写如下内容:

for (int i = 0; i != N; ++i) a[i] = b[i] + c[i] + 2 * d[i];

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

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

我们可以看到,在某种程度上,虽然 numpy 非常棒、方便且非常快,但它正在减慢速度,因为虽然它似乎正在调用一种快速编译的语言来进行艰苦的工作,但编译器并没有看到整个程序,它只是喂了一些孤立的小部分。这对编译器来说是非常不利的,尤其是现代编译器,它们非常智能,当代码编写得很好时,每个周期可以退出多条指令。

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

于 2014-09-20T19:15:04.763 回答
27

当您要求 numpy 执行以下操作时:

x = x*2 - ( y * 55 )

它在内部被翻译为:

tmp1 = y * 55
tmp2 = x * 2
tmp3 = tmp2 - tmp1
x = tmp3

这些临时文件中的每一个都是必须分配、操作然后释放的数组。另一方面,Numba 一次处理一个项目,并且不必处理这些开销。

于 2014-09-20T17:20:53.983 回答
9

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

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

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

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

使用 Winpython 对 Numpy、Cython 和 Numba 进行基准测试

于 2014-09-20T17:58:18.620 回答
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

我的机器今天的运行速度似乎比昨天快一点,所以在这里它们与 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

请注意,当我编写 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])

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

于 2014-09-20T18:48:56.080 回答