GPU とベクトル化された計算を使用したパフォーマンスの改善
この例では、CPU の代わりに GPU で関数を実行し、計算をベクトル化することによってコードを高速化する方法を示します。
MATLAB® は、行列とベクトルに関する演算に最適化されています。ループベースでスカラー指向のコードを、MATLAB 行列およびベクトル演算を使用するように変更するプロセスは "ベクトル化" と呼ばれます。多くの場合、ベクトル化コードはループベースの同等のコードよりはるかに高速で実行され、一般に短くて理解しやすいものになります。ベクトル化の概要については、ベクトル化の使用を参照してください。
この例では、CPU と GPU での関数の実行時間を関数をベクトル化する前と後で比較します。
ループベースの関数の GPU と CPU での実行時間の測定
高速の畳み込みは、信号処理アプリケーションにおける一般的な演算です。高速の畳み込み演算は次の手順で構成されます。
データの各列を時間領域から周波数領域に変換します。
周波数領域のデータをフィルター ベクトルの変換で乗算します。
フィルター処理されたデータを時間領域に再度変換し、結果を行列に格納します。
このセクションでは、サポート関数 fastConvolution
を使用して、行列に対して高速の畳み込みを実行します。この関数については、この例の最後で定義しています。
ランダムな複素数の入力データとランダムなフィルター ベクトルを作成します。
data = complex(randn(4096,100),randn(4096,100)); filter = randn(16,1);
CPU で関数 fastConvolution
を使用してデータに対する高速の畳み込みを実行し、関数timeit
を使用して実行時間を測定します。
CPUtime = timeit(@()fastConvolution(data,filter))
CPUtime = 0.0132
入力データを通常の MATLAB 配列ではなくgpuArray
オブジェクトに変更して、GPU で関数を実行します。関数 fastConvolution
は関数zeros
の 'like'
構文を使用するため、data
が gpuArray
であれば出力は gpuArray
になります。GPU での関数の実行時間の測定にはgputimeit
を使用します。GPU を使用する関数には timeit
よりも gputimeit
の方が適しています。これにより、必ず GPU でのすべての演算が完了してから経過時間が記録されます。この特定の問題については、GPU の方が CPU よりも関数の実行に時間がかかります。その理由は、for
ループで高速フーリエ変換 (FFT)、乗算、逆 FFT (IFFT) の各演算を長さが 4096 の各列に個別に実行するためです。GPU は一般に多数の演算を実行するのに効果的であり、これらの演算を各列に個別に実行する場合には GPU の計算能力を効果的に活用できません。
gData = gpuArray(data); gFilter = gpuArray(filter); GPUtime = gputimeit(@()fastConvolution(gData,gFilter))
GPUtime = 0.0160
ベクトル化された関数の CPU と GPU での実行時間の測定
パフォーマンスを改善する簡単な方法は、コードをベクトル化することです。for
ループ内で各列を個別に渡す代わりに、すべてのデータを入力として渡すだけで、FFT と IFFT の演算をベクトル化できます。乗算演算子 .*
で、行列の各列とフィルターとの乗算が一度に実行されます。この例の最後にベクトル化されたサポート関数 fastConvolutionVectorized
を示しています。関数がどのようにベクトル化されているかを確認するには、サポート関数の fastConvolution
と fastConvolutionVectorized
を比べてみてください。
ベクトル化された関数を使用して同じ計算を実行し、時間測定結果をベクトル化されていない関数の実行と比較します。
CPUtimeVectorized = timeit(@()fastConvolutionVectorized(data,filter))
CPUtimeVectorized = 0.0046
GPUtimeVectorized = gputimeit(@()fastConvolutionVectorized(gData,gFilter))
GPUtimeVectorized = 4.5261e-04
CPUspeedup = CPUtime/CPUtimeVectorized
CPUspeedup = 2.8720
GPUspeedup = GPUtime/GPUtimeVectorized
GPUspeedup = 35.3766
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
参考
gpuArray
| gputimeit
| fft
| ifft