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というファイルが生成されています、
実行するときはこれを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の固定小数点演算って時間かかりすぎじゃね?
終わり