11

Numba は、数値コードの実行を高速化する優れたソリューションのようです。ただし、配列への割り当てがある場合、Numba は標準の Python コードよりも遅いようです。Numba の有無にかかわらず、配列/スカラーに書き込む 4 つの選択肢を比較するこの例を考えてみましょう。

(計算は、スカラーへの割り当てと配列セルへの割り当てという問題に焦点を当てるために、意図的に非常に単純に保たれています)

@autojit
def fast_sum_arr(arr):
    z = arr.copy()
    M = len(arr)
    for i in range(M):
        z[i] += arr[i]

    return z

def sum_arr(arr):
    z = arr.copy()
    M = len(arr)
    for i in range(M):
        z[i] += arr[i]

    return z

@autojit
def fast_sum_sclr(arr):
    z = 0
    M = len(arr)
    for i in range(M):
        z += arr[i]

    return z

def sum_sclr(arr):
    z = 0
    M = len(arr)
    for i in range(M):
        z += arr[i]

    return z

IPython の %timeit を使用して、取得した 4 つの選択肢を評価します。

In [125]: %timeit fast_sum_arr(arr)
100 loops, best of 3: 10.8 ms per loop

In [126]: %timeit sum_arr(arr)
100 loops, best of 3: 4.11 ms per loop

In [127]: %timeit fast_sum_sclr(arr)
100000 loops, best of 3: 10 us per loop

In [128]: %timeit sum_sclr(arr)
100 loops, best of 3: 2.93 ms per loop

Numba でコンパイルされていないsum_arr は、Numba でコンパイルされた fast_sum_arr よりも 2 倍以上高速です。一方、Numba でコンパイルされた fast_sum_sclr は、Numbaでコンパイルされていない sum_sclr よりも 2 桁以上高速です。

そのため、Numba は sum_sclr を高速化するタスクを非常にうまく実行しますが、実際には sum_arr の実行を遅くします。sum_sclr と sum_arr の唯一の違いは、前者はスカラーに割り当てられ、後者は配列セルに割り当てられることです。

関係があるかどうかはわかりませんが、最近ブログhttp://www.phi-node.com/で次のことを読みました。

「Numba が直接サポートしていない構造に直面すると、(非常に) 遅いコード パスに切り替えることが判明しました。」

ブログの著者は、Python の max() の代わりに if ステートメントを使用して、Numba のパフォーマンスを大幅に高速化しました。

これに関する洞察はありますか?

ありがとう、

FS

4

3 に答える 3

4

ここで遅いのは、配列への書き込みアクセスではなく、arr.copy() 関数です。証拠:

# -*- coding: utf-8 -*-
from numba import autojit
from Timer import Timer
import numpy as np

@autojit
def fast_sum_arr(arr, z):
    #z = arr.copy()
    M = len(arr)
    for i in range(M):
        z[i] += arr[i]

    return z

def sum_arr(arr, z):
    #z = arr.copy()
    M = len(arr)
    for i in range(M):
        z[i] += arr[i]

    return z

@autojit
def fast_sum_sclr(arr):
    z = 0
    M = len(arr)
    for i in range(M):
        z += arr[i]

    return z

def sum_sclr(arr):
    z = 0
    M = len(arr)
    for i in range(M):
        z += arr[i]

    return z

if __name__ == '__main__':
    vec1 = np.ones(1000)
    z = vec1.copy()
    with Timer() as t0:
        for i in range(10000):
            pass
    print "time for empty loop ", t0.secs
    print
    with Timer() as t1:
        for i in range(10000):
            sum_arr(vec1, z)
    print "time for sum_arr  [µs]:   ", (t1.secs-t0.secs)  / 10000 * 1e6
    with Timer() as t1:
        for i in range(10000):
            fast_sum_arr(vec1, z)
    print "time for fast_sum_arr  [µs]:   ", (t1.secs-t0.secs)  / 10000 * 1e6
    with Timer() as t1:
        for i in range(10000):
            sum_sclr(vec1)
    print "time for sum_arr  [µs]:   ", (t1.secs-t0.secs)  / 10000 * 1e6
    with Timer() as t1:
        for i in range(10000):
            fast_sum_sclr(vec1)
    print "time for fast_sum_arr  [µs]:   ", (t1.secs-t0.secs)  / 10000 * 1e6

"""
time for empty loop  0.000312089920044

time for sum_arr       [µs]:    432.02688694
time for fast_sum_arr  [µs]:      7.43598937988
time for sum_arr       [µs]:    284.574580193
time for fast_sum_arr  [µs]:      5.74610233307
"""
于 2013-08-20T02:38:44.097 に答える
1

私は numba についてあまり知りませんが、フードの下で何をしているのかについていくつかの基本的な仮定を立てれば、autojit バージョンが遅い理由と、マイナーな変更でそれを高速化する方法を推測できます...

sum_arr から始めましょう。

1 def sum_arr(arr):
2     z = arr.copy()
3     M = len(arr)
4     for i in range(M):
5         z[i] += arr[i]
6 
7     return z

ここで何が起こっているかはかなり明確ですが、次のように書き換えることができる 5 行目について取り上げましょう。

1 a = arr[i]
2 b = z[i]
3 c = a + b
4 z[i] = c

Python はこれを次のようにさらに中断します。

1 a = arr.__getitem__(i)
2 b = arr.__getitem__(i) 
3 c = a.__add__(b)
4 z.__setitem__(i, c)

a、b、および c はすべて numpy.int64 (または同様のもの) のインスタンスです。

numba がこれらの項目の日付型を検査し、それらをいくつかの numba ネイティブ データ型に変換しようとしていると思われます (numpy コードで見られる最大の速度低下の 1 つは、うっかり python データ型から numpy データ型に切り替えることです)。これが実際に起こっていることである場合、numba は少なくとも 3 回の変換を行っています。 int)))。numba はデータ型のチェックに余分なオーバーヘッドを追加し、おそらくループをまったく最適化しないのではないかと思います。ループから型の変更を削除するとどうなるか見てみましょう...

1 @autojit
2 def fast_sum_arr2(arr):
3     z = arr.tolist()
4     M = len(arr)
5     for i in range(M):
6         z[i] += arr[i]
7 
8     return numpy.array(z)

3 行目の微妙な変更 (copy ではなく tolist) により、データ型が Python の int に変更されますが、6 行目にはまだ numpy.int64 -> native があります。これを z[i] += z[i] に書き換えましょう。

1 @autojit
2 def fast_sum_arr3(arr):
3     z = arr.tolist()
4     M = len(arr)
5     for i in range(M):
6         z[i] += z[i]
7 
8     return numpy.array(z)

すべての変更により、かなりのスピードアップが見られます (ただし、必ずしも純粋な python に勝るとは限りません)。もちろん、arr+arr はばかげた速さです。

  1 import numpy
  2 from numba import autojit
  3 
  4 def sum_arr(arr):
  5     z = arr.copy()
  6     M = len(arr)
  7     for i in range(M):
  8         z[i] += arr[i]
  9 
 10     return z
 11 
 12 @autojit
 13 def fast_sum_arr(arr):
 14     z = arr.copy()
 15     M = len(arr)
 16     for i in range(M):
 17         z[i] += arr[i]
 18     
 19     return z
 20 
 21 def sum_arr2(arr):
 22     z = arr.tolist()
 23     M = len(arr)
 24     for i in range(M):
 25         z[i] += arr[i]
 26 
 27     return numpy.array(z)
 28 
 29 @autojit
 30 def fast_sum_arr2(arr):
 31     z = arr.tolist()
 32     M = len(arr)
 33     for i in range(M):
 34         z[i] += arr[i]
 35         
 36     return numpy.array(z)
 37     
 38 def sum_arr3(arr):
 39     z = arr.tolist()
 40     M = len(arr)
 41     for i in range(M):
 42         z[i] += z[i]
 43         
 44     return numpy.array(z)
 45 
 46 @autojit
 47 def fast_sum_arr3(arr):
 48     z = arr.tolist()
 49     M = len(arr)
 50     for i in range(M):
 51         z[i] += z[i]
 52 
 53     return numpy.array(z)
 54 
 55 def sum_arr4(arr):
 56     return arr+arr
 57 
 58 @autojit
 59 def fast_sum_arr4(arr):
 60     return arr+arr
 61 
 62 arr = numpy.arange(1000)

そして、タイミング、

In [1]: %timeit sum_arr(arr)
10000 loops, best of 3: 129 us per loop

In [2]: %timeit sum_arr2(arr)
1000 loops, best of 3: 232 us per loop

In [3]: %timeit sum_arr3(arr)
10000 loops, best of 3: 51.8 us per loop

In [4]: %timeit sum_arr4(arr)
100000 loops, best of 3: 3.68 us per loop

In [5]: %timeit fast_sum_arr(arr)
1000 loops, best of 3: 216 us per loop

In [6]: %timeit fast_sum_arr2(arr)
10000 loops, best of 3: 65.6 us per loop

In [7]: %timeit fast_sum_arr3(arr)
10000 loops, best of 3: 56.5 us per loop

In [8]: %timeit fast_sum_arr4(arr)
100000 loops, best of 3: 2.03 us per loop
于 2013-08-13T16:36:35.450 に答える