あなたのコメントから:あなたは比較的少数の非常に大きな要素を持っています。つまり、外側のループの反復の速度は無関係ですが、内側のループの反復の速度は重要です。
これに実際の数字を入れてみましょう。外側の配列には、最大サイズ10の最大4つの次元があります。これは、最大10000の要素があることを意味します。一方、要素は「かなり大きい」ので、控えめに言って50と解釈しましょう。つまり、510000回のループ反復があります。10000回の外部反復の速度を向上させるために行うことはすべて、コードの違いが2%未満になります。実際、それはそれよりはるかに少ないです。2%は、反復自体以外に実行する作業がないと想定していますが、これは明らかに真実ではありません。
だから、あなたは間違った場所に焦点を当てています。500000の内部反復のみが重要です。配列の配列を単一の1次元のより高い配列に置き換えて、すべてをで行うことができれば、numpy
より高速になる可能性がありますが、コードがはるかに複雑になり、理解が難しくなり、1分の1程度のゲインが得られます。 %はばかげています。vectorize
答えまたは理解の答えを使用するだけです。これらは単純で明白です。
その間:
おそらく、すべての行列要素にスレッドを使用して、評価を並列化するように試みる必要があります。
ここでは並列処理をお勧めします。ただし、スレッドは使用せず、要素ごとに1つも使用しません。
たとえば、8コアのマシンを使用している場合、8つのハードウェアスレッドを使用すると、ほぼ8倍の速度で処理を実行できます。ただし、10000のハードウェアスレッドを使用しても、10000倍、さらには8倍の速度で処理を実行できるわけではありません。コンテキストの切り替え、スレッドスタックの割り当てと解放などに多くの時間を費やしているため、実際にはシーケンシャルよりも時間がかかります。バージョン。したがって、8つのハードウェアスレッドのワーカープールを作成し、10000のタスクをキューに入れてそのプールで実行する必要があります。
また、10000はおそらくあまりにもきめ細かいです。各ジョブには少しのオーバーヘッドがあり、ジョブが小さすぎると、オーバーヘッドに多くの時間を浪費していることになります。物事をバッチ処理する明白な方法は、軸ごとです。それぞれが10要素の1行を実行する1000個のジョブ、またはそれぞれが100要素の1つの2D配列を実行する100個のジョブがあります。0、1、2、および3軸のバッチサイズをテストし、どれが最高のパフォーマンスを提供するかを確認します。違いが大きい場合は、3x10x10と4x10x10の3D配列に分割するなど、もう少し複雑なバッチ処理を試してみることをお勧めします。
最後に、Pythonスレッドは実際のOS(したがってハードウェア)スレッドですが、GILは、一度に複数のスレッドがPythonコードを実行するのを防ぎます。numpy
これを回避するためにいくつかの作業を行いますが、完全ではありません(特に、numpy
呼び出しの間に多くのオーバーヘッドがある場合)。これを回避する最も簡単な方法はmultiprocessing
、を使用することです。これにより、1つのプロセスに8つのスレッドを含める代わりに、8つの個別のプロセスのプールを作成できます。これにより、オーバーヘッドが大幅に増加します。サブ配列をコピーするか(タスク関数のパラメーターと戻り値として暗黙的に、またはパイプを介して明示的に)、共有メモリに配置する必要があります。ただし、タスクサイズが十分に大きい場合は、通常は問題ありません。
既存のコードを並列化する方法の例を次に示します。
f = np.mean
result = np.zeros(a.shape)
future_map = {}
with futures.ProcessPoolExecutor(max_workers=8) as executor:
for i in np.ndindex(a.shape):
future_map[executor.submit(f, a[i])] = i
for future in futures.as_completed(future_map):
i = future_map[future]
result[i] = future.result()
もちろん、単純化することもできますが(たとえば、送信ループを理解に置き換えるなどdict
)、何を変更する必要があるかをできるだけ明確にしたいと思います。
また、コードが少し簡単になるため、私は使用してfutures
います(3.2以降が必要です。2.7を使用している場合は、 PyPIからバックポートをインストールしてください)。より柔軟性が必要な場合は、より複雑なmultiprocessing
ライブラリが必要です。
そして最後に、私はバッチ処理を行っていません(各タスクは単一の要素です)ので、オーバーヘッドはおそらくかなり悪くなるでしょう。
ただし、これから始めて、使いやすい範囲で単純化してから、前述のように1、2、3軸などのバッチを使用するように変換します。