MATLAB ユーザーコミュニティー

MATLAB & Simulink ユーザーコミュニティー向け日本語ブログ

MATLAB R2023a アップデート:関数ハンドルの高速化

※この投稿は 2023 年 3 月 16 日に The MATLAB blog (Mike Croucher) に投稿されたものの抄訳です。


MathWorks が MATLAB の処理速度を上げる方法は大きく分けて 2 つあります。個々の関数に潜り込んでオーバーヘッドを除去したり、アルゴリズムを改善したりする方法と、MATLAB 言語自体のパフォーマンスを向上させて高速化する方法です。
今日は、R2023a リリースで関数ハンドルの高速化が行われた、後者の例を見てみましょう。

 

関数ハンドルって何?

ドキュメントからそのまま引用すると・・「関数ハンドルは、関数を表す MATLAB® データ型です。一般に、関数ハンドルは関数を別の関数に渡すために使用します。たとえば、関数ハンドルは、所定の値の範囲に対して数式を評価する関数の入力引数として使用できます。」
例えば、こんな関数があったとします。
function y = computeSquare(x)
y = x.^2;
end
この関数にたいして関数ハンドルを作って、
f = @computeSquare
値をもつ f = function_handle:
@computeSquare
4 の二乗をこんな感じで計算できます。
b = f(4)
b = 16
この「それがどうした・・」的に見える機能は非常に便利で、MATLAB コードのいたるところで使われているのを見かけると思います。

 

関数ハンドルの速度チェック

関数ハンドルを使って、結果を保存せずに 1 億個の数字の二乗を計算してみましょう。特に意味のない計算ですが、R2023a で関数ハンドルのオーバーヘッドが大幅に削減されていることがわかります。
n = 1e8;
tic
for i = 1:n
out = f(i);
end
t = toc
t = 0.7665
私の PC ではこんな結果でした。
  • R2022b: 33 seconds
  • R2023a: 0.77 seconds
R2023a で 40 倍強の高速化となっています。パスやローカル関数への関数ハンドルは直接の関数呼び出しと同じくらいに高速化されています。
もう少し現実的な例を見てみましょう。

 

関数ハンドルの高速化 = ODE ソルバーの高速化

この例は、bench コマンド内で評価される関数から取ってきています。vanderpol 関数は bench の最後に定義されており、ここでは@vanderpol というハンドル名で呼び出しています。計算を 5 回繰り返し、一番良い結果を取っています。
% ODE. van der Pol equation, mu = 1
y0 = [2; 0];
tspan = [0 20000];
numRepeats = 5; % Number of times I’ll repeat the computation. I’ll later only report the best one.
t = zeros(1,numRepeats); % Store the timing results
for count=1:numRepeats
tic
[s,y] = ode45(@vanderpol,tspan,y0);
t(count) = toc;
end
fprintf(“ode45 best time is %.2f seconds\n”,min(t))
ode45 best time is 0.21 seconds
  • R2022b: 0.4 seconds
  • R2023a: 0.21 seconds
ほぼ 2 倍のスピードアップです。これは、先に見た 40 倍のスピードアップには程遠いですが、 ode45 では関数呼び出し以外にもいろいろなことが行われているからですね。

 

最適化ソルバーはどうだろう?

他にも、関数ハンドルを何度も使うようなものはある程度高速化されるはずです。ODE の次に考えたのは最適化。最適化処理でも目的関数が何度も呼ばれています。ここでは、シミュレーテッドアニーリングを使った Global Optimisation Toolbox の簡単な例を紹介します。
numRepeats = 5; % Number of times I’ll repeat the computation. I’ll later only report the best one.
t = zeros(1,numRepeats); % Store the timing results
for count=1:numRepeats
rng default
x0 = [0.5 0.5];
tic;[x,fval,exitFlag,output] = simulannealbnd(@objective,x0);
t(count) = toc;
end
Optimization terminated: change in best function value less than options.FunctionTolerance.
Optimization terminated: change in best function value less than options.FunctionTolerance.
Optimization terminated: change in best function value less than options.FunctionTolerance.
Optimization terminated: change in best function value less than options.FunctionTolerance.
Optimization terminated: change in best function value less than options.FunctionTolerance.
5 回実行した中で一番速かったものは・・
fprintf(“Simulated annealing best time is %.2f seconds\n”,min(t))
Simulated annealing best time is 0.16 seconds
で何回関数が呼ばれているかというと・・
fprintf(“The optimiser made %d function calls\n”,output.funccount);
The optimiser made 2971 function calls

R2022b と R2023a での結果はこちら。

  • R2022b: 0.19 seconds
  • R2023a: 0.16 seconds
残念ながらあまり速くなっていませんね。関数ハンドルの高速化では違いがでない例でした。3000 回の目的関数評価で節約できる関数ハンドルのオーバーヘッドは、私の PC では0.01 秒にも満たないと予想してますが、速くなってることは確かですね。

 

フィードバックお待ちしています

関数ハンドルを多用するコードで高速化を実感しているものがあれば、コメント欄やツイッターで是非教えてください。
function y = computeSquare(x)
%Used to show function handle overheads
y = x.^2;
end
function dydt = vanderpol(~,y)
%VANDERPOL Evaluate the van der Pol ODEs for mu = 1
%used for the ode45 demo
dydt = [y(2); (1-y(1)^2)*y(2)-y(1)];
end
function y = objective(x)
%used for simulated annealing demo
y = (4-2.1.*x(1).^2+x(1).^4./3).*x(1).^2+x(1).*x(2)+(-4+4.*x(2).^2).*x(2).^2;
end
|
  • print

Comments

To leave a comment, please click here to sign in to your MathWorks Account or create a new one.