結果は少し誤解を招く可能性があります。2 つの空の行列を乗算すると、結果の行列はすぐに「割り当て」および「初期化」されず、最初に使用するまで延期されます (遅延評価のようなもの)。
範囲外にインデックスを付けて変数を大きくする場合も同じことが当てはまります。数値配列の場合、欠落しているエントリはゼロで埋められます (非数値の場合については後で説明します)。もちろん、この方法で行列を成長させても、既存の要素は上書きされません。
そのため、高速に見えるかもしれませんが、実際にマトリックスを最初に使用するまで割り当て時間を遅らせているだけです。最終的には、最初から割り当てを行った場合と同様のタイミングになります。
他のいくつかの選択肢と比較して、この動作を示す例:
N = 1000;
clear z
tic, z = zeros(N,N); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
clear z
tic, z = zeros(N,0)*zeros(0,N); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
clear z
tic, z(N,N) = 0; toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
clear z
tic, z = full(spalloc(N,N,0)); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
clear z
tic, z(1:N,1:N) = 0; toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
clear z
val = 0;
tic, z = val(ones(N)); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
clear z
tic, z = repmat(0, [N N]); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))
結果は、それぞれのケースで両方の命令の経過時間を合計すると、同様の合計タイミングになることを示しています。
// zeros(N,N)
Elapsed time is 0.004525 seconds.
Elapsed time is 0.000792 seconds.
// zeros(N,0)*zeros(0,N)
Elapsed time is 0.000052 seconds.
Elapsed time is 0.004365 seconds.
// z(N,N) = 0
Elapsed time is 0.000053 seconds.
Elapsed time is 0.004119 seconds.
他のタイミングは次のとおりです。
// full(spalloc(N,N,0))
Elapsed time is 0.001463 seconds.
Elapsed time is 0.003751 seconds.
// z(1:N,1:N) = 0
Elapsed time is 0.006820 seconds.
Elapsed time is 0.000647 seconds.
// val(ones(N))
Elapsed time is 0.034880 seconds.
Elapsed time is 0.000911 seconds.
// repmat(0, [N N])
Elapsed time is 0.001320 seconds.
Elapsed time is 0.003749 seconds.
これらの測定値はミリ秒単位では小さすぎて、あまり正確ではない可能性があるため、これらのコマンドをループで数千回実行して平均を取ることができます。また、保存された M 関数を実行すると、スクリプトやコマンド プロンプトを実行するよりも高速になることがあります。
どちらの方法でも、割り当ては通常 1 回行われるため、さらに 30 ミリ秒かかるかどうかは誰が気にします :)
セル配列または構造体の配列でも同様の動作が見られます。次の例を検討してください。
N = 1000;
tic, a = cell(N,N); toc
tic, b = repmat({[]}, [N,N]); toc
tic, c{N,N} = []; toc
与える:
Elapsed time is 0.001245 seconds.
Elapsed time is 0.040698 seconds.
Elapsed time is 0.004846 seconds.
それらがすべて等しい場合でも、それらは異なる量のメモリを占有することに注意してください。
>> assert(isequal(a,b,c))
>> whos a b c
Name Size Bytes Class Attributes
a 1000x1000 8000000 cell
b 1000x1000 112000000 cell
c 1000x1000 8000104 cell
実際、MATLAB はおそらく複数のコピーを作成するのではなく、すべてのセルに対して同じ空行列を共有しているため、状況はここではもう少し複雑です。
セル配列a
は、実際には初期化されていないセルの配列 (NULL ポインターの配列) であり、一方、b
は各セルが空の配列であるセル配列です[]
(内部的にも、データ共有のために、最初のセルのみがb{1}
指し[]
、残りはすべて最初のセルへの参照)。最後の配列は(初期化されていないセル) にc
似ていますが、最後の配列には空の数値行列が含まれています。a
[]
libmx.dll
( Dependency Walkerツールを使用して)からエクスポートされた C 関数のリストを調べたところ、興味深いことがいくつか見つかりました。
初期化されていない配列を作成するための文書化されていない関数があります: mxCreateUninitDoubleMatrix
、mxCreateUninitNumericArray
、およびmxCreateUninitNumericMatrix
。実際、File Exchangeでは、これらの機能を利用して、より高速な代替機能を提供していzeros
ます。
という文書化されていない関数が存在しますmxFastZeros
。オンラインでグーグルすると、この質問が MATLAB Answers にもクロス投稿されていることがわかります。そこにはいくつかの優れた回答があります。James Tursa (以前の UNINIT の作者と同じ) は、この文書化されていない関数の使用方法の例を示しました。
libmx.dll
tbbmalloc.dll
共有ライブラリにリンクされています。これはIntel TBBスケーラブル メモリ アロケータです。このライブラリは、並列アプリケーション用に最適化された同等のメモリ割り当て関数 ( malloc
、calloc
、 ) を提供します。free
多くの MATLAB 関数は自動的にマルチスレッド化zeros(..)
されるため、がマルチスレッド化され、行列のサイズが十分に大きくなれば Intel のメモリ アロケータを使用しても驚かないでしょう(これは、この事実を確認するLoren Shureによる最近のコメントです)。
メモリ アロケータに関する最後のポイントについては、 @PavanYalamanchiliと同様のベンチマークを C/C++ で作成し、使用可能なさまざまなアロケータを比較できます。このようなもの。MATLAB は、、または関数を使用して MEX ファイルに割り当てられたメモリを自動的に解放するため、 MEX ファイルのメモリ管理オーバーヘッドはわずかに高くなります。価値があるのは、以前のバージョンでは内部メモリ マネージャーを変更することが可能だったことです。mxCalloc
mxMalloc
mxRealloc
編集:
これは、議論された代替案を比較するためのより完全なベンチマークです。割り当てられたマトリックス全体の使用を強調すると、3 つの方法はすべて同等の立場にあり、違いはごくわずかであることを具体的に示しています。
function compare_zeros_init()
iter = 100;
for N = 512.*(1:8)
% ZEROS(N,N)
t = zeros(iter,3);
for i=1:iter
clear z
tic, z = zeros(N,N); t(i,1) = toc;
tic, z(:) = 9; t(i,2) = toc;
tic, z = z + 1; t(i,3) = toc;
end
fprintf('N = %4d, ZEROS = %.9f\n', N, mean(sum(t,2)))
% z(N,N)=0
t = zeros(iter,3);
for i=1:iter
clear z
tic, z(N,N) = 0; t(i,1) = toc;
tic, z(:) = 9; t(i,2) = toc;
tic, z = z + 1; t(i,3) = toc;
end
fprintf('N = %4d, GROW = %.9f\n', N, mean(sum(t,2)))
% ZEROS(N,0)*ZEROS(0,N)
t = zeros(iter,3);
for i=1:iter
clear z
tic, z = zeros(N,0)*zeros(0,N); t(i,1) = toc;
tic, z(:) = 9; t(i,2) = toc;
tic, z = z + 1; t(i,3) = toc;
end
fprintf('N = %4d, MULT = %.9f\n\n', N, mean(sum(t,2)))
end
end
以下は、行列サイズの増加に関して、100回の反復で平均化されたタイミングです。R2013a でテストを実行しました。
>> compare_zeros_init
N = 512, ZEROS = 0.001560168
N = 512, GROW = 0.001479991
N = 512, MULT = 0.001457031
N = 1024, ZEROS = 0.005744873
N = 1024, GROW = 0.005352638
N = 1024, MULT = 0.005359236
N = 1536, ZEROS = 0.011950846
N = 1536, GROW = 0.009051589
N = 1536, MULT = 0.008418878
N = 2048, ZEROS = 0.012154002
N = 2048, GROW = 0.010996315
N = 2048, MULT = 0.011002169
N = 2560, ZEROS = 0.017940950
N = 2560, GROW = 0.017641046
N = 2560, MULT = 0.017640323
N = 3072, ZEROS = 0.025657999
N = 3072, GROW = 0.025836506
N = 3072, MULT = 0.051533432
N = 3584, ZEROS = 0.074739924
N = 3584, GROW = 0.070486857
N = 3584, MULT = 0.072822335
N = 4096, ZEROS = 0.098791732
N = 4096, GROW = 0.095849788
N = 4096, MULT = 0.102148452