RISC-Vベクトル拡張について、仕様書の観点からではなく、ベンチマークテストプログラムの観点からどのようにプログラムを構築すれば良いのかについて調べてみる。
RISC-Vベクトル拡張について、いくつかのサンプルプログラムが公式仕様書に添付されている。
まずはmemcpyから。以下からサンプルコードを参照できる。
memcpy: mv a3, a0 # Copy destination loop: vsetvli t0, a2, e8, m8, ta, ma # Vectors of 8b vle8.v v0, (a1) # Load bytes add a1, a1, t0 # Bump pointer sub a2, a2, t0 # Decrement count vse8.v v0, (a3) # Store bytes add a3, a3, t0 # Bump pointer bnez a2, loop # Any more? ret # Return
いくつかの観点からこのサンプルコードを読み解いていく。
- ループ中には、1つのロード命令と1つのストア命令のみが実行される。ループアンローリングは適用されない。理由は以下。
vsetvli命令により処理するデータサイズを指定する。- 上記のループアンローリングをしない理由がここにある。
vsetvli命令によりm8が指定されており、LMUL=8が指定されている。 - これにより、各ベクトル命令は8個のレジスタを同時に扱うことになる。
vle8.vではv0からv7までのレジスタにシーケンシャルなデータがロードされる。vse8.vではv0からv7までのレジスタの値がシーケンシャルにメモリにストアされる。 つまり自動的に8回分のアンローリングがされていることになる。
これを詳細に理解するためには、vsetvliの動作を理解する必要がある。
vsetvli t0, a2, e8, m8, ta, m8
taは末尾要素の対処方法で、ここでは説明を省略する。
この命令では、レジスタのサイズに応じて計算に必要な要素数を算出してt0に格納する。計算対象となる要素数がa2に格納されている。
e8は計算対象となる演算単位を指定する(e8は8ビットが演算単位となることを意味する: vadd.vvではベクトルレジスタに対して8ビット単位での演算が行われることを意味する。
つまり、a2(演算対象要素数)=1000で、ベクトルレジスタ長(VLEN=256)の場合、どのように考えるかというと、
e8でVLEN=256なので、VLEN/8=256/8=32となり、8ビットの要素が32個分処理されるモードになっている。a2が1000だが、同時に処理できるのは上記の通り32要素なので、- もしループが繰り返され、残りの演算要素数が
a2=10くらいになった場合、32よりも小さいので、VL=10となり、ベクトルレジスタ全体の内10要素だけが処理されることになる。

このループを繰り返していくことにより、memcpyが実現される。