Main Content

arrayfun を使用した、要素単位の MATLAB 関数の GPU におけるパフォーマンス改善

この例では、GPU でarrayfunを使用して MATLAB® 関数を実行することでコードのパフォーマンスを改善する方法を示します。

MATLAB 関数に要素単位の演算が多数含まれている場合、arrayfun を使用すると、gpuArray入力データを用いて MATLAB 関数を GPU で直接実行する場合に比べてパフォーマンスを改善できます。関数が arrayfun と互換性をもつには、入力配列の単一の要素について演算を行い、スカラー演算と算術演算を使用して出力配列の単一の要素を計算できなければなりません。

この例では、関数を CPU で実行する場合、GPU で arrayfun を使用せずに実行する場合、および GPU で arrayfun を使用して実行する場合の実行時間を比較します。

テスト用の関数の定義

特殊相対性理論の原理に従い、物体が動くときの物体の物理特性の変化はローレンツ因子で決まります。観測者に対する物体の相対的な移動速度が v であるとすると、ローレンツ因子 γ は次のように定義されます。

γ=11-v2c2=11-β2.

βvc の比です。c は真空中の光の速度です。この例の最後で定義している関数 lorentz は、ローレンツ因子を次のように計算します。

Y = 1./sqrt(1-B.*B);

GPU 実行用の関数の準備

ほとんどの MATLAB 関数は、既定では CPU で実行されます。lorentz を GPU で実行するには、gpuArray オブジェクトを入力として指定します。gpuArray オブジェクトは GPU メモリに格納される配列を表します。多くの関数が gpuArray の入力をサポートしているため、通常はコードに最小限の変更を加えるだけで GPU で実行できます。詳細については、GPU での MATLAB 関数の実行を参照してください。

lorentz には要素単位の個別の演算が含まれているため、各演算を GPU で一度に 1 つずつ実行してもパフォーマンスはそれほど改善しません。arrayfun を使用して関数 lorentz のすべての演算を一度に実行することで、パフォーマンスを改善できます。

関数 lorentz を GPU で arrayfun を使用して実行するために、関数のハンドルを定義します。

lorentzFcn = @lorentz;

比較用のパラメーターの設定

この例では、104108.5 の要素を含む配列で関数 lorentz の実行時間を比較します。比較を行う回数、および関数に渡す配列のサイズの上限と下限を設定します。

numComp = 15;
lowerLimit = 4;
upperLimit = 8.5;

関数に渡す配列のサイズは、対数的に等間隔な配列として指定します。この例の実行には数分かかる場合があります。実行時間を短縮するには、配列のサイズの上限を小さくしてください。

arraySize = ceil(logspace(lowerLimit,upperLimit,numComp));

CPU と GPU での実行時間の測定

次のようにして、それぞれの実行モードの時間を測定します。

  1. サイズの増加についてのランダムな単精度の入力データを生成します。

  2. timeitを使用して CPU での lorentz の実行時間を測定します。

  3. gpuArray を使用して GPU に入力データを送信します。

  4. gputimeitを使用して GPU での lorentz の実行時間を測定します。

  5. gputimeit を使用して GPU での arrayfun を使用した lorentz の実行時間を測定します。

for i = 1:numComp
    
    fprintf('Timing function for input array of size %d \n', arraySize(i));

    % Create random input data
    data = rand(arraySize(i),1,"single");
    
    % CPU execution
    tcpu(i) = timeit(@() lorentzFcn(data));
    
    % Send data to the GPU
    gdata = gpuArray(data);

    % GPU execution using only gpuArray objects
    tgpuObject(i) = gputimeit(@() lorentzFcn(gdata));

    % GPU execution using gpuArray objects with arrayfun
    tgpuArrayfun(i) = gputimeit(@() arrayfun(lorentzFcn,gdata));
  
end
Timing function for input array of size 10000 
Timing function for input array of size 20962 
Timing function for input array of size 43940 
Timing function for input array of size 92106 
Timing function for input array of size 193070 
Timing function for input array of size 404709 
Timing function for input array of size 848343 
Timing function for input array of size 1778280 
Timing function for input array of size 3727594 
Timing function for input array of size 7813708 
Timing function for input array of size 16378938 
Timing function for input array of size 34333201 
Timing function for input array of size 71968568 
Timing function for input array of size 150859071 
Timing function for input array of size 316227767 

結果の比較

結果を比較するために、実行時間をデータ要素数に対してプロットします。

loglog(arraySize,[tgpuObject; tgpuArrayfun; tcpu])
xlabel("Input Array Size")
ylabel("Execution Time (s)")
legend(["GPU Execution" "GPU Execution with \fontname{courier}arrayfun" "CPU Execution"], ...
    location="southeast")

小さい配列については、CPU の方が GPU よりも関数の実行が高速です。入力配列のサイズが大きくなるにつれ、GPU のパフォーマンスの改善が CPU のパフォーマンスに比べて大きくなります。しきい値の配列サイズを超えると、GPU の方が CPU よりも関数の実行が高速になります。GPU のパフォーマンスが CPU のパフォーマンスを上回るしきい値は、使用するハードウェアや実行する関数によって異なります。

GPU のみの実行時間と GPU で arrayfun を使用した実行時間のそれぞれに対する CPU の実行時間の比率を計算します。

gpuObjectSpeedup = tcpu./tgpuObject;
gpuArrayfunSpeedup = tcpu./tgpuArrayfun;

比率を入力配列のサイズに対してプロットします。

semilogx(arraySize,[gpuObjectSpeedup;gpuArrayfunSpeedup])
xlabel("Input Array Size")
ylabel("Ratio of CPU to GPU Execution Times")
legend(["GPU Execution" "GPU Execution with \fontname{courier}arrayfun"],location="southeast")

lorentz の実行は、arrayfun を使用した場合の方が GPU のみで実行した場合よりも一貫して高速になります。この例で説明した手法を独自のコードに適用した場合のパフォーマンスの改善は、ハードウェアや実行するコードに大きく依存します。

サポート関数

関数 lorentz は、β を受け取り、次の方程式に従ってローレンツ因子を計算します。

γ=11-v2c2=11-β2.

function Y = lorentz(B)
Y = 1./sqrt(1-B.*B);
end

参考

| |

関連するトピック