<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > ARM處理器NEON編程及優(yōu)化技巧——矩陣乘法的實例

          ARM處理器NEON編程及優(yōu)化技巧——矩陣乘法的實例

          作者: 時間:2016-11-10 來源:網(wǎng)絡(luò) 收藏
          ARM的NEON協(xié)處理器技術(shù)是一個64/128-bit的混合SIMD架構(gòu),用于加速包括視頻編碼解碼、音頻解碼編碼、3D圖像、語音和圖像等多媒體和信號處理應(yīng)用。本文主要介紹如何使用NEON的匯編程序來寫SIMD的代碼,包括如何開始NEON的開發(fā),如何高效的利用NEON。首先會關(guān)注內(nèi)存操作,即如何變更指令來靈活有效的加載和存儲數(shù)據(jù)。接下來是由于SIMD指令的應(yīng)用而導(dǎo)致剩下的若干個單元的處理,然后是用一個矩陣乘法的例子來說明用NEON來進行SIMD優(yōu)化,最后關(guān)注如何用NEON來優(yōu)化各種各樣的移位操作,左移或者右移以及雙向移位等。本節(jié)是一個用NEON優(yōu)化矩陣乘法的實例。

          矩陣

          本節(jié)將介紹如何用NEON有效的處理一個4x4的矩陣乘法運算,這種類型的運算經(jīng)常用于3D圖形,我們認為這些矩陣在內(nèi)存里是按照列為主排列的,這是按照OPENGL-ES的通用格式。

          本文引用地址:http://www.ex-cimer.com/article/201611/317424.htm

          矩陣乘法算法

          我們首先看一下矩陣乘法的計算方式,計算的展開,用NEON指令來進行子操作過程。

          圖1. 以列為主的矩陣乘法運算

          由于數(shù)據(jù)是按照列序存儲的,因而矩陣乘法就是把第一個矩陣的每一列乘以第二個矩陣的每一行,然后把乘積結(jié)果相加。乘累加結(jié)果 作為結(jié)果矩陣的一個元素。

          圖2. 矩陣乘法中的向量乘以標(biāo)量的運算

          假設(shè)每列元素在NEON寄存器中表示為一個向量,那么上述的矩陣乘法就是一個向量乘以標(biāo)量的運算,而后續(xù)的累加也同樣可以同向量乘以標(biāo)量的累加指令實現(xiàn)。因為我們的操作是在第一個矩陣的列,然后計算列的結(jié)果,讀列元素和寫列元素都是線性的加載和存儲操作,不需要interleave的加載和存儲操作。

          代碼

          浮點運算版本

          首先看一個單精度浮點的矩陣乘法實現(xiàn)。首先加載矩陣元素到NEON寄存器,然后按照列序做乘法,用VLD1做線性的加載數(shù)據(jù)到NEON寄存器,用VST1把計算結(jié)果保存到內(nèi)存。

          vld1.32 {d16-d19}, [r1]!            @ 加載矩陣0的上8個元素 

          vld1.32 {d20-d23}, [r1]!            @ 加載矩陣0的下8個元素 

          vld1.32 {d0-d3}, [r2]!              @ 加載矩陣1的上8個元素 

          vld1.32 {d4-d7}, [r2]!              @ 加載矩陣1的下8個元素 

          NEON有32個64位寄存器,因而加載所有的輸入矩陣元素到16個64-bit寄存器,我們?nèi)匀挥?6個64位寄存器做后續(xù)的處理。

          D和Q寄存器

          多數(shù)的NEON指令有兩種方法來訪問寄存器組:

          • 作為32個雙字寄存器,64-bit位寬,命名為d0-d31
          • 作為16個四字寄存器,128-bit位寬,命名為q0-q15

          圖3. NEON寄存器組

          這些寄存器中一個Q寄存器是一對D寄存器的別名,如Q0是d0和d1寄存器對的別名,寄存器中的值可以用兩種方式訪問。這種實現(xiàn)方式很類似C語言里的union聯(lián)合的數(shù)據(jù)結(jié)構(gòu)。對于浮點的矩陣乘法,我們會經(jīng)常使用Q寄存器的表達方式,因為經(jīng)常會處理4個32-bit的單精度浮點,這對應(yīng)于128-bit的Q寄存器。

          代碼部分

          通過以下4條NEON乘法指令能完成一列4個結(jié)果:

          vmul.f32    q12, q8, d0[0]        @ 向量乘以標(biāo)量(MUL),矩陣0的第一列乘以矩陣1的每列的第一個元素0 

          vmla.f32    q12, q9, d0[1]        @ 累加的向量乘以標(biāo)量(MAC),矩陣0的第二列乘以矩陣1的每列的第二個元素1 

          vmla.f32    q12, q10, d1[0] @ 累加的向量乘以標(biāo)量(MAC),矩陣0的第二列乘以矩陣1的每列的第二個元素2 

          vmla.f32    q12, q11, d1[1] @ 累加的向量乘以標(biāo)量(MAC),矩陣0的第二列乘以矩陣1的每列的第二個元素3 

          第一條指令是圖2中的列元素x0, x1, x2, x3 (寄存器q8)乘以y0 (d0[0]),然后結(jié)果保存到q12寄存器。接下來的指令操作類似,就是把第一個矩陣的其他列乘以第二個矩陣的第一列的響應(yīng)元素,結(jié)果累加到寄存器Q12里。需要注意的是標(biāo)量元素如d1[1]也可以用q0[3]表示,但是可能編譯器如GNU匯編器會不能接受這種方式。

          如果我們只需要矩陣乘以向量的運算,如很多3D圖像處理中的那樣,那么此時的計算就結(jié)束了,可以把結(jié)果向量保存 到內(nèi)存了,但是為了完成矩陣相乘,還需要完成后面的迭代操作,使用寄存器Q1到Q3的y4到y(tǒng)F的元素。如果定義如下的宏,那么就能簡化代碼結(jié)構(gòu)了:

          .macro mul_col_f32 res_q, col0_d, col1_d 

          vmul.f32    res_q, q8, col0_d[0]      @向量乘以標(biāo)量(MUL),矩陣0的第一列乘以矩陣1的每列的第一個元素0 

          vmla.f32    res_q, q9, col0_d[1]      @累加的向量乘以標(biāo)量(MAC),矩陣0的第二列乘以矩陣1的每列的第二個元素1 

          vmla.f32    res_q, q10, col1_d[0] @累加的向量乘以標(biāo)量(MAC),矩陣0的第二列乘以矩陣1的每列的第二個元素2 

          vmla.f32    res_q, q11, col1_d[1] @累加的向量乘以標(biāo)量(MAC),矩陣0的第二列乘以矩陣1的每列的第二個元素3 

          .endm 

          那么整個4x4的矩陣乘法代碼可能如下:

          vld1.32 {d16-d19}, [r1]!            @ 加載矩陣0的上8個元素 

          vld1.32 {d20-d23}, [r1]!            @ 加載矩陣0的下8個元素 

          vld1.32 {d0-d3}, [r2]!              @ 加載矩陣1的上8個元素 

          vld1.32 {d4-d7}, [r2]!              @ 加載矩陣1的下8個元素 

          mul_col_f32 q12, d0, d1         @ 矩陣 0 * 矩陣1的第一列 

          mul_col_f32 q13, d2, d3         @ 矩陣 0 * 矩陣1的第二列 

          mul_col_f32 q14, d4, d5         @ 矩陣 0 * 矩陣1的第三列 

          mul_col_f32 q15, d6, d7         @ 矩陣 0 * 矩陣1的第四列 

          vst1.32 {d24-d27}, [r0]!            @ 保存結(jié)果的上8個元素 

          vst1.32 {d28-d31}, [r0]!            @ 保存結(jié)果的下8個元素 

          定點算法

          定點算法計算往往比浮點計算更快,因為往往定點運算可能需要更少的內(nèi)存帶寬,整數(shù)值的乘法也會比浮點算法更為簡單。但是定點算法,你需要很仔細的選擇表示格式來避免溢出或者飽和,這些會影響你的算法最終的精度。定點算法實現(xiàn)的矩陣乘法和浮點算法類似,在本例中,用Q1.14定點格式,但是基本的實現(xiàn)格式基本類似,只是實現(xiàn)中可能需要對結(jié)果做一些移位調(diào)整。下面是列乘的宏:

          .macro mul_col_s16 res_d, col_d 

          vmull.s16   q12, d16, col_d[0] @ 向量乘以標(biāo)量(MUL),矩陣0的第一列乘以矩陣1的每列的第一個元素0 

          vmlal.s16   q12, d17, col_d[1] @ 累加的向量乘以標(biāo)量(MAC),矩陣0的第二列乘以矩陣1的每列的第二個元素1 

          vmlal.s16   q12, d18, col_d[2] @ 累加的向量乘以標(biāo)量(MAC),矩陣0的第二列乘以矩陣1的每列的第二個元素2 

          vmlal.s16   q12, d19, col_d[3] @ 累加的向量乘以標(biāo)量(MAC),矩陣0的第二列乘以矩陣1的每列的第二個元素3 

          vqrshrn.s32 res_d, q12, #14             @ 把結(jié)果右移14位,并把累加結(jié)果變成Q1.14定點格式,并飽和運算 

          .endm 

          比較定點和浮點算法的宏,你會發(fā)現(xiàn)如下的主要區(qū)別:

          • 矩陣元素的值為16位而不是32位,因而用D寄存器來保存4個輸入元素
          • 矩陣乘法的結(jié)果是把16x16=32位的數(shù)據(jù),使用VMULL和VMLAL來吧結(jié)果保存到Q寄存器。
          • 最終結(jié)果也是16位,因而需要把32位累加器結(jié)果來得到16-bit的結(jié)果,使用VQRSHRN,飽和處理把32位的結(jié)果舍入到16位的narrow右移操作。

          把數(shù)據(jù)從32-bits變成16-bits也能有效的處理內(nèi)存訪問,加載和存儲數(shù)據(jù)都只需要更少的帶寬。

          vld1.16 {d16-d19}, [r1]     @ 加載16個元素到矩陣0 

          vld1.16 {d0-d3}, [r2]       @ 加載16個元素到矩陣1 

          mul_col_s16 d4, d0                      @ 矩陣0乘以矩陣1的列0 

          mul_col_s16 d5, d1                      @ 矩陣0乘以矩陣1的列1 

          mul_col_s16 d6, d2                      @ 矩陣0乘以矩陣1的列2 

          mul_col_s16 d7, d3                      @ 矩陣0乘以矩陣1的列3 

          vst1.16 {d4-d7}, [r0]       @ 保存16個結(jié)果元素 

          指令重排

          我們先展示一下指令重排如何能提高代碼性能。在宏中,臨近的乘法指令會寫入到相同的目標(biāo)寄存器,這會讓NEON的流水線等待前面的乘法結(jié)果完成才能開始下一條指令的執(zhí)行。如果不使用宏定義,而合理安排指令的次序,把那些相關(guān)依賴的指令變成不依賴,這些指令就能并發(fā)而不會造成流水線的stall。

          vmul.f32    q12, q8, d0[0]              @ rslt col0  = (mat0 col0) * (mat1 col0 elt0) 

          vmul.f32    q13, q8, d2[0]              @ rslt col1  = (mat0 col0) * (mat1 col1 elt0) 

          vmul.f32    q14, q8, d4[0]              @ rslt col2  = (mat0 col0) * (mat1 col2 elt0) 

          vmul.f32    q15, q8, d6[0]              @ rslt col3  = (mat0 col0) * (mat1 col3 elt0) 

          vmla.f32    q12, q9, d0[1]              @ rslt col0 += (mat0 col1) * (mat1 col0 elt1) 

          vmla.f32    q13, q9, d2[1]              @ rslt col1 += (mat0 col1) * (mat1 col1 elt1) 

          ... 

          ... 

          用以上的處理方式,矩陣乘法的性能在Cortex-A8處理平臺上性能提升了一倍。從文檔arm.com/help/index.jsp?topic=/com.arm.doc.set.cortexa/index.html" rel="nofollow">Technical Reference Manual for your Cortex core可以看到 各個指令的需要時間以及延遲,有這些延遲周期,能夠更為合理的安排代碼次序,提升性能。



          評論


          技術(shù)專區(qū)

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();