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

# 1. はじめに
こちらの記事[***「FPGAオンボードメモリのデバッグ　～テストパターン生成/表示画面のキャプチャ～」***](https://qiita.com/SacredTubes/items/96f0b41f900c6d556224)を書くために、固定小数点データのごく簡単なビット操作（抽出、並び替え、連結など）を行なう必要がありました。

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

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

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

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

こんなコード
```matlab:読み込みデータを画像データに変換
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
```

もう一つ
```matlab:画像データをメモリ書き込みデータに変換
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](https://www.mathworks.com/help/parallel-computing/parallel-for-loops-parfor.html)。
試してみましたが、自分のマシンだと4スレッドで動作して、せいぜい1/3～1/4程度の時間にしかならず、71分が21分になったところで根本的な解決にはならずNG・・・

次にそういやこんな機能あったなと思い出したのが[fiaccel](https://www.mathworks.com/help/fixedpoint/ref/fiaccel.html)。
これ、高速化のためにCコード生成～コンパイルして実行ファイル化する、いわゆるMEXファイルを生成するコマンド。
しかもCコード生成するためのオプションは不要だったはず。

https://www.mathworks.com/help/fixedpoint/ref/fiaccel.html

# 4. MEX変換

fiaccelを適用するには、対象部分を別ファイルにして関数化します。
```matlab:
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/142ae4d1-34e8-38f7-160c-1cd69f0a8587.png)

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

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



:::note warn
MATLABコード
読み込みデータを画像データに変換：2662.784595秒 = 44分
画像データをメモリ書き込みデータに変換：4200.143911秒 = 70分
:::
:::note info
fiaccel（MEXファイル）
読み込みデータを画像データに変換：0.139422秒（19099倍）
画像データをメモリ書き込みデータに変換：0.046277秒（90761倍）
:::


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

終わり