メインコンテンツ

グラフィックス パフォーマンスの向上

データの可視化を作成する際、複数の手法を使用してパフォーマンスを改善できます。このトピックでは、パフォーマンスの問題を特定し、より効率的なコーディング パターンや機能を選択するための手法を示します。作成するグラフィックスのタイプに応じて有効な手法を使用してください。

  • コード内のボトルネックの特定 — プロファイラーを使用して、実行時間の長い関数を特定します。

  • オブジェクトの再作成と不要な検索の回避 — オブジェクトをループで繰り返し作成する代わりに、各オブジェクトのインスタンスを 1 つ作成し、それらを変数として格納します。その後、ループを使用して、オブジェクトの変化するプロパティのみを変更できます。

  • 配列の事前割り当て — ループの内部で新しい要素を追加する代わりに、ループの前で配列を事前に割り当てます。

  • 新しい機能の使用による速度の向上 — より高速で柔軟性が高い新しい機能を使用するようにコードの更新を検討します。

  • 不要な機能の無効化 — 既定で有効になっている不要な機能を無効にすることを検討します。

  • drawnow の戦略的な使用 — パフォーマンスに影響する可能性がある余分な drawnow コマンドを特定し、コマンドを効果的に使用するためのさまざまな方法を確認します。

コード内のボトルネックの特定

MATLAB® プロファイラー アプリを使用して、実行時間の長い関数を特定します。これにより、それらの関数のパフォーマンス改善の可能性を評価できます。

たとえば、myPlot 関数を使用して 10 行 500 列の配列の散布図を作成します。

function myPlot
x = rand(10,500);
y = rand(10,500);
scatter(x,y,"blue");
end

プロファイラーを使用して関数 myPlot の実行時間を測定します。コードの実行には約 2.7 秒かかっています。

profile on
myPlot
profile viewer

Flame graph in the profile summary. The myPlot function accounts for 99.9% of the code execution time and takes 2.736 seconds to run.

x 配列と y 配列には 500 列のデータが含まれているため、scatter 関数は 500 個の Scatter オブジェクトを作成します。この場合、代替方法として、5,000 個のデータ点をもつ 1 つのオブジェクトを作成して同じデータをプロットできます。

function myPlot
   x = rand(10,500);
   y = rand(10,500);
   scatter(x(:),y(:),"blue");
end

コードを再度プロファイリングすると、更新後の関数の実行時間は 0.3 秒未満になることがわかります。

profile on
myPlot
profile viewer

Flame graph in the profile summary. The myPlot function accounts for 98.5% of the code execution time and takes 0.262 second to run.

プロファイラーの使用に関する詳細については、パフォーマンス向上のためのコードのプロファイリングを参照してください。

オブジェクトの再作成と不要な検索の回避

コードで後で変更する予定のグラフィックス オブジェクトを作成する場合は、次のベスト プラクティスを使用してパフォーマンスを改善できます。

  • オブジェクトを再作成する代わりに、プロパティを設定することで変更が必要なデータだけを更新します。

  • 後でアクセスするときに検索しなくても済むように、オブジェクトを変数として保存します。後でアクセスできるように変数を作成しておくと、findobj 関数や findall 関数でグラフィックス階層を検索するよりも効率的です。

たとえば、次のコードは表面とマーカーをプロットし、マーカーを m という名前の Line オブジェクトとして格納します。その後に、for ループでマーカーの座標を更新します。ループの各反復では、マーカーの位置のみが変化します。表面プロットに関連付けられたすべてのデータ、およびマーカー プロパティの多くは、各ステップで変化しません。surf 関数と plot3 関数を複数回呼び出してすべてのデータを更新するのではなく、Line オブジェクトの位置を制御するプロパティのみを更新します。

[sx,sy,sz] = peaks(500);
nframes = 490;

surf(sx,sy,sz,EdgeColor="none")
hold on
m = plot3(sx(1,1),sy(1,1),sz(1,1),"o", ...
    MarkerFaceColor="red", ...
    MarkerSize=14);
hold off

for t = 1:nframes
    m.XData = sx(t,t);
    m.YData = sy(t,t);
    m.ZData = sz(t,t) + 0.5;
    drawnow
end

Animation of a marker moving across a surface

たとえば経時的に収集するデータをプロットするなど、ループで大きくなるデータをアニメーション化するには、animatedline オブジェクトの作成を検討してください。このオブジェクトで、addpoints 関数を使用してラインに点を追加します (例についてはdrawnow の戦略的な使用を参照)。あるいは、データをセグメント単位で更新することを検討します。この方法の詳細については、データのセグメント化による更新時間の短縮を参照してください。

配列の事前割り当て

配列を事前に割り当て、ループで要素を埋めていきます。配列の末尾に新しい要素をループで追加するよりも、この手法の方が効率的です。

たとえば、axes オブジェクトの配列をループで作成するには次のようにします。最初に、gobjects 関数を使用して GraphicsPlaceholder オブジェクトの配列を作成します。次に、GraphicsPlaceholder オブジェクトを axes オブジェクトにループで置き換えます。

numaxes = 100;
ax = gobjects(numaxes,1);
t = tiledlayout(10,10);
for i=1:numaxes
   ax(i) = nexttile;
end

同様に、NaN 値を使用してプロットを初期化し、その後にループでデータを取り込むことができます。たとえば、1,000 個の NaN 値のラインをプロットします。その後、ラインの XData プロパティと YData プロパティをループで設定して NaN 値を数値に置き換えます。

x = NaN(1000,1);
y = NaN(1000,1);
p = plot(x,y);
hold on
for i = 1:10:1000
    p.XData(i:i+9) = i:i+9;
    p.YData(i:i+9) = rand(10,1);
    drawnow
end
hold off

新しい機能の使用による速度の向上

パフォーマンスを改善する方法の 1 つは、可能な場合は新しい機能を使用するようにコードを更新することです。古い機能に置き換わる新しい機能があるほか、コードの速度を向上できるオプションの機能強化もあります。このセクションでは、検討すべき新しい機能をいくつか示します。

setget の代わりにドット表記を使用する

グラフィックス オブジェクトのプロパティの設定と取得には、set 関数と get 関数よりも、ドット表記を使用した方がパフォーマンスが高くなります。たとえば、Figure を作成し、Color プロパティを取得して、その値を clr 変数に格納します。その後、Figure の Name プロパティを "My Figure" に設定します。

f = figure;
clr = f.Color;
f.Name = "My Figure";

タブ付きコンテナーで Figure を表示する

R2025a 以降

既定では、Figure は Figure コンテナー内にタブとして表示されます。タブを再編成してレイアウトをカスタマイズしたり、複数の Figure をタイルに配置して同時に表示したりできます。Figure コンテナーを使用すると、複数の Figure 用に 1 つの共有のリソース セット (Figure ツールストリップなど) が提供されるため、パフォーマンス上の利点があります。一方、アンドックされた複数の Figure を開くと、Figure ごとに別個のリソース セットが必要になり、その読み込みに追加の時間がかかります。パフォーマンスを高めるには、Figure をコンテナーに格納したまま、アンドックしないことを検討してください。

subplot の代わりに tiledlayout を使用する

tiledlayout 関数を使用して Figure でプロットをタイル表示します。tiledlayout 関数は、subplot 関数で作成されるレイアウトよりも詳細にカスタマイズ可能および構成可能なレイアウトを作成します。タイル表示チャート レイアウトを使用するときのパフォーマンスを高めるには、次のベスト プラクティスに従います。

  • レイアウトを作成するときは固定サイズのグリッドを指定します。たとえば、2 行 2 列のタイル配置を作成するには、tiledlayout("flow") ではなく tiledlayout(2,2) を使用します。固定サイズのグリッドを指定すると、各 axes オブジェクトのサイズと配置を判別するために必要な計算が少なくなります。

  • Figure のサイズを変更するときやプロットにデータを追加するときに MATLAB で範囲が再計算されないように、xlimylim、および zlim 関数を使用して各 axes オブジェクトの範囲を設定します。

  • 座標軸の作成時間を改善するには、axtoolbar 関数を使用して、タイル表示チャート レイアウトで共有の座標軸ツール バーを有効にします。共有の座標軸ツール バーのみを対話機能に使用し、他のメソッドは無効にします。ax.Toolbar=[] を使用して、各 axes オブジェクトの座標軸ツール バーを無効にします。ここで、ax はいずれかの axes オブジェクトです。disableDefaultInteractivity 関数を使用して、組み込みの座標軸操作を無効にします。

表示イメージの解像度を制限する

R2022b 以降

イメージを処理する場合、MaxRenderedResolution プロパティを設定すれば、MATLAB でイメージの大きい次元を表示するときに使用する最大解像度を制御できます。小さい次元は、縦横比を保持するように調整されます。指定する値は画面上の表示に影響しますが、イメージの CData プロパティに格納されているイメージ データには影響しません。

イメージをフル解像度で表示するには、MaxRenderedResolution プロパティを "none" に設定します。表示されるイメージのサイズを制限するには、数値を指定します。より大きい数値 (および "none") を指定すると画質は向上しますが、初期イメージのレンダリングに時間がかかる可能性があります。より小さい数値を指定するとイメージがダウンサンプリングされますが、より高速にレンダリングされます。

特に帯域幅に制約がある状況では、元のイメージの最大次元より小さい値を指定すると、より高速にイメージをレンダリングできます。ただし、1 ピクセルまたは数ピクセル分のみ小さい値を指定すると、そのイメージの初期レンダリングはフル解像度でのレンダリングより長時間かかる場合があります。

たとえば、大きい方の次元を最大 128 ピクセルにして peppers.png (384×512 の RGB イメージ) を表示します。

imdata = imread("peppers.png");
imagesc(imdata,MaxRenderedResolution=128)

メモ

MATLAB Online™imshow 関数を使用してイメージを表示する場合、関数は MaxRenderedResolution の値として 512 を使用します。

以前の操作の代わりに組み込みの座標軸操作を使用する

パン、ズーム、回転には、組み込みの座標軸操作を使用します。これらの操作は既定で利用可能であり、panzoomrotate3d の各関数よりも高速で応答性に優れています。また、WindowButtonMotionFcn などのコールバック関数と比べても、これらの操作の方がはるかに高速で応答性も高くなります。

座標軸の InteractionOptions プロパティを使用して操作をカスタマイズすることもできます。組み込み操作の使用とカスタマイズの詳細については、チャートの対話機能の制御を参照してください。

不要な機能の無効化

グラフィックスのパフォーマンスを改善するもう 1 つの方法は、使用しない機能を無効にすることです。次の表に、その機能を使用するかどうかに関係なく、パフォーマンスに影響する可能性がある機能を示します。

機能パフォーマンスへの影響パフォーマンスの改善方法

組み込みの座標軸操作

組み込みの座標軸操作は、既定で有効になっていて、座標軸の作成時間に影響することがあります。

プロットを操作する必要がない場合は、これらの操作を無効にしてパフォーマンスを改善できます。

disableDefaultInteractivity 関数に座標軸を渡して操作を無効にします。

座標軸ツール バー

座標軸ツール バーは、既定で有効になっていて、座標軸の作成時間に影響することがあります。

プロットを操作する必要がない場合は、座標軸ツール バーを無効にしてパフォーマンスを改善できます。

座標軸を作成した直後に、axes オブジェクトの Toolbar プロパティを空の配列 ([]) に設定して座標軸ツール バーを無効にします。

ax = axes;
ax.Toolbar=[];

座標軸の範囲の自動計算

範囲の計算は、既定で有効になっていて、プロットを作成するときや既存のプロットを更新するときの目盛りラベルの更新時間に影響することがあります。座標軸の範囲の計算は、アニメーションのフリッカーの原因になることもあります。

プロットする前にデータの範囲がわかっている場合は、範囲の自動計算を無効にしてパフォーマンスを改善できます。

座標軸の範囲を静的な値に設定して範囲の自動計算を無効にします。xlimylim、および zlim 関数を使用します。あるいは、関連する座標軸のモード プロパティ (XLimModeYLimMode、および ZLimMode) を "manual" に設定します。

範囲 (または関連するモード プロパティ) は、座標軸に最初にプロットした後に設定します。そうしないと、プロット関数によってプロパティの値がリセットされる可能性があります。追加のプロット関数を呼び出す前に hold 関数を呼び出します。

凡例の自動更新

凡例の自動更新は、既定で有効になっていて、座標軸のオブジェクトを追加または削除するときに Figure に更新が描画されるまでの時間に影響することがあります。

座標軸のすべてのオブジェクトを作成してから凡例を作成します。

drawnow の戦略的な使用

コードでグラフィックス オブジェクトを作成または変更する際、それらの変更が Figure にすぐに表示されないことがあります。多くの場合、drawnow コマンドを使用することで更新を強制できます。ただし、このコマンドは実行に時間がかかり、頻繁に使用するとパフォーマンスに影響する可能性があります。コードを記述するときは、drawnow コマンドが有用な状況と不要な状況を検討してください。

1 つ以上の Figure に変更があった場合、以下の状況では、それらの Figure が自動的に更新されます (つまり、drawnow コマンドを使用する必要はありません)。

  • pausefigureuifigure、または getframe 関数が実行されたとき。

  • 自動計算されるプロパティ (他のプロパティに依存するプロパティ) をコードでクエリしたとき。詳細については、グラフィックス プロパティを取得および設定するコードの最適化を参照してください。

  • デバッガーでのコードの実行が終了して MATLAB コマンド ウィンドウに制御が戻ったとき、またはブレークポイントでコードが停止したとき。

これ以外の状況で意図した効果を得るには drawnow が必要です。次の表に、drawnow コマンドを使用すると効果的な最も一般的な状況を示します。

状況説明

比較的少ない反復回数でアニメーションを作成する

比較的少ない数のフレームを使用してアニメーションを表示するには、オブジェクトを作成し、そのプロパティをループで更新します。各ループ反復で変更を表示するには、ループの最後に drawnow コマンドを追加します。

ラインに沿って移動するマーカーのアニメーションを作成します。ループの各反復でマーカーの座標を変更し、drawnow を呼び出してマーカーの位置が変化する様子を表示します。

x = linspace(0,10,500);
y = sin(x);

% Plot a line and a marker
plot(x,y)
hold on
mkr = scatter(x(1),y(1),[],"red","filled");
hold off
xlim([0 10])
ylim([-1 1])

% Move the marker along the line
for i = 2:length(x)
    mkr.XData = x(i);
    mkr.YData = y(i);
    drawnow
end

長時間かかるアニメーションを高速化する。

長時間かかるアニメーションのパフォーマンスを向上させるには、画面に更新を表示するために drawnow ではなく drawnow limitrate を使用することを検討してください。どちらのコマンドも Figure を更新しますが、drawnow limitrate は更新の回数を 1 秒あたり 20 フレームに制限します。その結果、アニメーションの表示が高速化される可能性があります。

アニメーションですべてのフレームを表示する必要がない場合や、リアルタイム シミュレーション データのプロットなど、最新のフレームを表示することが重要な場合は、drawnow limitrate の使用を検討してください。

50,000 個のデータ点をループで追加するアニメーション化されたラインを作成します。ループの前に drawnow を呼び出して、ループの実行の開始前に Figure と座標軸が必ず表示されるようにします。

ループの内部で、drawnow limitrate を使用して表示の更新回数を制限します。これにより、各反復で更新を実行するよりもアニメーションが高速になります。

h = animatedline;
xlim([0 4*pi])
ylim([-1 1])
x = linspace(0,4*pi,50000);
drawnow
 
for i = 1:length(x)
    y = sin(x(i));
    addpoints(h,x(i),y);
    drawnow limitrate
end

Figure を選択的に更新する

特定の条件に当てはまる場合にのみ選択的に Figure を更新することで、不要な更新を排除します。

変数 i1 から 500 まで反復するアニメーション ループを作成します。座標軸にマーカーを追加し、i が素数の場合にのみ Figure を更新します。

n = 500;
ax = axes;
xlim([1 n])
ylim([1 n])
hold on

for i=1:n
    if isprime(i)
        scatter(i,i,[],"red","filled")
        drawnow
    end
end
hold off

高速に実行されるアニメーションの開始前に Figure と座標軸の初期状態を表示する。

場合によっては、Figure と axes オブジェクトの初期化が完了して表示される前に、アニメーション ループが終了することがあります。この場合、アニメーションの最終状態しか表示されません。アニメーション ループの前に drawnow を呼び出すと、必ず先に Figure と座標軸が表示されるため、アニメーションの進行を確認できるようになります。

ループで drawnow limitrate を使用して 2,000 個の点のアニメーションを作成します。これは、比較的高速に実行されます。ループの前に drawnow を呼び出して、アニメーションの開始前に Figure と座標軸が表示されるようにします。

figure
axes
x = linspace(0,4*pi,2000);
h = animatedline;
xlim([0 4*pi])
ylim([-1 1])
drawnow 

for i = 1:length(x)
    y = sin(x(i));
    addpoints(h,x(i),y);
    drawnow limitrate
end

参考

トピック