-1

本当の「コーダー」ではなく、データ分析と数値計算のための Python のユーザーとして、私は非常にオーバーヘッドの少ない並列ループ計算を複数のコアに分散させる方法を見逃していました。私が学んだように、Numba には prange コンストラクトがありましたが、「不安定性とパフォーマンスの問題」のために放棄されました

新たにオープンソース化された@guvectorizeデコレーターをいじって、それを使って後期 prange の機能を実質的にオーバーヘッドなしでエミュレートする方法を見つけました。

Continuum Analytics の担当者のおかげで、このツールが手元にあることを非常にうれしく思います。@guvectorize のこの使用法について明確に言及しているウェブは見つかりませんでした。以前に NumbaPro を使用していた人にとっては些細なことかもしれませんが、私はこれをすべての非コーダー仲間のために投稿しています (この「質問」に対する私の回答を参照してください)。

4

1 に答える 1

-1

以下の例を考えてみましょう。コアが 2 つの入力配列とループ インデックスの関数を含む数値計算を行う 2 レベルのネストされた for ループが 4 つの異なる方法で実行されます。各バリアントは、Ipython の%timeit魔法でタイミングがとられています。

  1. numba.jit を使用してコンパイルされた単純な for ループ
  2. シングルスレッドで実行される、numba.guvectorize を使用した forall のような構造 ( target = "cpu")
  3. numba.guvectorize を使用した forall のような構造で、CPU の「コア」 (私の場合はハイパースレッド) と同じ数のスレッドで実行されます ( target = "parallel")
  4. 3. と同じですが、ランダムに並べ替えられた「並列」ループ インデックスのシーケンスで「guvectorized」forall を呼び出します。

(この特定の例では) 内側のループの範囲が外側のループのインデックスの値に依存するため、最後の 1 つが実行されます。gufunc 呼び出しのディスパッチが numpy 内でどのように編成されているかは正確にはわかりませんが、「並列」ループ インデックスのランダム化により負荷分散がわずかに向上するようです。

私の(遅い)マシン(第1世代コアi5、2コア、4ハイパースレッド)では、タイミングが得られます:

1 loop, best of 3: 8.19 s per loop
1 loop, best of 3: 8.27 s per loop
1 loop, best of 3: 4.6 s per loop
1 loop, best of 3: 3.46 s per loop

注: このレシピが target="gpu" にすぐに適用されるかどうか (適用されるはずですが、現在適切なグラフィックス カードにアクセスできません)、高速化はどうなのか興味があります。投稿してください!

そして、ここに例があります:

import numpy as np
from numba import jit, guvectorize, float64, int64

@jit
def naive_for_loop(some_input_array, another_input_array, result):
    for i in range(result.shape[0]):
        for k in range(some_input_array.shape[0] - i):
            result[i] += some_input_array[k+i] * another_input_array[k] * np.sin(0.001 * (k+i)) 

@guvectorize([(float64[:],float64[:],int64[:],float64[:])],'(n),(n),()->()', nopython=True, target='parallel')
def forall_loop_body_parallel(some_input_array, another_input_array, loop_index, result):
    i = loop_index[0]       # just a shorthand
    # do some nontrivial calculation involving elements from the input arrays and the loop index
    for k in range(some_input_array.shape[0] - i):
        result[0] += some_input_array[k+i] * another_input_array[k] * np.sin(0.001 * (k+i)) 

@guvectorize([(float64[:],float64[:],int64[:],float64[:])],'(n),(n),()->()', nopython=True, target='cpu')
def forall_loop_body_cpu(some_input_array, another_input_array, loop_index, result):
    i = loop_index[0]       # just a shorthand
    # do some nontrivial calculation involving elements from the input arrays and the loop index
    for k in range(some_input_array.shape[0] - i):
        result[0] += some_input_array[k+i] * another_input_array[k] * np.sin(0.001 * (k+i)) 

arg_size = 20000

input_array_1 = np.random.rand(arg_size)
input_array_2 = np.random.rand(arg_size)
result_array = np.zeros_like(input_array_1)

# do single-threaded naive nested for loop
# reset result_array inside %timeit call 
%timeit -r 3 result_array[:] = 0.0; naive_for_loop(input_array_1, input_array_2, result_array)
result_1 = result_array.copy()

# do single-threaded forall loop (loop indices in-order)
# reset result_array inside %timeit call 
loop_indices = range(arg_size)
%timeit -r 3 result_array[:] = 0.0; forall_loop_body_cpu(input_array_1, input_array_2, loop_indices, result_array)
result_2 = result_array.copy()

# do multi-threaded forall loop (loop indices in-order)
# reset result_array inside %timeit call 
loop_indices = range(arg_size)
%timeit -r 3 result_array[:] = 0.0; forall_loop_body_parallel(input_array_1, input_array_2, loop_indices, result_array)
result_3 = result_array.copy()

# do forall loop (loop indices scrambled for better load balancing)
# reset result_array inside %timeit call 
loop_indices_scrambled = np.random.permutation(range(arg_size))
loop_indices_unscrambled = np.argsort(loop_indices_scrambled)
%timeit -r 3 result_array[:] = 0.0; forall_loop_body_parallel(input_array_1, input_array_2, loop_indices_scrambled, result_array)
result_4 = result_array[loop_indices_unscrambled].copy()


# check validity
print(np.all(result_1 == result_2))
print(np.all(result_1 == result_3))
print(np.all(result_1 == result_4))
于 2016-02-21T20:54:23.947 に答える