メインコンテンツ

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

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

R2026a

この例では、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.0148

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

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

入力データを通常の MATLAB 配列ではなくgpuArrayオブジェクトに変更して、GPU で関数を実行します。関数 fastConvolution は関数zerosの like 構文を使用するため、data が gpuArray であれば出力は 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.0158

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

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

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

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

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

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

サポート関数

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

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

参考

| | |

トピック