バックグラウンド
私の質問は、経験豊富な MATLAB ユーザーがしばしば保持/作成する信念/仮定を幾分弱体化させる単純な観察によって動機付けられています。
- MATLAB は、組み込み関数や基本的な言語機能 (インデックス ベクトルや行列など) に関して、非常に最適化されています。
- MATLAB のループは (JIT にもかかわらず) 低速であり、アルゴリズムがネイティブの「ベクトル化」された方法で表現できる場合は、一般的に回避する必要があります。
要するに、MATLAB のコア機能は効率的であり、MATLAB コードを使用してそれを凌駕しようとすることは、不可能ではないにしても困難です。
ベクトル索引付けのパフォーマンスの調査
以下に示すコード例は基本的なものです。すべてのベクトル エントリにスカラー値を割り当てます。まず、空の vector を割り当てますx
。
tic; x = zeros(1e8,1); toc
Elapsed time is 0.260525 seconds.
すべてのエントリをx
同じ値に設定したいと思います。実際には、たとえば のように別の方法で行うことになりますx = value*ones(1e8,1)
が、ここでのポイントは、ベクトル インデックスのパフォーマンスを調査することです。最も簡単な方法は次のように書くことです:
tic; x(:) = 1; toc
Elapsed time is 0.094316 seconds.
これを方法 1 と呼びましょう ( に割り当てられた値からx
)。非常に高速のようです (少なくともメモリ割り当てよりも高速です)。ここで行うのはメモリ操作だけなので、得られた実効メモリ帯域幅を計算し、それを私のコンピューターのハードウェア メモリ帯域幅と比較することで、このコードの効率を見積もることができます。
eff_bandwidth = numel(x) * 8 bytes per double * 2 / time
上記では、2
SSE ストリーミングが使用されない限り、メモリ内の値を設定するには、メモリからの読み取りとメモリへの書き込みの両方が必要になるため、乗算します。上記の例では:
eff_bandwidth(1) = 1e8*8*2/0.094316 = 17 Gb/s
私のコンピューターのSTREAM ベンチマーク メモリ帯域幅は約 17.9 Gb/s であるため、実際、この場合、MATLAB はピークに近いパフォーマンスを提供します! ここまでは順調ですね。
方法 1 は、すべてのベクトル要素を何らかの値に設定する場合に適しています。ただし、エントリごとに要素にアクセスする場合は、を などstep
に置き換える必要があります。以下は、方法 1 との直接の速度比較です。:
1:step:end
tic; x(1:end) = 2; toc
Elapsed time is 0.496476 seconds.
異なるパフォーマンスを期待することはできませんが、方法 2 は明らかに大きな問題です。理由もなく 5 倍の速度低下が発生します。私の疑いは、この場合、MATLAB がインデックス ベクトル ( 1:end
) を明示的に割り当てることです。これは、 の代わりに明示的なベクトル サイズを使用することで多少確認されend
ます。
tic; x(1:1e8) = 3; toc
Elapsed time is 0.482083 seconds.
方法 2 と 3 のパフォーマンスは同じくらい悪いです。
もう 1 つの可能性は、インデックス ベクトルを明示的に作成し、id
それを index に使用することx
です。これにより、最も柔軟なインデックス作成機能が提供されます。私たちの場合には:
tic;
id = 1:1e8; % colon(1,1e8);
x(id) = 4;
toc
Elapsed time is 1.208419 seconds.
方法 1 と比較して 12 倍の速度低下です。に使用されるメモリが増えるため、方法 1 よりもパフォーマンスが低下することは理解していますがid
、方法 2 および 3 よりもパフォーマンスが大幅に低下するのはなぜですか?
絶望的に聞こえるかもしれませんが、ループを試してみましょう。
tic;
for i=1:numel(x)
x(i) = 5;
end
toc
Elapsed time is 0.788944 seconds.
驚くべきことに、ループはvectorized
方法 4 よりも優れていますが、それでも方法 1、2、3 よりも遅いです。
tic;
for i=1:1e8
x(i) = 6;
end
toc
Elapsed time is 0.321246 seconds.
そして、これがおそらくこの研究の最も奇妙な結果です。MATLAB で記述されたループは、ネイティブのベクトル インデックス作成よりも大幅に優れています。そんなはずはありません。JIT されたループは、方法 1 でほぼ得られた理論上のピークよりも 3 倍遅いことに注意してください。したがって、まだ改善の余地がたくさんあります。通常の「ベクトル化された」索引付け ( 1:end
) がさらに遅いことは驚くべきことです (強い言葉の方が適しています)。
質問
- MATLAB での単純なインデックス付けは非常に非効率的です (方法 2、3、および 4 は方法 1 よりも遅い)、または何かを見逃していましたか?
- 方法 4が方法 2 および 3よりも(非常に) 遅いのはなぜですか?
1e8
ループ バウンドの代わりに使用するとnumel(x)
、コードが 2 倍高速化されるのはなぜですか?
編集 ジョナスのコメントを読んだ後、論理インデックスを使用してそれを行う別の方法を次に示します。
tic;
id = logical(ones(1, 1e8));
x(id) = 7;
toc
Elapsed time is 0.613363 seconds.
方法 4 よりもはるかに優れています。
便宜上:
function test
tic; x = zeros(1,1e8); toc
tic; x(:) = 1; toc
tic; x(1:end) = 2; toc
tic; x(1:1e8) = 3; toc
tic;
id = 1:1e8; % colon(1,1e8);
x(id) = 4;
toc
tic;
for i=1:numel(x)
x(i) = 5;
end
toc
tic;
for i=1:1e8
x(i) = 6;
end
toc
end