MATLAB Language
パフォーマンスとベンチマーク
サーチ…
備考
- コードをプロファイリングすることは、開発者が実際に最適化作業を正当化するコードの部分に焦点を当てることによって、「 時期尚早の最適化 」という恐ろしい習慣を回避する方法です。
- 「 プログラムのパフォーマンスを測定する 」MATLABドキュメンテーションの記事
プロファイラを使用したパフォーマンスボトルネックの特定
MATLAB Profilerは、MATLABコードのソフトウェアプロファイリングツールです。プロファイラを使用すると、実行時間とメモリ消費の両方を視覚的に表示することができます。
プロファイラを実行するには、次の2つの方法があります。
MATLAB GUIで "Run and Time"ボタンをクリックし、エディタで
.m
ファイルを開きます( R2012bに追加)。プログラムを使用して、
profile on <some code we want to test> profile off
以下に、サンプルコードとそのプロファイリングの結果を示します。
function docTest
for ind1 = 1:100
[~] = var(...
sum(...
randn(1000)));
end
spy
上記から、 spy
関数は実行時間の約25%を占めることがわかります。 「実コード」の場合、このような実行時間の割合が大きい関数は、最適化を避けるべきvar
およびcla
類似した関数ではなく、最適化の候補となります。
さらに、 機能名列の項目をクリックすると、その項目の実行時間の詳細を見ることができます。 spy
をクリックする例です:
また、Profilerを実行する前にprofile('-memory')
実行してメモリ消費量をプロファイルすることもできます。
複数の関数の実行時間の比較
広く使われているtic
とtoc
組み合わせは、関数やコードスニペットの実行時間を大まかに知ることができます。
いくつかの関数を比較するためには使用すべきではありません。どうして?上記のソリューションを使用してスクリプト内で比較するために、すべてのコードスニペットに等しい条件を指定することはほとんど不可能です。関数が同じ関数空間と共通変数を共有しているので、あとで呼び出される関数とコードスニペットは、すでに初期化された変数と関数を既に利用しています。また、JITコンパイラがこれらのスニペットと同じように処理するかどうかについても洞察はありません。
ベンチマークの専用機能はtimeit
です。次の例は、その使用方法を示しています。
配列A
と行列B
ます。異なる要素の数を数えることによって、 B
のどの行がA
最も類似しているかを判断する必要があります。
function t = bench()
A = [0 1 1 1 0 0];
B = perms(A);
% functions to compare
fcns = {
@() compare1(A,B);
@() compare2(A,B);
@() compare3(A,B);
@() compare4(A,B);
};
% timeit
t = cellfun(@timeit, fcns);
end
function Z = compare1(A,B)
Z = sum( bsxfun(@eq, A,B) , 2);
end
function Z = compare2(A,B)
Z = sum(bsxfun(@xor, A, B),2);
end
function Z = compare3(A,B)
A = logical(A);
Z = sum(B(:,~A),2) + sum(~B(:,A),2);
end
function Z = compare4(A,B)
Z = pdist2( A, B, 'hamming', 'Smallest', 1 );
end
このベンチマークのこの方法は、最初にこの答えで見られました。
「シングル」になるのは大丈夫です!
概要:
MATLABの数値配列のデフォルトのデータ型はdouble
です。 double
は数値の浮動小数点表現であり 、この形式は値ごとに8バイト(または64ビット)を要します。 いくつかのケースでは、例えば、整数でのみ扱う場所や数値不安定性が差し迫っ問題ではないときに、このような高いビット深度が必要とされないことがあります。このような理由から、の利点を検討することをお勧めしsingle
の精度(または他の適切なタイプを ):
- より速い実行時間(特にGPUで顕著)。
- メモリ消費量の半分:メモリ不足エラーのために
double
障害が発生した場合は成功するかもしれません。ファイルとして保存するときはよりコンパクトです。
変数をサポートされている任意のデータ型からsingle
変数に変換するには、以下を使用します。
sing_var = single(var);
デフォルトでdouble
値を出力する一般的に使用される関数( zeros
、 eye
、 ones
など )は、出力の型/クラスを指定することができます。
スクリプト内の変数をデフォルト以外の精度/型/クラスに変換する:
2016年7月現在、 デフォルトの MATLABデータ型をdouble
から変更する方法はありません。
MATLABでは、新しい変数は通常、作成時に使用される変数のデータ型を模倣します。これを説明するために、次の例を考えてみましょう:
A = magic(3);
B = diag(A);
C = 20*B;
>> whos C
Name Size Bytes Class Attributes
C 3x1 24 double
A = single(magic(3)); % A is converted to "single"
B = diag(A);
C = B*double(20); % The stricter type, which in this case is "single", prevails
D = single(size(C)); % It is generally advised to cast to the desired type explicitly.
>> whos C
Name Size Bytes Class Attributes
C 3x1 12 single
したがって、いくつかの初期変数をキャスト/変換してコード全体に変更が浸透しているように見えるかもしれませんが、これは推奨されません (後述の注意点と落とし穴を参照)。
注意点と落とし穴:
数値ノイズ(
single
からdouble
single
までのキャスト時)や情報の損失(double
からsingle
double
に、または特定の整数型間でキャストする場合)の導入により、繰り返しの変換はお勧めできません 。double(single(1.2)) == double(1.2) ans = 0
これは、
typecast
を使用していくらか軽減できます。 浮動小数点の不正確さに注意してください。暗黙的なデータタイピング(すなわち、MATLABが計算の出力のタイプを推測するもの)のみに依存することは、いくつかの望ましくない影響が生じる可能性があるため、 推奨されません。
情報の損失 :
double
結果が予想されるが、single
オペランドとdouble
オペランドの不注意な組み合わせがsingle
精度をもたらす。予期しないほど高いメモリ消費 :
single
結果が期待されるが、不注意な計算の結果、double
出力となる。GPUで作業する場合の不要なオーバーヘッド :
gpuArray
タイプ(つまりVRAMに格納された変数)と非gpuArray
変数( 通常はRAMに格納されている変数)をgpuArray
させる場合、計算を実行する前にデータを一方向に転送する必要があります。この操作には時間がかかり、繰り返し計算では非常に目立つことがあります。整数型と浮動小数点型を混在させるときのエラー : 整数型と浮動小数点型の混在入力に対しては、
mtimes
(*
)などの関数は定義されていません。times
(.*
)のような関数は、整数型の入力に対しては全く定義されていません。また、再びエラーになります。>> ones(3,3,'int32')*ones(3,3,'int32') Error using * MTIMES is not fully supported for integer classes. At least one input must be scalar. >> ones(3,3,'int32').*ones(3,3,'double') Error using .* Integers can only be combined with integers of the same class, or scalar doubles.
コードの可読性を向上させ、不要なタイプのリスクを減らすために、変数が明示的に目的のタイプにキャストされる防御的なアプローチが推奨されます。
関連項目:
- MATLABドキュメンテーション: 浮動小数点数 。
- Mathworksの技術記事: MATLABコードを固定小数点に変換するためのベストプラクティス
NDアレイを再配置すると、全体のパフォーマンスが向上することがあります
場合によっては、一連のND配列に関数を適用する必要があります。この簡単な例を見てみましょう。
A(:,:,1) = [1 2; 4 5];
A(:,:,2) = [11 22; 44 55];
B(:,:,1) = [7 8; 1 2];
B(:,:,2) = [77 88; 11 22];
A =
ans(:,:,1) =
1 2
4 5
ans(:,:,2) =
11 22
44 55
>> B
B =
ans(:,:,1) =
7 8
1 2
ans(:,:,2) =
77 88
11 22
どちらの行列も3Dですが、次のように計算する必要があります。
result= zeros(2,2);
...
for k = 1:2
result(i,j) = result(i,j) + abs( A(i,j,k) - B(i,j,k) );
...
if k is very large, this for-loop can be a bottleneck since MATLAB order the data in a column major fashion. So a better way to compute "result" could be:
% trying to exploit the column major ordering
Aprime = reshape(permute(A,[3,1,2]), [2,4]);
Bprime = reshape(permute(B,[3,1,2]), [2,4]);
>> Aprime
Aprime =
1 4 2 5
11 44 22 55
>> Bprime
Bprime =
7 1 8 2
77 11 88 22
今度は、上記のループを次のように置き換えます。
result= zeros(2,2);
....
temp = abs(Aprime - Bprime);
for k = 1:2
result(i,j) = result(i,j) + temp(k, i+2*(j-1));
...
私たちはキャッシュメモリを利用できるようにデータを並べ替えました。並べ替えと再形成にはコストがかかる可能性がありますが、大きなND配列で作業する場合、配列にない配列を使用するよりも、これらの演算に関連する計算コストが大幅に低くなります。
事前配分の重要性
MATLABの配列はメモリ内の連続ブロックとして保持され、MATLABによって自動的に割り当てられ、解放されます。 MATLABは、配列のサイズ変更などのメモリ管理操作を、使いやすい構文の背後に隠します。
a = 1:4
a =
1 2 3 4
a(5) = 10 % or alternatively a = [a, 10]
a =
1 2 3 4 10
これは重要なことですが、 a(5) = 10
はMATLABにサイズ5の新しいメモリブロックを割り当て、最初の4つの数値をコピーし、5番目を10に設定します。これはO(numel(a))
操作であり、 O(1)
ではありません。
次の点を考慮してください。
clear all
n=12345678;
a=0;
tic
for i = 2:n
a(i) = sqrt(a(i-1)) + i;
end
toc
Elapsed time is 3.004213 seconds.
a
はこのループでn
回再割り当てされます(MATLABによって行われるいくつかの最適化を除く)。 MATLABは警告メッセージを表示します。
"変数 'a'はループの繰り返しごとにサイズが変わるようですが、速度を事前に割り当てることを検討してください。"
私たちが事前に配属するとどうなりますか?
a=zeros(1,n);
tic
for i = 2:n
a(i) = sqrt(a(i-1)) + i;
end
toc
Elapsed time is 0.410531 seconds.
ランタイムが一桁減少していることがわかります。
事前配分の方法:
MATLABは、ユーザーの特定の要件に応じて、ベクトルと行列の割り当てにさまざまな機能を提供します。これらには、 zeros
、 ones
、 nan
、 eye
、 true
などが含まれtrue
。
a = zeros(3) % Allocates a 3-by-3 matrix initialized to 0
a =
0 0 0
0 0 0
0 0 0
a = zeros(3, 2) % Allocates a 3-by-2 matrix initialized to 0
a =
0 0
0 0
0 0
a = ones(2, 3, 2) % Allocates a 3 dimensional array (2-by-3-by-2) initialized to 1
a(:,:,1) =
1 1 1
1 1 1
a(:,:,2) =
1 1 1
1 1 1
a = ones(1, 3) * 7 % Allocates a row vector of length 3 initialized to 7
a =
7 7 7
データ型も指定できます。
a = zeros(2, 1, 'uint8'); % allocates an array of type uint8
既存の配列のサイズを複製することも簡単です:
a = ones(3, 4); % a is a 3-by-4 matrix of 1's
b = zeros(size(a)); % b is a 3-by-4 matrix of 0's
そして、タイプをクローン:
a = ones(3, 4, 'single'); % a is a 3-by-4 matrix of type single
b = zeros(2, 'like', a); % b is a 2-by-2 matrix of type single
「like」は複雑さと希薄性もクローン化することに注意してください。
事前kron
、 rand
、 gallery
、 kron
、 bsxfun
、 colon
など、必要な最終サイズの配列を返す関数を使用して暗黙的に実現されます。たとえば、線形に変化する要素を持つベクトルを割り当てる一般的な方法は、コロン演算子(2または3オペランドバリアント1を使用 )を使用することです。
a = 1:3
a =
1 2 3
a = 2:-3:-4
a =
2 -1 -4
セル配列は、 zeros()
とほぼ同じ方法でcell()
関数を使用して割り当てることができます。
a = cell(2,3)
a =
[] [] []
[] [] []
セル配列は、セルの内容を記憶している場所へのポインタを保持することによって機能することに注意してください。したがって、すべての事前割り当てのヒントは個々のセル配列要素にも適用されます。
参考文献:
- 「 メモリの事前割り当て 」に関する正式なMATLABのドキュメント 。
- 「 MATLABによるメモリの割り当て方法 」に関する公式のMATLABドキュメント
- 文書化されていないmatlabの 事前割り当てパフォーマンス 。
- MATLABの技術におけるLorenの 配列の事前割り付けの理解