[[Arm]]

-How to transpose a 4x4 matrix of 32bit value using NEON
-NEONを使って32bitの4x4行列の転置を行う話

*転置命令とは [#w6b44954]
-NEON にはVTRN命令があり、行列に見立てたQレジスタの、中身を入れ替えることができる&note{arm-processor-rearrange:[[ARM Processors: Coding for NEON - Part 5: Rearr... | ARM Connected Community>https://community.arm.com/groups/processors/blog/2012/03/13/coding-for-neon--part-5-rearranging-vectors]], 2012-03-13公開, 2016-07-07閲覧};

-例えば、16bitの要素8個のベクトル2つを使って、以下の様なデータを考える
    |  7|  6|  5|  4|  3|  2|  1|  0|
 q0 | 11| 12| 13| 14| 15| 16| 17| 18|
 q1 |206|205|204|203|202|201|200|199|
-16bit幅のVTRN命令を使うと、16x8のベクトル2本を、2x2の行列4個と見立てて転置が行える(vtrnq_u16)
-&mimetex(\begin{pmatrix} 11 &12 \\ 206&205 \end{pmatrix} \begin{pmatrix} 13 &14 \\ 204&203 \end{pmatrix} \begin{pmatrix} 15 &16 \\ 202&201 \end{pmatrix} \begin{pmatrix} 17 &18 \\ 200&199 \end{pmatrix}); -> &mimetex(\begin{pmatrix} 205 &12 \\ 206&11 \end{pmatrix} \begin{pmatrix} 203 &14 \\ 204&13 \end{pmatrix} \begin{pmatrix} 201 &16 \\ 202&15 \end{pmatrix} \begin{pmatrix} 199 &18 \\ 200&17 \end{pmatrix});
 after VTRN instruction (vtrnq_u16)
    |  7|  6|  5|  4|  3|  2|  1|  0|
 q0 |205| 12|203| 14|201| 16|199| 18|
 q1 |206| 11|204| 13|202| 15|200| 17|
*4x4行列の転置(16bit) [#beec1992]
-例えば16bitの4x4行列があって、4本のDレジスタに入っている場合、以下の手順で転置できる
    |  3|  2|  1|  0|
 d0 |207| 11|100|999|
 d1 |206| 12|101|998|
 d2 |205| 13|102|997|
 d3 |204| 14|103|996|
-d0とd1にVTRN命令(16bit幅)を使う
 after VTRN d0 d1
    |  3|  2|  1|  0|
 d0 | 12| 11|998|999|
 d1 |206|207|101|100|
 d2 |205| 13|102|997|
 d3 |204| 14|103|996|
-d2とd3でVTRN命令(16bit幅)を使う
 after VTRN d2 d3
    |  3|  2|  1|  0|
 d0 | 12| 11|998|999|
 d1 |206|207|101|100|
 d2 | 14| 13|996|997|
 d3 |204|205|103|102|
-q0とq1でVTRN命令(32bit幅)を使う。ArmのVレジスタはn番目のqレジスタはn*2番目とn*2+1番目のdレジスタを連結したもの
-なのでq0とq1の転置は、(d0とd1)と(d2とd3)での転置になる
 after VTRN q0 q1
    |  3|  2|  1|  0|
 d0 |996|997|998|999|
 d1 |103|102|101|100|
 d2 | 14| 13| 12| 11|
 d3 |204|205|206|207|
*4x4行列の転置(32bit) [#xed3744d]
-同様に32bitの転置をしたい場合は多々ある
-しかし、32bitでやる場合、64bit幅のVTRN命令が必要になり、Armでは実現できない。
-という訳で、多少の工夫が必要となる
**アセンブリで書く場合 [#t42c6ff5]
-参考ページ&note{arm-transpose-asm:[[Matrix transpose with NEON | ARM Connected Communit>https://community.arm.com/message/7916#7916]], 2011-10-03公開, 2016-07-07閲覧};に書いてあるように、VSWP命令を使うと、4ステップで実行可能
#geshi(asm){{
vtrn.32  q0, q1
vtrn.32  q2, q3
vswp     d1, d4
vswp     d3, d6
}}
-1ステップずつ書くと、各qレジスタに4つずつfloatが入っていたとして
    |    3|    2|    1|    0|
 q0 |  0.1| 11.0|100.0|999.0|
 q1 |  0.2| 12.0|101.0|998.0|
 q2 |  0.3| 13.0|102.0|997.0|
 q3 |  0.4| 14.0|103.0|996.0|
-最初のVTRN.32の後
 after VTRN.32 q0 q1
    |    3|    2|    1|    0|
 q0 | 12.0| 11.0|998.0|999.0|
 q1 |  0.2|  0.1|101.0|100.0|
 q2 |  0.3| 13.0|102.0|997.0|
 q3 |  0.4| 14.0|103.0|996.0|
-2回めのVTRN.32の後
 after VTRN.32 q2 q3
    |    3|    2|    1|    0|
 q0 | 12.0| 11.0|998.0|999.0|
 q1 |  0.2|  0.1|101.0|100.0|
 q2 | 14.0| 13.0|996.0|997.0|
 q3 |  0.4|  0.3|103.0|102.0|
-VSWPの後。d1がq0の後ろ(左半分)、d4がq2の前半(右半分)である点に注意
 after VSWP    d1 d4
    |    3|    2|    1|    0|
 q0 |996.0|997.0|998.0|999.0|
 q1 |  0.2|  0.1|101.0|100.0|
 q2 | 14.0| 13.0| 12.0| 11.0|
 q3 |  0.4|  0.3|103.0|102.0|
-VSWPの後。d3がq1の後ろ(左半分)、d6がq3の前半(右半分)である点に注意
 after VSWP    d3 d6
    |    3|    2|    1|    0|
 q0 |996.0|997.0|998.0|999.0|
 q1 |103.0|102.0|101.0|100.0|
 q2 | 14.0| 13.0| 12.0| 11.0|
 q3 |  0.4|  0.3|  0.2|  0.1|
-これで、無事32bitの転置に成功した(転置なのでintだろうとfloatだろうと関係ない点にも留意)
-しかし、これをC/C++のコードでから呼ぼうとすると、ちょっと困ったことになる。
#geshi(c++){{
asm(
"VMOV     q0  %4\n\t" // Copy input to q0
"VMOV     q1  %5\n\t" // Copy input to q1
"VMOV     q2  %6\n\t" // Copy input to q2
"VMOV     q3  %7\n\t" // Copy input to q3
"vtrn.32  q0, q1\n\t"
"vtrn.32  q2, q3\n\t"
"vswp     d1, d4\n\t"
"vswp     d3, d6\n\t"
"VMOV     %0  q0\n\t" // write back q0
"VMOV     %1  q1\n\t" // write back q1
"VMOV     %2  q2\n\t" // write back q2
"VMOV     %3  q3\n\t" // write back q3
 : "=r" (transposed_v0),"=r" (transposed_v1),"=r" (transposed_v2),"=r" (transposed_v3)
 : "r" (src_v0),"r" (src_v1),"r" (src_v2),"r" (src_v3)
 : "q0","q1","q2","q3"
); 
}}
-このように、たった4命令のはずが、Qレジスタ8個、合計12命令も使う処理に変わってしまう。
-なんでやねん。
-理由は、qレジスタとそれに対応する2つのdレジスタを指定する方法が無いため
-当然、もしそんな方法があるんだったら手島に知らせてください。
-このアセンブラでの方法のメリットは、dレジスタの境を超えてコピーする際にVSWPを使って入れ替えていること
-しかし、そのためにはqレジスタとdレジスタをコンパイラ任せではなく、自分で指定する必要がある
-そのため残念ながら、C/C++で書いたコードと、ASMの間でレジスタのコピーが必要になり、せっかくの簡単な転置も、なんだか無駄に長くなってしまう。
-しかも、ASMで書いてしまったので、このコードはAarch64(64bitArm)用にはビルドできなくなってしまう。&note{hoge:厳密に言えばコンパイルは通るが、オブジェクトにできない};
-なので、32bitArm固定で、全ASMで書くことができる環境であれば、これが最速である

**intrinsicを使う方法 [#w63025f6]
-Arm32bit/64bitの両方でビルドできるC/C++コードを考えると、コンパイラが提供するintrinsicを使うのが一番安全であろう
-ここではgccに付属のintrinsicで説明する
#geshi(c++){{
float32x4_t _v0 = vcvtq_f32_u32(vld1_u8(src_v0));
float32x4_t _v1 = vcvtq_f32_u32(vld1_u8(src_v1));
float32x4_t _v2 = vcvtq_f32_u32(vld1_u8(src_v2));
float32x4_t _v3 = vcvtq_f32_u32(vld1_u8(src_v3));
//    |    3|    2|    1|    0|
// _v0|  0.1| 11.0|100.0|999.0|
// _v1|  0.2| 12.0|101.0|998.0|
// _v2|  0.3| 13.0|102.0|997.0|
// _v3|  0.4| 14.0|103.0|996.0|

float32x4x2_t v01 = vtrnq_f32(_v0, _v1);
float32x4x2_t v23 = vtrnq_f32(_v2, _v3);
//    |    3|    2|    1|    0|
// _v0| 12.0| 11.0|998.0|999.0|
// _v1|  0.2|  0.1|101.0|100.0|
// _v2| 14.0| 13.0|996.0|997.0|
// _v3|  0.4|  0.3|103.0|102.0|

float32x4_t _dst0 = vcombine_f32(vget_low_f32(v01.val[0]), vget_low_f32(v23.val[0]));
float32x4_t _dst1 = vcombine_f32(vget_low_f32(v01.val[1]), vget_low_f32(v23.val[1]));
float32x4_t _dst2 = vcombine_f32(vget_high_f32(v01.val[0]), vget_high_f32(v23.val[0]));
float32x4_t _dst3 = vcombine_f32(vget_high_f32(v01.val[1]), vget_high_f32(v23.val[1]));
//    |    3|    2|    1|    0|
// _v0|996.0|997.0|998.0|999.0|
// _v1|103.0|102.0|101.0|100.0|
// _v2| 14.0| 13.0| 12.0| 11.0|
// _v3|  0.4|  0.3|  0.2|  0.1|
}}
-このように、float32x4_tでなく、float32x4x2_tを使うのがミソ
-最後の4行に、鬼のようにvget_low_f32、vget_high_f32やvcombine_f32が出てくるが、こいつらはコンパイル時にq0からd0を参照、あるはその逆、の様に動いてくれるので見た目より命令数はずっと少なくて済む。

**RGBを読み込む場合の転置 [#m1758bd3]
-通常の行列操作以外にも、インターリーブされたRGBを紐解くのに、転置が便利だったりする
-RGBは通常8bitだが、それをfloat演算に用いる場合に上記の様な転置が必要になる
-ただし、そもそもRGBの8bitずつをロードしたい場合は、VLD3命令を使うと、ロードするだけでインターリーブが解除される&note{arm-processor-interleave:[[ARM Processors: Coding for NEON - Part 1: Load ... | ARM Connected Community>https://community.arm.com/groups/processors/blog/2010/03/17/coding-for-neon--part-1-load-and-stores]], 2010-03-17公開, 2016-07-07閲覧};

トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS