本文是為了幫助開發者快速入門 risc-v 架構下vector 的 intrinsic 編程,首先介紹了risc-v vector extension 的特性和 intrinsic 編程常見的數據類型與指令接口命名,然后給出一個數組/向量相加的完整例程,介紹C語言的普通實現與intrinsic向量化實現,最后展示了如何獲取平頭哥相關工具鏈編譯程序并通過qemu模擬器運行。需要說明的是,本文介紹的特性與案例均以玄鐵 c906 處理器為基礎。
01vector extension
與ARM CPU支持的 SVE(Scalable Vector Extension) 類似,risc-v vector extension 可以通過向量化來提升程序運行速度。平頭哥玄鐵 c906 CPU支持 risc-v vector extension 0.7.1,新增了32個向量寄存器 v0-v31,下面對 vector extension中重要概念作介紹:
(1) 向量寄存器位寬 vlen:對于玄鐵 c906,vlen = 128,即可以并行計算16個8位整數或4個32位浮點數等。
(2) 標準元素寬度 sew:sew = 8/16/32/64b,對于float計算,sew=32b;對于int8計算,sew=8b。
(3) 向量寄存器組 lmul:lmul = 1/2/4/8,多個向量寄存器可以組合在一起形成一個向量寄存器組,一條向量指令就可以對多個向量寄存器進行操作,還能提供更好的執行效率(具體的執行效率取決于執行部件的帶寬)。
(4) 向量長度寄存器 vl:vl 寄存器保存一個無符號整數,指定由向量指令更新的元素個數。在向量指令執行期間,任何具有索引 ≥ vl 的目標向量寄存器組中的元素都被清零。如當 vl = 3 時,對于 float32 的運算,由于vlen=128,只會取向量寄存器中的低96位的3個float32數據進行計算,最高32位將清零,如下圖所示。
?
圖1. c906 vl=3向量浮點相加示意圖
02intrinsic
intrinsic 本質上是底層匯編指令的封裝,與手工編寫匯編程序相比,intrinsic 編程不需要考慮底層寄存器的分配,對于C語言用戶而言更加容易上手,可維護性強,可讀性更強,在跨平臺的可移植性上也更加友好,配上相關編譯器工具鏈后在性能上幾乎可以媲美手工匯編。
2.1 數據類型
根據不同的 sew 和 lmul 組合,risc-v vector extension intrinsic 基本數據類型格式如下:
v<基本類型>m<向量寄存器組lmul>_t 其中:
基本類型:int8,int16,int32,int64,uint8,uint16,uint32,uint64,float16,float32
向量寄存器組 lmul:1,2,4,8
例如:
vint32m1_t:1個向量寄存器中存放 vl 個int32 數據, 0 < vl <= 4 (vlen/sew*lmul=128/32*1=4 )
vfloat32m1_t:1個向量寄存器中存放 vl 個float32數據, 0 < vl <=4
vint8m2_t:2個向量寄存器中存放 vl 個int8數據, 0 < vl <= 32
vfloat16m2_t:2個向量寄存器中存放 vl 個float16數據, 0 < vl <= 16
2.2 intrinsic命名
幾乎每一條 risc-v vector 指令都有其對應的 intrinsic 函數接口,函數名格式如下:
<指令名>_<數據基本類型簡寫>m<向量寄存器組lmul> 其中:
指令名:vmul.vv (vmul_vv), vadd.vv (vadd_vv) 等
數據基本類型簡寫:i8,i16,i32,i64,u8,u16,u32,u64,f16,f32
向量寄存器組 lmul:1,2,4,8
例如:
// 2個向量寄存器中各自 vl 個float32數據逐個相乘得到 vl 個float32結果存入1個向量寄存器中,其中 0 < vl <= 4
vfloat32m1_t vfmul_vv_f32m1(vfloat32m1_t op1, vfloat32m1_t op2, size_t vl)
// 4個向量寄存器兩兩分組,每組寄存器中各自 vl 個int32數據逐個相加得到 vl 個int32結果存入2個向量寄存器中,其中? 0 < vl <= 8
vint32m2_t vadd_vv_i32m2(vint32m2_t op1, vint32m2_t op2, size_t vl)
關于RVV intrinsic更多數據類型和指令接口可以參考附錄中intrinsic手冊。
03示例程序
本節通過數組相加的例子對 vector 擴展 intrinsic 使用作簡單介紹;假設有長度為 ARRAY_LEN 的 float 類型數組 A,B,C,為了實現 C = A + B,有:
3.1 普通標量實現?
在循環中對數組 A,B 中的浮點數逐個相加,循環的次數為 ARRAY_LEN 次。
?
void add(const float *a, const float *b, float *c, size_t length) { for (int i = 0; i < length; i++) { c[i] = a[i] + b[i]; } }
?
3.2 intrinsic 向量化實現?
在循環中一次對數組 A,B 中的多個浮點數相加:
?
void add_vec(const float *a, const float *b, float *c, size_t length) { while (length > 0) { size_t vl = vsetvl_e32m1(length); // 設置向量寄存器每次操作的元素個數 vfloat32m1_t va = vle32_v_f32m1(a, vl); // 從數組a中加載vl個元素到向量寄存器va中 vfloat32m1_t vb = vle32_v_f32m1(b, vl); // 從數組b中加載vl個元素到向量寄存器vb中 vfloat32m1_t vc = vfadd_vv_f32m1(va, vb, vl); // 向量寄存器va和向量寄存器vb中vl個元素對應相加,結果為vc vse32_v_f32m1(c, vc, vl); // 將向量寄存器中的vl個元素存到數組c中 a += vl; b += vl; c += vl; length -= vl; } }
?
設置 lmul = 1,由于 vlen = 128,向量加法每次最多能操作4個 float 數據,即循環的次數為 ceil(ARRAY_LEN / 4) 次,本例中定義ARRAY_LEN = 11,需循環計算3次,且第3次時剩余元素3個,即 vl=3,計算過程如圖1所示。
從上述代碼看,在使用 vector intrinsic 實現向量化時,需要手動從指定地址 load 數據到向量寄存器變量中,計算后,同樣需要手動將向量寄存器變量中數據 store 回指定地址。相比于普通串行實現,利用 vector intrinsic 實現理論上有接近4倍的加速比,當設置 lmul = 2/4/8 或數據類型是short或者char時,可以取得更高的加速比。
04編譯運行
4.1 獲取安裝工具鏈
從平頭哥開放社區occ下載對應版本的risc-v工具鏈(https://occ.t-head.cn/community/download?spm=a2cl5.25411629.0.0.51b975321lplnb&id=4090445921563774976):
?
wget https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1663142514282/Xuantie-900-gcc-linux-5.10.4-glibc-x86_64-V2.6.1-20220906.tar.gz tar -zxvf Xuantie-900-gcc-linux-5.10.4-glibc-x86_64-V2.1.9-20210918.tar.gz -C {yourToolchainDir} # 解壓至自己的目錄 cd {yourToolchainDir}/bin riscv64-unknown-linux-gnu-gcc -v #查看工具鏈版本
?
確保gcc工具鏈版本為 V2.6.1
gcc version 10.2.0 (Xuantie-900 linux-5.10.4 glibc gcc Toolchain V2.6.1 B-20220906)
4.2 編譯
?
{yourToolchainDir}/bin/riscv64-unknown-linux-gnu-gcc -march=rv64gcv0p7xthead -static add.c -o add.elf
?
注:c906 只支持risc-v vector 0.7.1 擴展,需要在編譯選項中加上 -march=rv64gcv0p7xthead 選項
4.3 qemu運行
從平頭哥開放社區occ下載最新qemu模擬器(https://occ.t-head.cn/community/download?spm=a2cl5.25411629.0.0.51b975321lplnb&id=4168444414324183040)
?
wget https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1681722863240/xuantie-qemu-x86_64-Ubuntu-18.04-20230413-0706.tar.gz tar -zxvf csky-qemu-x86_64-Ubuntu-16.04-20210202-1445.tar.gz -C {yourQemuDir} # 解壓至自己的目錄 {yourQemuDir}/bin/qemu-riscv64 -cpu c906fdv add.elf
?
05附
(1)完整示例代碼 add.c :
?
#include#include #include #define ARRAY_SIZE 11 float a_array[ARRAY_SIZE] = { 1.18869953, 1.55298864, -0.17365574, -1.86193886, -1.52391526, -0.36566814, 0.70753702, 0.73992422, -0.13493693, 1.09563677,1.03797902 }; float b_array[ARRAY_SIZE] = { 1.19655525, 0.23393777, -0.11629651, -0.54508896, -1.2424749, -1.54835913, 0.86935212, 0.12946646, 0.81831905, -0.42723697, -0.89793257 }; // float c_array[ARRAY_SIZE] = { // 2.38525478, 1.78692641, -0.28995225, -2.40702783, // -2.76639016 -1.91402727,1.57688914, 0.86939068, // 0.68338213, 0.66839979, 0.14004644 // }; float c_array_ref[ARRAY_SIZE]; float c_array_vec[ARRAY_SIZE]; void add(const float *a, const float *b, float *c, size_t length) { for (int i = 0; i < length; i++) { c[i] = a[i] + b[i]; } } void add_vec(const float *a, const float *b, float *c, size_t length) { while (length > 0) { size_t vl = vsetvl_e32m1(length); // 設置向量寄存器每次操作的元素個數 vfloat32m1_t va = vle32_v_f32m1(a, vl); // 從數組a中加載vl個元素到向量寄存器va中 vfloat32m1_t vb = vle32_v_f32m1(b, vl); // 從數組b中加載vl個元素到向量寄存器vb中 vfloat32m1_t vc = vfadd_vv_f32m1(va, vb, vl); // 向量寄存器va和向量寄存器vb中vl個元素對應相加,結果為vc vse32_v_f32m1(c, vc, vl); // 將向量寄存器中的vl個元素存到數組c中 a += vl; b += vl; c += vl; length -= vl; } } int main() { printf("Test add function by rvv0.7.1 intrinsic "); add(a_array, b_array, c_array_ref, ARRAY_SIZE); add_vec(a_array, b_array, c_array_vec, ARRAY_SIZE); // 逐個比較普通實現與intrinsic實現的結果 for (int i = 0; i < ARRAY_SIZE; i++) { if (fabsf(c_array_ref[i] - c_array_vec[i]) > 1e-6) { printf("index[%d] failed, %f=!%f ", i, c_array_ref[i], c_array_vec[i]); } else { printf("index[%d] successed, %f=%f ", i, c_array_ref[i], c_array_vec[i]); } } return 0; }
?
(2)運行結果:
在 main 函數中,對普通實現與 intrinsic 實現的結果逐個進行比對,若兩者絕對誤差大于 1e-6,認為向量化計算結果錯誤,從下圖運行結果看,兩者輸出結果完全一致,符合預期。
Test add function by rvv0.7.1 intrinsic?
index[0] successed, 2.385255=2.385255
index[1] successed, 1.786926=1.786926
index[2] successed, -0.289952=-0.289952
index[3] successed, -2.407028=-2.407028
index[4] successed, -2.766390=-2.766390
index[5] successed, -1.914027=-1.914027
index[6] successed, 1.576889=1.576889
index[7] successed, 0.869391=0.869391
index[8] successed, 0.683382=0.683382
index[9] successed, 0.668400=0.668400
index[10] successed, 0.140046=0.140046
(3)riscv 編譯工具鏈下載:https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1663142514282/Xuantie-900-gcc-linux-5.10.4-glibc-x86_64-V2.6.1-20220906.tar.gz
(4)qemu模擬器下載:https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1681722863240/xuantie-qemu-x86_64-Ubuntu-18.04-20230413-0706.tar.gz
(5)Xuantie 900 Series RVV-0.7.1 Intrinsic Manual.pdf 手冊下載:https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1663142187133/Xuantie+900+Series+RVV-0.7.1+Intrinsic+Manual.pdf
審核編輯:湯梓紅
評論
查看更多