web-dev-qa-db-ja.com

ここでnumbaがnumpyより速いのはなぜですか?

ここでnumbaがnumpyを打っている理由がわかりません(3倍以上)。ここでベンチマークを行う方法にいくつかの根本的な誤りを犯しましたか? numpyの完璧な状況のように思われますか?チェックとして、numbaとnumpyを組み合わせたバリエーションも実行したことに注意してください(図には示されていません)。これは、numbaなしでnumpyを実行した場合と同じでした。

(これはフォローアップの質問です: 2次元配列を数値処理する最速の方法:データフレーム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)

結果:

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)、標準のアナコンダディストリビューションで実行しました。必要に応じて、セットアップの詳細を提供できます。

24
JohnE

この質問は、高水準言語からプリコンパイルされた関数を呼び出す際の制限を(ある程度)強調していると思います。 C++で次のように記述したとします。

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

コンパイラーは、コンパイル時にこれらすべて、つまり式全体を認識します。ここでは、一時的な最適化(およびループのアンロール)の最適化など、非常にインテリジェントな多くのことができます。

pythonただし、何が起こっているのかを考慮してください:numpyを使用する場合、各 `` + ''はnp配列型(連続するメモリブロックの薄いラッパー、つまり、低レベルの意味)、そして加算を超高速で実行するfortran(またはC++)関数を呼び出しますが、加算を1つだけ実行し、一時的に吐き出します。

Numpyは素晴らしく便利でかなり高速ですが、ハードワークのために高速コンパイルされた言語を呼び出しているように見えても、コンパイラーがプログラム全体、それは孤立した小さなビットを供給されています。また、これはコンパイラー、特に非常にインテリジェントでコードが適切に記述されている場合にサイクルごとに複数の命令をリタイアできる最新のコンパイラーにとって非常に有害です。

一方、Numbaはjitを使用しました。したがって、実行時に一時ファイルが不要であることを理解し、一時ファイルを最適化することができます。基本的に、Numbaはプログラムを全体としてコンパイルする機会があります。numpyは、それ自体が事前にコンパイルされた小さなアトミックブロックのみを呼び出すことができます。

34
Nir Friedman

Numpyに依頼する場合:

x = x*2 - ( y * 55 )

内部的には次のように翻訳されています。

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

これらのtempはそれぞれ、割り当て、操作、割り当て解除の必要がある配列です。一方、Numbaは一度に1つの項目を処理し、そのオーバーヘッドを処理する必要はありません。

22
Jaime

Numbaは一般にNumpyやCython(少なくともLinuxでは)よりも高速です。

これがプロットです( Numba vs. Cython:Take 2 )から盗まれました)。 Benchmark on Numpy, Cython and Numba

このベンチマークでは、ペアワイズ距離が計算されているため、これはアルゴリズムに依存する場合があります。

これは他のプラットフォームでは異なる場合があることに注意してください。Winpythonの場合はこれを参照してください(- WinPython Cythonチュートリアル から):

Benchmark on Numpy, Cython and Numba with Winpython

8
sebix

元の質問をさらに雑然とさせるのではなく、ジェフ、ハイメ、ヴィートラックに応じて、ここにさらにいくつかのものを追加します。

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には役立ちません。

5
JohnE