D1 riscv芯片上運(yùn)行rt-thread進(jìn)行RVV性能評(píng)估
掃描二維碼
隨時(shí)隨地手機(jī)看文章
D1 riscv芯片上運(yùn)行rt-thread進(jìn)行RVV性能評(píng)估
-
概述
-
rt-thread在D1芯片上的移植
-
如何開(kāi)啟D1&&D1s的rvv擴(kuò)展
-
RVV性能對(duì)比評(píng)估
-
RVV在RTOS如何使用的更好
概述
D1 && D1s(f133)采用的是平頭哥C906的core,上面已經(jīng)支持了RVV 0.7.1版本,雖然目前RVV1.0已經(jīng)frozen,這就意味著上游編譯器或者一些相關(guān)的生態(tài)軟件將支持RVV1.0,但是作為性能評(píng)估RVV0.7.1與RVV1.0影響并不大。下面的文章主要描述如何在D1 && D1s芯片上運(yùn)行rt-thread,并且描述如何開(kāi)啟RVV,同時(shí)對(duì)RVV性能進(jìn)行一個(gè)簡(jiǎn)單的評(píng)估,最后討論RVV如何與RTOS使用的問(wèn)題。
rt-thread在D1芯片上的移植
目前D1s有64MB的內(nèi)置DDR2,這非常適合運(yùn)行RTOS,所以將rtos移植到D1s上是非常不錯(cuò)的選擇。
其移植的細(xì)節(jié)不詳細(xì)介紹,可以查看源代碼
https://github.com/bigmagic123/d1-nezha-rtthread
移植的難點(diǎn)在于:
1.啟動(dòng),從芯片上電啟動(dòng)入口處跳轉(zhuǎn)到rt-thread入口處的匯編代碼
2.硬件串口的初始化,這里會(huì)涉及到時(shí)鐘配置
3.tick時(shí)鐘中斷,目前采用的是riscv的通用timer,需要配置clic
4.任務(wù)切換和上下文的保存與恢復(fù),這部分需要對(duì)入棧出棧的順序十分的清晰
5.串口中斷,這里需要弄清楚plic中斷處理機(jī)制
解決上述問(wèn)題,移植rt-thread將非常的簡(jiǎn)單,當(dāng)前的rt-thread運(yùn)行在M-mode,具有比較高的權(quán)限,可以隨意操作寄存器進(jìn)行配置。
編譯與運(yùn)行在windows和Linux上均可操作,主要參考如下的文檔:
https://github.com/bigmagic123/d1-nezha-rtthread/blob/main/README.md
目前已經(jīng)實(shí)現(xiàn)的功能有:
1.timer 2.uart 3.gpio 4.vector 5.FPU
如何開(kāi)啟D1&&D1s的rvv擴(kuò)展
想要使用RVV功能,需要開(kāi)啟VS標(biāo)志位,該位位于
VS位于MSTATUS寄存器的23到24位。但是需要注意的是,當(dāng)使用RVV時(shí),需要開(kāi)啟浮點(diǎn)寄存器(FS),不然會(huì)報(bào)錯(cuò)。
這部分在rt-thread的體現(xiàn)是在上下文切換的時(shí)候,需要將使用RVV的線程的MSTATUS設(shè)置成開(kāi)啟VS的模式。
可以VS設(shè)置b01。
只有當(dāng)使能該位,才能正常使用RVV指令,不然運(yùn)行會(huì)直接報(bào)錯(cuò)。
接著,編譯選項(xiàng)中還需要添加成如下選項(xiàng)
-march=rv64gcvxtheadc -mabi=lp64d -mtune=c906
這樣才能告訴編譯器支持RVV指令。
RVV性能對(duì)比評(píng)估
riscv 的RVV其編程模型主要有兩種方式,第一種采用rvv-intrinsic。這就是在編譯器中進(jìn)行intrinsic函數(shù)的構(gòu)建,可以將相關(guān)的rvv操作變成編輯器的內(nèi)置函數(shù)。采用類似下面的函數(shù)方式進(jìn)行編程。
vint8mf8_t vcopy_v_i8mf8 (vint8mf8_t src); vint8mf4_t vcopy_v_i8mf4 (vint8mf4_t src); ...
其操作詳情可以參考:
https://github.com/riscv-non-isa/rvv-intrinsic-doc/
本質(zhì)上就是將復(fù)雜的匯編代碼操作換成C語(yǔ)言函數(shù)的形式,這種方式可讀性更強(qiáng)。rvv1.0后面也會(huì)提供這一種方式進(jìn)行。
第二種就是采用匯編進(jìn)行操作,裸寫匯編代碼的難度很大,但是可以實(shí)現(xiàn)比較好的優(yōu)化,這特別是在關(guān)鍵耗時(shí)的操作上進(jìn)行匯編級(jí)別的優(yōu)化,可以很大程度上提升程序運(yùn)行的性能。
在平頭哥開(kāi)源出來(lái)的GCC工具鏈中,并沒(méi)有rvv-intrinsic功能,所以只能通過(guò)匯編函數(shù)的方式進(jìn)行操作。
平頭哥開(kāi)源出來(lái)了一個(gè)神經(jīng)網(wǎng)絡(luò)的庫(kù),里面有C906的RVV底層操作。
https://github.com/T-head-Semi/csi-nn2
實(shí)現(xiàn)了一些基本的操作,抽象了各種常用的網(wǎng)絡(luò)層的接口,并且提供一系列已優(yōu)化的二進(jìn)制庫(kù)。
其主要的使用類似于CMSIS,Tensorflow等等。有了這些抽象接口,在使用RVV進(jìn)行特定優(yōu)化的時(shí)候,難度也會(huì)有所降低。
下面主要從csi-nn2抽取出vertor add,vertor mul,memcpy,也就是加法計(jì)算,乘法計(jì)算和內(nèi)存拷貝,三個(gè)方面,對(duì)其性能進(jìn)行評(píng)估。
https://github.com/bigmagic123/d1-nezha-rtthread/blob/main/bsp/d1-nezha/applications/vector.c
浮點(diǎn)數(shù)加法
采用向量操作
float a[10] = {1.2,1.3,1.4,1.5,1.6,1.7,1.8,2.0,2.1,2.2}; float b[10] = {1.2,1.3,1.4,1.5,1.6,1.7,1.8,2.0,2.1,2.2}; float c[10]; element_mul_f32(a, b, c, 10);
一次性處理10個(gè)元素,采用RVV向量的操作。
普通的加法
for(ii = 0; ii < 10; ii++)
{
d[ii] = a[ii] + b[ii];
}
單個(gè)元素相加。
分別計(jì)算30000次。
最后的結(jié)果如下:
采用向量加法只需要7ms,而直接相加,則消耗了36ms,性能相差5倍左右。
浮點(diǎn)數(shù)相乘
與加法操作類似,也是一次性處理10個(gè)元素
float a[10] = {1.2,1.3,1.4,1.5,1.6,1.7,1.8,2.0,2.1,2.2}; float b[10] = {1.2,1.3,1.4,1.5,1.6,1.7,1.8,2.0,2.1,2.2}; float c[10]; element_mul_f32(a, b, c, 10);
普通的乘法
for(ii = 0; ii < 10; ii++)
{
d[ii] = a[ii] * b[ii];
}
兩者性能對(duì)比
向量乘法也比普通的乘法性能強(qiáng)大一些,接近5倍的差別。
內(nèi)存拷貝
內(nèi)存拷貝的測(cè)試方法是測(cè)試rt_memcpy,memcpy,csi_c906_memcpy。分別代表rt-thread內(nèi)置的內(nèi)存拷貝函數(shù),采用C語(yǔ)言進(jìn)行實(shí)現(xiàn),memcpy是newlib庫(kù)函數(shù)的實(shí)現(xiàn),里面會(huì)對(duì)riscv架構(gòu)進(jìn)行優(yōu)化處理,csi_c906_memcpy則是采用向量操作,進(jìn)行內(nèi)存拷貝。
測(cè)試是先申請(qǐng)1MB的目標(biāo)內(nèi)存,1MB的源內(nèi)存,往源內(nèi)存中寫隨機(jī)數(shù)。
拷貝源內(nèi)存中的數(shù)據(jù)到目標(biāo)內(nèi)存,拷貝100次,也就是100M的內(nèi)存拷貝數(shù)據(jù)量。
結(jié)果如下:
顯然,內(nèi)存拷貝操作newlib中的memcpy性能是最佳的,而向量操作的memcpy反而其次,最差的是rt-thread的rt_memcpy。這里的原因是newlib的memcpy的是經(jīng)過(guò)優(yōu)化后的,而vector memcpy也可能是優(yōu)化的不好導(dǎo)致性能與newlib的memcpy相當(dāng)。rt-thread采用純C語(yǔ)言實(shí)現(xiàn),其通用性比較好,但是性能不佳。
RVV在RTOS如何使用的更好
這是一個(gè)關(guān)于更好的在RTOS上使用RVV的問(wèn)題,由于RTOS是為了追求實(shí)時(shí)性,一般來(lái)說(shuō),開(kāi)啟了FPU和RVV后,其寄存器的數(shù)量會(huì)非常的多,每次入棧和出棧的操作,如果每次都將全部的寄存器壓入和彈出,將會(huì)讓切換任務(wù)的時(shí)間變長(zhǎng),影響了系統(tǒng)的實(shí)時(shí)性。對(duì)于這種情況,其實(shí)可以利用mstatus中的VS和FS的標(biāo)志位進(jìn)行判斷。
在切換任務(wù)時(shí),可以通過(guò)這些標(biāo)識(shí),選擇是否壓棧和出棧,這樣保證了一部分性能實(shí)時(shí)性的情況下,也可以很好的處理FPU和RVV。