LoginSignup
9
2

More than 1 year has passed since last update.

MATLABの固定小数点演算って時間かかりすぎじゃね?

Last updated at Posted at 2022-12-12

1. はじめに

こちらの記事「FPGAオンボードメモリのデバッグ ~テストパターン生成/表示画面のキャプチャ~」を書くために、固定小数点データのごく簡単なビット操作(抽出、並び替え、連結など)を行なう必要がありました。

画像データをFPGAのメモリに書き込むのに、画素数1024x768データの簡単なビット操作するだけで70分以上とやたら時間がかかって困っていたのですが、fiaccelコマンドで超高速化できたので、備忘録として書きたいと思います。

2. どんな固定小数点演算を行ってるか

FPGAボードに搭載されているDDRメモリのデータを読み込んだり、逆に書き込んだりするために、解像度1024x768 RGB 8bitデータ<=>4byte/32bitの変換をするためのコードです。

最初のコードの実行時間が約44分、次のコードが約70分でした。

こんなコード

読み込みデータを画像データに変換
nt32 = numerictype(0, 32, 0);
readData = fi(zeros(1, 1024*768*3/4), nt32);
dataRGB = fi(zeros(1,length(readData)*4), nt8);

for n = 1:length(readData)
    dataRGB((n-1)*4+1) = bitconcat(bitget(readData(n), 8:-1:1));
    dataRGB((n-1)*4+2) = bitconcat(bitget(readData(n), 16:-1:9));
    dataRGB((n-1)*4+3) = bitconcat(bitget(readData(n), 24:-1:17));
    dataRGB((n-1)*4+4) = bitconcat(bitget(readData(n), 32:-1:25));
end
dataRGB3D = reshape(dataRGB, [3, 1024, 768]);
dataRGB3D2 = permute(dataRGB3D, [3, 2, 1]);
dataRGB3D3 = dataRGB3D2(:,:,[3 2 1]);  % BGR => RGB

もう一つ

画像データをメモリ書き込みデータに変換
imageSize = [768, 1024];  % v, h
nt8 = numerictype(0, 8, 0);
nt32 = numerictype(0, 32, 0);

inputImageP = imresize(imread('peppers.png'), imageSize);
figure, imshow(inputImageP), shg

% RGB => BGR
inputImagePP = inputImageP(:,:,[3 2 1]);
serialImageP = reshape(permute(inputImagePP, [3, 2, 1]), [prod(imageSize)*3, 1, 1]);
writeDataP = fi(zeros(1, prod(imageSize)*3/4), nt32);
for n = 0:length(serialImageP)/4-1
    writeDataP(n+1) = fi(bitconcat(...
          fi(serialImageP(n*4+4), nt8),...
          fi(serialImageP(n*4+3), nt8),...
          fi(serialImageP(n*4+2), nt8), ...
          fi(serialImageP(n*4+1), nt8)), nt32);
end

3. 高速化の手段

MATLABでは高速化のためにforループより、行列演算を使えというのが定説ですが、固定小数点関数は行列演算に対応していないものが多く、行列演算化はできなそうでした。(もしできるようだったら教えてください!)

最近のMATLABはforループも速くなっているらしいのですが、上記のとおり、こんなシンプルな固定小数点操作するだけで、えらく時間がかかっていました。

高速化の手法として1つ考えられるのはforを並列演算するparfor
試してみましたが、自分のマシンだと4スレッドで動作して、せいぜい1/3~1/4程度の時間にしかならず、71分が21分になったところで根本的な解決にはならずNG・・・

次にそういやこんな機能あったなと思い出したのがfiaccel
これ、高速化のためにCコード生成~コンパイルして実行ファイル化する、いわゆるMEXファイルを生成するコマンド。
しかもCコード生成するためのオプションは不要だったはず。

4. MEX変換

fiaccelを適用するには、対象部分を別ファイルにして関数化します。

function writeDataP = serial2WriteData(serialImageP)

imageSize = [768, 1024];  % v, h
nt8 = numerictype(0, 8, 0);
nt32 = numerictype(0, 32, 0);
writeDataP = fi(zeros(1, prod(imageSize)*3/4), nt32);

parfor n = 0:length(serialImageP)/4-1
        writeDataP(n+1) = fi(bitconcat(...
                fi(serialImageP(n*4+4), nt8),...
                fi(serialImageP(n*4+3), nt8),...
                fi(serialImageP(n*4+2), nt8), ...
                fi(serialImageP(n*4+1), nt8)), nt32);

end

次のコマンドで関数ファイルをMEXファイルに変換します。
>> fiaccel serial2WriteData -args {serialImageP} -report

下図のように、カレントディレクトリに***_mex.mexw64というファイルが生成されています、
image.png

実行するときはこれをCallします。
>> writeDataP = serial2WriteData_mex(serialImageP)

5. 高速化の結果

fiaccelでMEX化する前後でどれぐらい高速化できたのか、時間を示します。

MATLABコード
読み込みデータを画像データに変換:2662.784595秒 = 44分
画像データをメモリ書き込みデータに変換:4200.143911秒 = 70分

fiaccel(MEXファイル)
読み込みデータを画像データに変換:0.139422秒(19099倍)
画像データをメモリ書き込みデータに変換:0.046277秒(90761倍)

このfiaccel、かなり劇的に高速化できて感激でした。
っていうか、MATLABの固定小数点演算って時間かかりすぎじゃね?

終わり

9
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2