序文
これらのテストはすべて、他のユーザーと共有されているマシンで実行されるため、完全にクリーンな環境ではありません。各テストの合間に、ワークスペースをクリアしてメモリを解放します。
個々の数値には注意を払わず、最適化前と最適化後の時間の違いだけを見てください。
注:コードに配置した呼び出しtic
とtoc
呼び出しは、所要時間を測定している場所を示すためのものです。
事前割り当て
Matlab で配列を事前に割り当てるという単純な行為により、速度が大幅に向上します。
tic;
for i = 1:100000
my_array(i) = 5 * i;
end
toc;
これには47秒かかります
tic;
length = 100000;
my_array = zeros(1, length);
for i = 1:length
my_array(i) = 5 * i;
end
toc;
これには0.1018秒かかります
追加された 1 行のコードで 47 秒から 0.1 秒に短縮されたことは、驚くべき改善です。明らかに、この単純な例では、ベクトル化できますmy_array = 5 * 1:100000
( 0.000423秒かかりました) が、ベクトル化がオプションではない、より複雑な時間を表現しようとしています。
私は最近、zeros 関数 (および同じ性質の他の関数) は、最後の値を 0 に設定するだけで事前割り当てが速くないことを発見しました。
tic;
length = 100000;
my_array(length) = 0;
for i = 1:length
my_array(i) = 5 * i;
end
toc;
これには0.0991秒かかります
明らかに、この小さな違いはあまり証明されていませんが、これらの最適化の多くを含む大きなファイルについて私を信じなければならないでしょう.違いはより明白になります.
なぜこれが機能するのですか?
事前割り当てメソッドは、作業用にメモリのチャンクを割り当てます。このメモリは連続しており、C++ や Java の配列と同様に、事前に取得できます。ただし、事前に割り当てない場合、MATLAB は使用するメモリを動的にさらに多く見つけなければなりません。私が理解しているように、これはJava ArrayListとは異なる動作をし、配列のさまざまなチャンクがメモリ内のいたるところに分割されているLinkedListに似ています。
これは、データを書き込むときに遅くなるだけでなく (47 秒!)、それ以降はアクセスするたびに遅くなります。実際、事前割り当てが絶対にできない場合でも、使用を開始する前に、マトリックスを新しい事前割り当て済みのマトリックスにコピーすると便利です。
どのくらいのスペースを割り当てればよいかわからない場合はどうすればよいですか?
これはよくある質問で、いくつかの異なる解決策があります。
- 過大評価-スペースを過少に割り当てるよりも、マトリックスのサイズを大幅に過大評価し、スペースを割り当てすぎる方が適切です。
- それに対処し、後で修正します -開発者が遅いポピュレーション時間に我慢して、マトリックスを新しい事前割り当てスペースにコピーした場合、これをよく見ました。通常、これは
.mat
後ですぐに読めるようにファイルなどに保存されます。
複雑な構造を事前に割り当てるにはどうすればよいですか?
すでに見たように、単純なデータ型の事前割り当ては簡単ですが、構造体の構造体などの非常に複雑なデータ型の場合はどうなるでしょうか?
これらを明示的に事前に割り当てることはできなかったので(誰かがより良い方法を提案してくれることを願っています)、この単純なハックを思いつきました:
tic;
length = 100000;
% Reverse the for-loop to start from the last element
for i = 1:length
complicated_structure = read_from_file(i);
end
toc;
これには1.5分かかります
tic;
length = 100000;
% Reverse the for-loop to start from the last element
for i = length:-1:1
complicated_structure = read_from_file(i);
end
% Flip the array back to the right way
complicated_structure = fliplr(complicated_structure);
toc;
これには6秒かかります
これは明らかに完全な事前割り当てではなく、後で配列を反転するのに少し時間がかかりますが、時間の改善がそれを物語っています。誰かがこれを行うためのより良い方法を持っていることを願っていますが、これは当面はかなり良いハックです.
データ構造
メモリ使用量に関しては、構造体の配列は配列の構造体よりも桁違いに悪いです。
% Array of Structs
a(1).a = 1;
a(1).b = 2;
a(2).a = 3;
a(2).b = 4;
624バイトを使用
% Struct of Arrays
a.a(1) = 1;
a.b(1) = 2;
a.a(2) = 3;
a.b(2) = 4;
384バイトを使用
ご覧のとおり、この単純な/小さな例でも、構造体の配列は配列の構造体よりも多くのメモリを使用します。また、データをプロットする場合は、配列の構造体の方が便利な形式です。
各 Struct には大きなヘッダーがあり、ご覧のとおり、構造体の配列はこのヘッダーを複数回繰り返しますが、配列の構造体にはヘッダーが 1 つしかないため、使用するスペースが少なくなります。この違いは、配列が大きいほど顕著になります。
ファイル読み取り
コード内の (またはそのシステム コールの) 数が少ないほどfreads
、より良い結果が得られます。
tic;
for i = 1:100
fread(fid, 1, '*int32');
end
toc;
前のコードは、次のコードよりもかなり遅くなります。
tic;
fread(fid, 100, '*int32');
toc;
当たり前のことだと思うかもしれませんが、同じ原則をより複雑なケースにも適用できます。
tic;
for i = 1:100
val1(i) = fread(fid, 1, '*float32');
val2(i) = fread(fid, 1, '*float32');
end
toc;
この問題は単純ではなくなりました。これは、メモリ内でフロートが次のように表現されるためです。
val1 val2 val1 val2 etc.
ただしskip
、 fread の値を使用して、以前と同じ最適化を実現できます。
tic;
% Get the current position in the file
initial_position = ftell(fid);
% Read 100 float32 values, and skip 4 bytes after each one
val1 = fread(fid, 100, '*float32', 4);
% Set the file position back to the start (plus the size of the initial float32)
fseek(fid, position + 4, 'bof');
% Read 100 float32 values, and skip 4 bytes after each one
val2 = fread(fid, 100, '*float32', 4);
toc;
したがって、このファイルfread
の読み取りは 200 ではなく 2 を使用して達成され、大幅な改善が行われました。
関数呼び出し
私は最近、多くの関数呼び出しを使用するいくつかのコードに取り組みましたが、それらはすべて別のファイルにありました。つまり、100 個の個別のファイルがあり、すべてが相互に呼び出しているとしましょう。このコードを 1 つの関数に「インライン化」することで、実行速度が 9 秒から 20% 向上しました。
明らかに、再利用性を犠牲にしてこれを行うことはありませんが、私の場合、関数は自動的に生成され、まったく再利用されませんでした. しかし、これからも学ぶことができ、実際には必要のない過度の関数呼び出しを避けることができます。
外部 MEX 関数を呼び出すと、オーバーヘッドが発生します。したがって、大きな MEX 関数を 1 回呼び出す方が、小さな MEX 関数を何度も呼び出すよりもはるかに効率的です。
多くの切断された線をプロットする
垂直線のセットなどの切断されたデータをプロットする場合、Matlab でこれを行う従来の方法は、line
またはplot
を使用して複数の呼び出しを繰り返すことhold on
です。ただし、プロットする個々の線が多数ある場合、これは非常に遅くなります。
NaN
私が見つけた手法は、プロットするデータに値を導入できるという事実を利用しており、データが壊れる原因となります。
以下の不自然な例では、x_values、y1_values、および y2_values (行は [x, y1] から [x, y2] まで) のセットを への単一の呼び出しに適した形式に変換しますplot
。
例えば:
% Where x is 1:1000, draw vertical lines from 5 to 10.
x_values = 1:1000;
y1_values = ones(1, 1000) * 5;
y2_values = ones(1, 1000) * 10;
% Set x_plot_values to [1, 1, NaN, 2, 2, NaN, ...];
x_plot_values = zeros(1, length(x_values) * 3);
x_plot_values(1:3:end) = x_values;
x_plot_values(2:3:end) = x_values;
x_plot_values(3:3:end) = NaN;
% Set y_plot_values to [5, 10, NaN, 5, 10, NaN, ...];
y_plot_values = zeros(1, length(x_values) * 3);
y_plot_values(1:3:end) = y1_values;
y_plot_values(2:3:end) = y2_values;
y_plot_values(3:3:end) = NaN;
figure; plot(x_plot_values, y_plot_values);
この方法を使用して何千もの小さな線を印刷しましたが、パフォーマンスの向上は計り知れません。最初のプロットだけでなく、ズームやパン操作などのその後の操作のパフォーマンスも向上しました。