轉(zhuǎn)載自:http://www.cnblogs.com/xylc/p/5410517.html
方便筆記和后續(xù)學習哪亿,如有侵權(quán)請聯(lián)系刪除
前言
本系列博文用于介紹ARM CPU下NEON指令優(yōu)化。
博文github地址:github
相關(guān)代碼github地址:github
NEON指令集
主流支持目標平臺為ARM CPU的編譯器基本都支持NEON指令睬隶÷嗉校可以通過在代碼中嵌入NEON匯編來使用NEON,但是更加常見的方式是通過類似C函數(shù)的NEON Instrinsic來編寫NEON代碼苏潜。就如同NEON hello world一樣银萍。NEON Instrinsic是編譯器支持的一種buildin類型和函數(shù)的集合,基本涵蓋NEON的所有指令恤左,通常這些Instrinsic包含在arm_neon.h頭文件中贴唇。
本文以android-ndk-r11c中armv7的arm_neon.h為例,講解NEON的指令類型飞袋。
寄存器
ARMV7架構(gòu)包含:
16個通用寄存器(32bit)戳气,R0-R15
16個NEON寄存器(128bit),Q0-Q15(同時也可以被視為32個64bit的寄存器巧鸭,D0-D31)
16個VFP寄存器(32bit)瓶您,S0-S15
NEON和VFP的區(qū)別在于VFP是加速浮點計算的硬件不具備數(shù)據(jù)并行能力,同時VFP更盡興雙精度浮點數(shù)(double)的計算纲仍,NEON只有單精度浮點計算能力呀袱。更多請參考stackoverflow:neon vs vfp
基本數(shù)據(jù)類型
64bit數(shù)據(jù)類型,映射至寄存器即為D0-D31
相應的c/c++語言類型(stdint.h或者csdtint頭文件中類型)在注釋中說明郑叠。
//typedef int8_t[8] int8x8_t;typedef__builtin_neon_qiint8x8_t__attribute__((__vector_size__ (8)));//typedef int16_t[4] int16x4_t;typedef__builtin_neon_hiint16x4_t__attribute__((__vector_size__ (8)));//typedef int32_t[2] int32x2_t;typedef__builtin_neon_siint32x2_t__attribute__((__vector_size__ (8)));//typedef int64_t[1] int64x1_t;typedef__builtin_neon_diint64x1_t;//typedef float16_t[4] float16x4_t;//(注:該類型為半精度夜赵,在部分新的CPU上支持,c/c++語言標注中尚無此基本數(shù)據(jù)類型)typedef__builtin_neon_hffloat16x4_t__attribute__((__vector_size__ (8)));//typedef float32_t[2] float32x2_t;typedef__builtin_neon_sffloat32x2_t__attribute__((__vector_size__ (8)));//poly8以及poly16類型在常用算法中基本不會使用//詳細解釋見://http://stackoverflow.com/questions/22224282/arm-neon-and-poly8-t-and-poly16-ttypedef__builtin_neon_poly8poly8x8_t__attribute__((__vector_size__ (8)));typedef__builtin_neon_poly16poly16x4_t__attribute__((__vector_size__ (8)));#ifdef__ARM_FEATURE_CRYPTOtypedef__builtin_neon_poly64poly64x1_t;#endif//typedef uint8_t[8] uint8x8_t;typedef__builtin_neon_uqiuint8x8_t__attribute__((__vector_size__ (8)));//typedef uint16_t[4] uint16x4_t;typedef__builtin_neon_uhiuint16x4_t__attribute__((__vector_size__ (8)));//typedef uint32_t[2] uint32x2_t;typedef__builtin_neon_usiuint32x2_t__attribute__((__vector_size__ (8)));//typedef uint64_t[1] uint64x1_t;typedef__builtin_neon_udiuint64x1_t;
128bit數(shù)據(jù)類型乡革,映射至寄存器即為Q0-Q15
相應的c/c++語言類型(stdint.h或者csdtint頭文件中類型)在注釋中說明寇僧。
//typedef int8_t[16] int8x16_t;typedef__builtin_neon_qiint8x16_t__attribute__((__vector_size__ (16)));//typedef int16_t[8] int16x8_t;typedef__builtin_neon_hiint16x8_t__attribute__((__vector_size__ (16)));//typedef int32_t[4] int32x4_t;typedef__builtin_neon_siint32x4_t__attribute__((__vector_size__ (16)));//typedef int64_t[2] int64x2_t;typedef__builtin_neon_diint64x2_t__attribute__((__vector_size__ (16)));//typedef float32_t[4] float32x4_t;typedef__builtin_neon_sffloat32x4_t__attribute__((__vector_size__ (16)));//poly8以及poly16類型在常用算法中基本不會使用//詳細解釋見://http://stackoverflow.com/questions/22224282/arm-neon-and-poly8-t-and-poly16-ttypedef__builtin_neon_poly8poly8x16_t__attribute__((__vector_size__ (16)));typedef__builtin_neon_poly16poly16x8_t__attribute__((__vector_size__ (16)));#ifdef__ARM_FEATURE_CRYPTOtypedef__builtin_neon_poly64poly64x2_t__attribute__((__vector_size__ (16)));#endif//typedef uint8_t[16] uint8x16_t;typedef__builtin_neon_uqiuint8x16_t__attribute__((__vector_size__ (16)));//typedef uint16_t[8] uint16x8_t;typedef__builtin_neon_uhiuint16x8_t__attribute__((__vector_size__ (16)));//typedef uint32_t[4] uint32x4_t;typedef__builtin_neon_usiuint32x4_t__attribute__((__vector_size__ (16)));//typedef uint64_t[2] uint64x2_t;typedef__builtin_neon_udiuint64x2_t__attribute__((__vector_size__ (16)));typedeffloatfloat32_t;typedef__builtin_neon_poly8poly8_t;typedef__builtin_neon_poly16poly16_t;#ifdef__ARM_FEATURE_CRYPTOtypedef__builtin_neon_poly64poly64_t;typedef__builtin_neon_poly128poly128_t;#endif
結(jié)構(gòu)化數(shù)據(jù)類型
下面這些數(shù)據(jù)類型是上述基本數(shù)據(jù)類型的組合而成的結(jié)構(gòu)化數(shù)據(jù)類型,通常為被映射到多個寄存器中沸版。
typedefstructint8x8x2_t{int8x8_tval[2];}int8x8x2_t;...//省略......#ifdef__ARM_FEATURE_CRYPTOtypedefstructpoly64x2x4_t{poly64x2_tval[4];}poly64x2x4_t;#endif
基本指令集
NEON指令按照操作數(shù)類型可以分為正常指令嘁傀、寬指令、窄指令视粮、飽和指令细办、長指令。
正常指令:生成大小相同且類型通常與操作數(shù)向量相同到結(jié)果向量馒铃。
長指令:對雙字向量操作數(shù)執(zhí)行運算蟹腾,生產(chǎn)四字向量到結(jié)果。所生成的元素一般是操作數(shù)元素寬度到兩倍区宇,并屬于同一類型娃殖。L標記,如VMOVL议谷。
寬指令:一個雙字向量操作數(shù)和一個四字向量操作數(shù)執(zhí)行運算炉爆,生成四字向量結(jié)果。W標記卧晓,如VADDW芬首。
窄指令:四字向量操作數(shù)執(zhí)行運算,并生成雙字向量結(jié)果逼裆,所生成的元素一般是操作數(shù)元素寬度的一半郁稍。N標記,如VMOVN胜宇。
飽和指令:當超過數(shù)據(jù)類型指定到范圍則自動限制在該范圍內(nèi)耀怜。Q標記,如VQSHRUN
NEON指令按照作用可以分為:加載數(shù)據(jù)桐愉、存儲數(shù)據(jù)财破、加減乘除運算、邏輯AND/OR/XOR運算从诲、比較大小運算等左痢,具體信息參考資料[1]中附錄C和附錄D部分。
常用的指令集包括:
初始化寄存器
寄存器的每個lane(通道)都賦值為一個值N
Result_tvcreate_type(Scalar_t N)Result_tvdup_type(Scalar_t N)Result_tvmov_type(Scalar_t N)
lane(通道)在下面有說明系洛。
加載內(nèi)存數(shù)據(jù)進寄存器
間隔為x俊性,加載數(shù)據(jù)進NEON寄存器
Result_t vld[x]_type(Scalar_t* N)
Result_t vld[x]q_type(Scalar_t* N)
間隔為x,加載數(shù)據(jù)進NEON寄存器的相關(guān)lane(通道)碎罚,其他lane(通道)的數(shù)據(jù)不改變
Result_t vld[x]_lane_type(Scalar_t* N,Vector_t M,intn)Result_t vld[x]q_lane_type(Scalar_t* N,Vector_t M,intn)
從N中加載x條數(shù)據(jù)磅废,分別duplicate(復制)數(shù)據(jù)到寄存器0-(x-1)的所有通道
Result_t vld[x]_dup_type(Scalar_t* N)
Result_t vld[x]q_dup_type(Scalar_t* N)
lane(通道):比如一個float32x4_t的NEON寄存器,它具有4個lane(通道)荆烈,每個lane(通道)有一個float32的值拯勉,因此c++ float32x4_t dst = vld1q_lane_f32(float32_t* ptr,float32x4_t src,int n=2)的意思就是先將src寄存器的值復制到dst寄存器中,然后從ptr這個內(nèi)存地址中加載第3個(lane的index從0開始)float到dst寄存器的第3個lane(通道中)憔购。最后dst的值為:{src[0],src[1],ptr[2],src[3]}宫峦。
間隔:交叉存取,是ARM NEON特有的指令玫鸟,比如c++ float32x4x3_t = vld3q_f32(float32_t* ptr)导绷,此處間隔為3,即交叉讀取12個float32進3個NEON寄存器中屎飘。3個寄存器的值分別為:{ptr[0],ptr[3],ptr[6],ptr[9]}妥曲,{ptr[1],ptr[4],ptr[7],ptr[10]}贾费,{ptr[2],ptr[5],ptr[8],ptr[11]}。
存儲寄存器數(shù)據(jù)到內(nèi)存
間隔為x檐盟,存儲NEON寄存器的數(shù)據(jù)到內(nèi)存中
voidvstx_type(Scalar_t* N)voidvstxq_type(Scalar_t* N)
間隔為x褂萧,存儲NEON寄存器的相關(guān)lane(通道)到內(nèi)存中
Result_t vst[x]_lane_type(Scalar_t* N,Vector_t M,intn)Result_t vst[x]q_lane_type(Scalar_t* N,Vector_t M,intn)
讀取/修改寄存器數(shù)據(jù)
讀取寄存器第n個通道的數(shù)據(jù)
Result_tvget_lane_type(Vector_t M,intn)
讀取寄存器的高/低部分到新的寄存器中,數(shù)據(jù)變窄(長度減半)葵萎。
Result_tvget_low_type(Vector_t M)Result_tvget_high_type(Vector_t M)
返回在復制M的基礎上設置通道n為N的寄存器數(shù)據(jù)
Result_tvset_lane_type(Scalar N,Vector_t M,intn)
寄存器數(shù)據(jù)重排
從寄存器M中取出后n個通道的數(shù)據(jù)置于低位导犹,再從寄存器N中取出x-n個通道的數(shù)據(jù)置于高位,組成一個新的寄存器數(shù)據(jù)羡忘。
Result_tvext_type(Vector_t N,Vector_t M,intn)Result_tvextq_type(Vector_t N,Vector_t M,intn)
其他數(shù)據(jù)重排指令還有:
vtbl_tyoe,vrev_type,vtrn_type,vzip_type,vunzip_type,vcombine ...
等以后有時間一一講解谎痢。
類型轉(zhuǎn)換指令
強制重新解釋寄存器的值類型,從SrcType轉(zhuǎn)化為DstType卷雕,其內(nèi)部實際值不變且總的字節(jié)數(shù)不變节猿,舉例:vreinterpret_f32_s32(int32x2_t),從int32x2_t轉(zhuǎn)化為float32x2_t爽蝴。
vreinterpret_DstType_SrcType(Vector_t N)
算數(shù)運算指令
[普通指令] 普通加法運算 res = M+N
Result_tvadd_type(Vector_t M,Vector_t N)Result_tvaddq_type(Vector_t M,Vector_t N)
[長指令] 變長加法運算 res = M+N沐批,為了防止溢出,一種做法是使用如下指令蝎亚,加法結(jié)果存儲到長度x2的寄存器中九孩,如:vuint16x8_t res = vaddl_u8(uint8x8_t M,uint8x8_t N)。
Result_tvaddl_type(Vector_t M,Vector_t N)
[寬指令] 加法運算 res = M+N发框,第一個參數(shù)M寬度大于第二個參數(shù)N躺彬。
Result_tvaddw_type(Vector_t M,Vector_t N)
[普通指令] 加法運算 res = trunct(M+N)(溢出則截斷)之后向右平移1位,即計算M和N的平均值
Result_tvhadd_type(Vector_t M,Vector_t N)
[普通指令] 加法運算 res = round(M+N)(溢出則循環(huán))之后向右平移1位梅惯,即計算M和N的平均值
Result_tvrhadd_type(Vector_t M,Vector_t N)
[飽和指令] 飽和加法運算 res = st(M+N)宪拥,如:vuint8x8_t res = vqadd_u8(uint8x8_t M,uint8x8_t N),res超出int8_t的表示范圍(0铣减,255)她君,比如256,則設為255.
Result_tvqadd_type(Vector_t M,Vector_t N)
[窄指令] 加法運算 res = M+N葫哗,結(jié)果比參數(shù)M/N的長度小一半缔刹,如 uint8x8_t res = vaddhn_u16(uint16x8_t M,uint16x8_t N)
Result_tvaddhn_type(Vector_t M,Vector_t N)
[普通指令] 減法運算 res = M-N
Result_tvsub_type(Vector_t M,Vector_t N)
[普通指令] 乘法運算 res = M*N
Result_tvmul_type(Vector_t M,Vector_t N)Result_tvmulq_type(Vector_t M,Vector_t N)
[普通指令] 乘&加法運算 res = M+N*P
Result_tvmla_type(Vector_t M,Vector_t N,Vector_t P)Result_tvmlaq_type(Vector_t M,Vector_t N,Vector_t P)
[普通指令] 乘&減法運算 res = M-N*P
Result_tvmls_type(Vector_t M,Vector_t N,Vector_t P)Result_tvmlsq_type(Vector_t M,Vector_t N,Vector_t P)
類似加法運算,減法和乘法運算也有一系列變種...
數(shù)據(jù)處理指令
[普通指令] 計算絕對值 res=abs(M)
Result_tvabs_type(Vector_t M)
[普通指令] 計算負值 res=-M
Result_tvneg_type(Vector_t M)
[普通指令] 計算最大值 res=max(M,N)
Result_tvmax_type(Vector_t M,Vector_t N)
[普通指令] 計算最小值 res=min(M,N)
Result_tvmin_type(Vector_t M,Vector_t N)
...
比較指令
[普通指令] 比較是否相等 res=mask(M == N)
Result_tvceg_type(Vector_t M,Vector_t N)
[普通指令] 比較是否大于或等于 res=mask(M >= N)
Result_tvcge_type(Vector_t M,Vector_t N)
[普通指令] 比較是否大于 res=mask(M > N)
Result_tvcgt_type(Vector_t M,Vector_t N)
[普通指令] 比較是否小于或等于 res=mask(M <= N)
Result_tvcle_type(Vector_t M,Vector_t N)
[普通指令] 比較是否小于 res=mask(M < N)
Result_tvclt_type(Vector_t M,Vector_t N)
...
歸約指令
[普通指令] 歸約加法劣针,M和N內(nèi)部的元素各自相加校镐,最后組成一個新的結(jié)果
Result_tvpadd_type(Vector_t M,Vector_t N)
[普通指令] 歸約最大比較,M和N內(nèi)部的元素比較得出最大值捺典,最后組成一個新的結(jié)果
Result_tvpmax_type(Vector_t M,Vector_t N)
[普通指令] 歸約最小比較鸟廓,M和N內(nèi)部的元素比較得出最小值,最后組成一個新的結(jié)果
Result_tvpmin_type(Vector_t M,Vector_t N)