Main Content

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

GPU とベクトル化された計算を使用したパフォーマンスの改善

この例では、CPU の代わりに GPU で関数を実行し、計算をベクトル化することによってコードを高速化する方法を示します。

MATLAB® は、行列とベクトルに関する演算に最適化されています。ループベースでスカラー指向のコードを、MATLAB 行列およびベクトル演算を使用するように変更するプロセスは "ベクトル化" と呼ばれます。多くの場合、ベクトル化コードはループベースの同等のコードよりはるかに高速で実行され、一般に短くて理解しやすいものになります。ベクトル化の概要については、ベクトル化の使用を参照してください。

この例では、CPU と GPU での関数の実行時間を関数をベクトル化する前と後で比較します。

ループベースの関数の GPU と CPU での実行時間の測定

高速の畳み込みは、信号処理アプリケーションにおける一般的な演算です。高速の畳み込み演算は次の手順で構成されます。

  1. データの各列を時間領域から周波数領域に変換します。

  2. 周波数領域のデータをフィルター ベクトルの変換で乗算します。

  3. フィルター処理されたデータを時間領域に再度変換し、結果を行列に格納します。

このセクションでは、サポート関数 fastConvolution を使用して、行列に対して高速の畳み込みを実行します。この関数については、この例の最後で定義しています。

ランダムな複素数の入力データとランダムなフィルター ベクトルを作成します。

data = complex(randn(4096,100),randn(4096,100));
filter = randn(16,1);

CPU で関数 fastConvolution を使用してデータに対する高速の畳み込みを実行し、関数timeitを使用して実行時間を測定します。

CPUtime = timeit(@()fastConvolution(data,filter))
CPUtime = 0.0140

目的の GPU が使用可能で選択されていることを確認します。

gpu = gpuDevice;
disp(gpu.Name + " GPU selected.")
NVIDIA RTX A5000 GPU selected.

入力データを通常の MATLAB 配列ではなくgpuArrayオブジェクトに変更して、GPU で関数を実行します。関数 fastConvolution は関数zeros'like' 構文を使用するため、datagpuArray であれば出力は gpuArray になります。GPU での関数の実行時間の測定にはgputimeitを使用します。GPU を使用する関数には timeit の代わりに gputimeit を使用します。gputimeit は必ず GPU でのすべての演算が完了してから経過時間を記録するためです。この特定の問題については、GPU の方が CPU よりも関数の実行に時間がかかります。その理由は、for ループで高速フーリエ変換 (FFT)、乗算、逆 FFT (IFFT) の各演算を長さが 4096 の各列に個別に実行するためです。GPU は一般に多数の演算を実行するのに効果的であり、これらの演算を各列に個別に実行する場合には GPU の計算能力を効果的に活用できません。

gData = gpuArray(data);
gFilter = gpuArray(filter);
GPUtime = gputimeit(@()fastConvolution(gData,gFilter))
GPUtime = 0.0144

ベクトル化された関数の CPU と GPU での実行時間の測定

パフォーマンスを改善する簡単な方法は、コードをベクトル化することです。for ループ内で各列を個別に渡す代わりに、すべてのデータを入力として渡すだけで、FFT と IFFT の演算をベクトル化できます。乗算演算子 .* で、行列の各列とフィルターとの乗算が一度に実行されます。この例の最後にベクトル化されたサポート関数 fastConvolutionVectorized を示しています。関数がどのようにベクトル化されているかを確認するには、サポート関数の fastConvolutionfastConvolutionVectorized を比べてみてください。

ベクトル化された関数を使用して同じ計算を実行し、時間測定結果をベクトル化されていない関数の実行と比較します。

CPUtimeVectorized = timeit(@()fastConvolutionVectorized(data,filter))
CPUtimeVectorized = 0.0062
GPUtimeVectorized = gputimeit(@()fastConvolutionVectorized(gData,gFilter))
GPUtimeVectorized = 4.5717e-04
CPUspeedup = CPUtime/CPUtimeVectorized
CPUspeedup = 2.2513
GPUspeedup = GPUtime/GPUtimeVectorized
GPUspeedup = 31.3964
bar(categorical(["CPU" "GPU"]), ...
    [CPUtime CPUtimeVectorized; GPUtime GPUtimeVectorized], ...
    "grouped")
ylabel("Execution Time (s)")
legend("Unvectorized","Vectorized")

コードをベクトル化すると CPU と GPU でパフォーマンスが向上します。ただし、ベクトル化によるパフォーマンスの改善は CPU より GPU の方がはるかに大きくなります。ベクトル化された関数は、CPU ではループベースの関数の約 2.9 倍、GPU ではループベースの関数の約 35.4 倍の速さで実行されます。ループベースの関数の実行は GPU の方が CPU よりも 21.3% 遅いのに対し、ベクトル化された関数の実行は GPU の方が CPU よりも約 10.1 倍速くなります。

この例で説明した手法を独自のコードに適用した場合のパフォーマンスの改善は、ハードウェアや実行するコードに大きく依存します。

サポート関数

高速の畳み込み演算として、データの各列を時間領域から周波数領域に変換し、フィルター ベクトルの変換で乗算してから、時間領域に再度変換して結果を出力行列に格納します。

function y = fastConvolution(data,filter)
% Zero-pad filter to the column length of data, and transform
[rows,cols] = size(data);
filter_f = fft(filter,rows);

% Create an array of zeros of the same size and class as data
y = zeros(rows,cols,'like',data);

for idx = 1:cols
    % Transform each column of data
    data_f = fft(data(:,idx));
    % Multiply each column by filter and compute inverse transform
    y(:,idx) = ifft(filter_f.*data_f);
end

end

高速の畳み込み演算を実行し、for ループをベクトル演算に置き換えます。

function y = fastConvolutionVectorized(data,filter)
% Zero-pad filter to the length of data, and transform
[rows,~] = size(data);
filter_f = fft(filter,rows);

% Transform each column of the input
data_f = fft(data);

% Multiply each column by filter and compute inverse transform
y = ifft(filter_f.*data_f);
end

参考

| | |

関連するトピック