<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于how-to-optimize-gemm初探矩陣乘法優(yōu)化

          共 423字,需瀏覽 1分鐘

           ·

          2020-11-06 09:05

          1. 前言

          這次,我們來(lái)聊一個(gè)輕松一點(diǎn)的話題,那就是給你一個(gè)矩陣A和一個(gè)矩陣B,使用矩陣乘法獲得目標(biāo)矩陣C,相信大家都不難寫(xiě)出下面的代碼:

          #define?A(?i,?j?)?a[?(i)*lda?+?(j)?]
          #define?B(?i,?j?)?b[?(i)*ldb?+?(j)?]
          #define?C(?i,?j?)?c[?(i)*ldc?+?(j)?]
          //?gemm?C?=?A?*?B?+?C
          void?MatrixMultiply(int?m,?int?n,?int?k,?float?*a,?int?lda,?float?*b,?int?ldb,?float?*c,?int?ldc)
          {
          ????for(int?i?=?0;?i?????????for?(int?j=0;?j????????????for?(int?p=0;?p????????????????C(i,?j)?=?C(i,?j)?+?A(i,?p)?*?B(p,?j);
          ????????????}
          ????????}
          ????}
          }

          然后,上篇文章如何判斷算法是否有可優(yōu)化空間?已經(jīng)測(cè)了這段代碼在單核A53(上篇文章錯(cuò)寫(xiě)為A17,十分抱歉)上的gflops表現(xiàn),這種實(shí)現(xiàn)的gflops只有硬件的2%-3%,是十分低效的,因此這篇文章就是基于https://github.com/flame/how-to-optimize-gemm這個(gè)工程,給大家介紹一下矩陣乘法有哪些可以優(yōu)化的方法。

          需要注意的是,這個(gè)工程是針對(duì)X86上的列主序程序,我這里主要是在移動(dòng)端A53上進(jìn)行測(cè)試,所以將代碼對(duì)應(yīng)修改成了arm指令集,并且修改為更加常見(jiàn)的行主序進(jìn)行測(cè)試。

          原始版本的gFlops測(cè)試結(jié)果如下圖所示:

          原始版本的gFlops測(cè)試結(jié)果

          2. 優(yōu)化之前的工作

          在談到優(yōu)化之前,我們需要將前言中的那部分代碼改成https://github.com/flame/how-to-optimize-gemm中類似的風(fēng)格,這樣便于對(duì)后面各種優(yōu)化技巧代碼的理解。改寫(xiě)風(fēng)格后的代碼如下:

          #include?

          #define?A(?i,?j?)?a[?(i)*lda?+?(j)?]
          #define?B(?i,?j?)?b[?(i)*ldb?+?(j)?]
          #define?C(?i,?j?)?c[?(i)*ldc?+?(j)?]

          /*?Routine?for?computing?C?=?A?*?B?+?C?*/

          /*?Create?macro?to?let?X(?i?)?equal?the?ith?element?of?x?*/

          #define?Y(i)?y[?(i)*incx?]

          void?AddDot(?int?k,?float?*x,?int?incx,??float?*y,?float?*gamma?)
          {
          ??/*?compute?gamma?:=?x'?*?y?+?gamma?with?vectors?x?and?y?of?length?n.
          ?????Here?x?starts?at?location?x?with?increment?(stride)?incx?and?y?starts?at?location?y?and?has?(implicit)?stride?of?1.
          ??*/

          ?
          ??int?p;

          ??for?(?p=0;?p????*gamma?+=?x[p]?*?Y(p);?????
          ??}
          }
          void?MY_MMult1(?int?m,?int?n,?int?k,?float?*a,?int?lda,?
          ????????????????????????????????????float?*b,?int?ldb,
          ????????????????????????????????????float?*c,?int?ldc?)

          {
          ??int?i,?j;
          ??for?(?j=0;?j1?){????????/*?Loop?over?the?columns?of?C?*/
          ????for?(?i=0;?i1?){????????/*?Loop?over?the?rows?of?C?*/
          ??????/*?Update?the?C(?i,j?)?with?the?inner?product?of?the?ith?row?of?A
          ??and?the?jth?column?of?B?*/

          ????//?for?(int?p=0;?p
          ????//?????????????C(i,?j)?=?C(i,?j)?+?A(i,?p)?*?B(p,?j);
          ????//?????????}
          ??????AddDot(?k,?&A(?i,0?),?lda,?&B(?0,j?),?&C(?i,j?)?);
          ????}
          ??}
          }

          考慮到排版和篇幅的原因,后面的優(yōu)化部分只貼最核心的代碼,完整代碼請(qǐng)到https://github.com/BBuf/ArmNeonOptimization查看,也歡迎Star這本項(xiàng)目。

          3. 內(nèi)存對(duì)齊

          這里設(shè)計(jì)到Cache的概念,我嘗試簡(jiǎn)短的描述一下,為什么內(nèi)存對(duì)齊是對(duì)Cache命中有好處的。注意,內(nèi)存對(duì)齊的原則是:任何K字節(jié)的基本對(duì)象的地址必須都是K的倍數(shù)。

          Cache,譯為高速緩沖存儲(chǔ)器,它可以更好的利用局部性原理,減少CPU訪問(wèn)主存的次數(shù)。這里需要再簡(jiǎn)單描述一下計(jì)算機(jī)的存儲(chǔ)體系,在當(dāng)代計(jì)算中存儲(chǔ)器是分為不同層次的,越靠近CPU的存儲(chǔ)器速度越快,制造成本也就越高,同時(shí)容量也越小。最靠近CPU的存儲(chǔ)器是寄存器,它的制造成本最高,所以個(gè)數(shù)也很有限。第二靠近的是緩存(Cache),同時(shí)緩存也是有分級(jí)的,有L1,L2,L3...等多個(gè)級(jí)別。再然后就是主存,即普通的內(nèi)存。最后是本地磁盤(pán)。它們的容量以及訪問(wèn)時(shí)間如下圖所示:

          計(jì)算機(jī)存儲(chǔ)體系結(jié)構(gòu)

          上面說(shuō)Cache可以更好的利用局部性原理,所謂局部性原理就是優(yōu)先從留CPU近的存儲(chǔ)結(jié)構(gòu)中去尋找當(dāng)前需要查找的數(shù)據(jù),加快數(shù)據(jù)訪問(wèn)速度從而減少程序中各個(gè)變量的存取時(shí)間。

          關(guān)于Cache更多的概念可以參考一下文末的資料1,寫(xiě)得非常好。

          “假設(shè) cache line 為 32B。待訪問(wèn)數(shù)據(jù)大小為 64B,地址在 0x80000001,則需要占用 3 條 cache 映射表項(xiàng);若地址在 0x80000000 則只需要 2 條。內(nèi)存對(duì)齊變相地提高了 cache 命中率。” 假定kernel一次計(jì)算執(zhí)行 大小的block, 根據(jù)MMult_4x4_7.c (https://github.com/flame/how-to-optimize-gemm/blob/master/src/MMult_4x4_7.c)和 MMult_4x4_8.c (https://github.com/flame/how-to-optimize-gemm/blob/master/src/MMult_4x4_8.c)代碼,可以看出MMult_4x4_8.c使用了偏移量完成內(nèi)存對(duì)齊。

          這樣我們就可以參考工程的MMult_1x4_3.c改寫(xiě)出一個(gè)FLOPs還不錯(cuò)的分塊的矩陣乘法,代碼實(shí)現(xiàn)如下,為了縮短代碼長(zhǎng)度,隱去了注釋,如果有什么疑問(wèn)歡迎留言區(qū)討論:

          void?AddDot1x4(?int?k,?float?*a,?int?lda,??float?*b,?int?ldb,?float?*c,?int?ldc?)
          {
          ??int?p;
          ??register?float??c_00_reg,???c_01_reg,???c_02_reg,???c_03_reg,?b_0p_reg;
          ??float??*ap0_pntr,?*ap1_pntr,?*ap2_pntr,?*ap3_pntr;?
          ????
          ??ap0_pntr?=?&A(?0,?0?);
          ??ap1_pntr?=?&A(?1,?0?);
          ??ap2_pntr?=?&A(?2,?0?);
          ??ap3_pntr?=?&A(?3,?0?);

          ??c_00_reg?=?0.0;?
          ??c_01_reg?=?0.0;?
          ??c_02_reg?=?0.0;?
          ??c_03_reg?=?0.0;
          ?
          ??for?(?p=0;?p4?){
          ????b_0p_reg?=?B(?p,?0?);

          ????c_00_reg?+=?b_0p_reg?*?*ap0_pntr++;
          ????c_01_reg?+=?b_0p_reg?*?*ap1_pntr++;
          ????c_02_reg?+=?b_0p_reg?*?*ap2_pntr++;
          ????c_03_reg?+=?b_0p_reg?*?*ap3_pntr++;

          ????b_0p_reg?=?B(?p+1,?0?);

          ????c_00_reg?+=?b_0p_reg?*?*ap0_pntr++;
          ????c_01_reg?+=?b_0p_reg?*?*ap1_pntr++;
          ????c_02_reg?+=?b_0p_reg?*?*ap2_pntr++;
          ????c_03_reg?+=?b_0p_reg?*?*ap3_pntr++;

          ????b_0p_reg?=?B(?p+2,?0?);

          ????c_00_reg?+=?b_0p_reg?*?*ap0_pntr++;
          ????c_01_reg?+=?b_0p_reg?*?*ap1_pntr++;
          ????c_02_reg?+=?b_0p_reg?*?*ap2_pntr++;
          ????c_03_reg?+=?b_0p_reg?*?*ap3_pntr++;

          ????b_0p_reg?=?B(?p+3,?0?);

          ????c_00_reg?+=?b_0p_reg?*?*ap0_pntr++;
          ????c_01_reg?+=?b_0p_reg?*?*ap1_pntr++;
          ????c_02_reg?+=?b_0p_reg?*?*ap2_pntr++;
          ????c_03_reg?+=?b_0p_reg?*?*ap3_pntr++;
          ??}

          ??C(?0,?0?)?+=?c_00_reg;?
          ??C(?1,?0?)?+=?c_01_reg;?
          ??C(?2,?0?)?+=?c_02_reg;?
          ??C(?3,?0?)?+=?c_03_reg;
          }

          void?MY_MMult_1x4_8(?int?m,?int?n,?int?k,?float?*a,?int?lda,?
          ????????????????????????????????????float?*b,?int?ldb,
          ????????????????????????????????????float?*c,?int?ldc?)

          {
          ??int?i,?j;
          ??for?(?j=0;?j1?){??????
          ????for?(?i=0;?i4?){????
          ??????AddDot1x4(?k,?&A(?i,0?),?lda,?&B(?0,j?),?ldb,?&C(?i,j?),?ldc?);
          ????}
          ??}
          }

          那么這個(gè)版本的gflops效果如何呢?單核A53測(cè)試結(jié)果如下:

          1x4_8 gflops

          可以看到最高的浮點(diǎn)峰值是原始版本的4倍,說(shuō)明上面的優(yōu)化是行之有效的。

          接下來(lái),我們將分塊的策略從擴(kuò)展到,代碼實(shí)現(xiàn)如下:

          void?AddDot4x4(?int?k,?float?*a,?int?lda,??float?*b,?int?ldb,?float?*c,?int?ldc?)
          {
          ??int?p;
          ??register?float?
          ???????c_00_reg,???c_01_reg,???c_02_reg,???c_03_reg,??
          ???????c_10_reg,???c_11_reg,???c_12_reg,???c_13_reg,??
          ???????c_20_reg,???c_21_reg,???c_22_reg,???c_23_reg,??
          ???????c_30_reg,???c_31_reg,???c_32_reg,???c_33_reg,
          ???????a_0p_reg,
          ???????a_1p_reg,
          ???????a_2p_reg,
          ???????a_3p_reg,
          ???????b_p0_reg,
          ???????b_p1_reg,
          ???????b_p2_reg,
          ???????b_p3_reg;

          ??float?
          ????/*?Point?to?the?current?elements?in?the?four?rows?of?A?*/
          ????*a_0p_pntr,?*a_1p_pntr,?*a_2p_pntr,?*a_3p_pntr;
          ??
          ??a_0p_pntr?=?&A(?0,?0);
          ??a_1p_pntr?=?&A(?1,?0);
          ??a_2p_pntr?=?&A(?2,?0);
          ??a_3p_pntr?=?&A(?3,?0);

          ??c_00_reg?=?0.0;???c_01_reg?=?0.0;???c_02_reg?=?0.0;???c_03_reg?=?0.0;
          ??c_10_reg?=?0.0;???c_11_reg?=?0.0;???c_12_reg?=?0.0;???c_13_reg?=?0.0;
          ??c_20_reg?=?0.0;???c_21_reg?=?0.0;???c_22_reg?=?0.0;???c_23_reg?=?0.0;
          ??c_30_reg?=?0.0;???c_31_reg?=?0.0;???c_32_reg?=?0.0;???c_33_reg?=?0.0;

          ??for?(?p=0;?p????a_0p_reg?=?*a_0p_pntr++;
          ????a_1p_reg?=?*a_1p_pntr++;
          ????a_2p_reg?=?*a_2p_pntr++;
          ????a_3p_reg?=?*a_3p_pntr++;

          ????b_p0_reg?=?B(?p,?0);
          ????b_p1_reg?=?B(?p,?1);
          ????b_p2_reg?=?B(?p,?2);
          ????b_p3_reg?=?B(?p,?3);

          ????/*?First?row?*/
          ????c_00_reg?+=?a_0p_reg?*?b_p0_reg;
          ????c_01_reg?+=?a_0p_reg?*?b_p1_reg;
          ????c_02_reg?+=?a_0p_reg?*?b_p2_reg;
          ????c_03_reg?+=?a_0p_reg?*?b_p3_reg;

          ????/*?Second?row?*/
          ????c_10_reg?+=?a_1p_reg?*?b_p0_reg;
          ????c_11_reg?+=?a_1p_reg?*?b_p1_reg;
          ????c_12_reg?+=?a_1p_reg?*?b_p2_reg;
          ????c_13_reg?+=?a_1p_reg?*?b_p3_reg;

          ????/*?Third?row?*/
          ????c_20_reg?+=?a_2p_reg?*?b_p0_reg;
          ????c_21_reg?+=?a_2p_reg?*?b_p1_reg;
          ????c_22_reg?+=?a_2p_reg?*?b_p2_reg;
          ????c_23_reg?+=?a_2p_reg?*?b_p3_reg;

          ????/*?Four?row?*/
          ????c_30_reg?+=?a_3p_reg?*?b_p0_reg;
          ????c_31_reg?+=?a_3p_reg?*?b_p1_reg;
          ????c_32_reg?+=?a_3p_reg?*?b_p2_reg;
          ????c_33_reg?+=?a_3p_reg?*?b_p3_reg;
          ??}

          ??C(?0,?0?)?+=?c_00_reg;???C(?0,?1?)?+=?c_01_reg;???C(?0,?2?)?+=?c_02_reg;???C(?0,?3?)?+=?c_03_reg;
          ??C(?1,?0?)?+=?c_10_reg;???C(?1,?1?)?+=?c_11_reg;???C(?1,?2?)?+=?c_12_reg;???C(?1,?3?)?+=?c_13_reg;
          ??C(?2,?0?)?+=?c_20_reg;???C(?2,?1?)?+=?c_21_reg;???C(?2,?2?)?+=?c_22_reg;???C(?2,?3?)?+=?c_23_reg;
          ??C(?3,?0?)?+=?c_30_reg;???C(?3,?1?)?+=?c_31_reg;???C(?3,?2?)?+=?c_32_reg;???C(?3,?3?)?+=?c_33_reg;
          }

          然后再測(cè)一下gflops的表現(xiàn):

          4x4_8的gflops

          現(xiàn)在gflops提升到了1.75gflops,性能看起來(lái)好了不少,但是仍然存在隨著矩陣尺寸快速變大性能衰減的問(wèn)題,這個(gè)問(wèn)題請(qǐng)看第六節(jié)。

          4. 向量化SIMD

          一個(gè)比較顯然的優(yōu)化是在k維度計(jì)算的時(shí)候可以使用Neon指令集進(jìn)行優(yōu)化,由于之前這個(gè)專欄中的文章已經(jīng)講得非常多了,這里不再贅述,貼一下在MMult_4x4_8版本基礎(chǔ)上的核心修改部分:

          void?AddDot4x4(?int?k,?float?*a,?int?lda,??float?*b,?int?ldb,?float?*c,?int?ldc?)
          {
          ??float?
          ????*a_0p_pntr,?*a_1p_pntr,?*a_2p_pntr,?*a_3p_pntr;

          ??a_0p_pntr?=?&A(0,?0);
          ??a_1p_pntr?=?&A(1,?0);
          ??a_2p_pntr?=?&A(2,?0);
          ??a_3p_pntr?=?&A(3,?0);

          ??float32x4_t?c_p0_sum?=?{0};
          ??float32x4_t?c_p1_sum?=?{0};
          ??float32x4_t?c_p2_sum?=?{0};
          ??float32x4_t?c_p3_sum?=?{0};

          ??register?float
          ????a_0p_reg,
          ????a_1p_reg,???
          ????a_2p_reg,
          ????a_3p_reg;

          ??for?(int?p?=?0;?p?????float32x4_t?b_reg?=?vld1q_f32(&B(p,?0));

          ????a_0p_reg?=?*a_0p_pntr++;
          ????a_1p_reg?=?*a_1p_pntr++;
          ????a_2p_reg?=?*a_2p_pntr++;
          ????a_3p_reg?=?*a_3p_pntr++;

          ????c_p0_sum?=?vmlaq_n_f32(c_p0_sum,?b_reg,?a_0p_reg);
          ????c_p1_sum?=?vmlaq_n_f32(c_p1_sum,?b_reg,?a_1p_reg);
          ????c_p2_sum?=?vmlaq_n_f32(c_p2_sum,?b_reg,?a_2p_reg);
          ????c_p3_sum?=?vmlaq_n_f32(c_p3_sum,?b_reg,?a_3p_reg);
          ??}

          ??float?*c_pntr?=?0;
          ??c_pntr?=?&C(0,?0);
          ??float32x4_t?c_reg?=?vld1q_f32(c_pntr);
          ??c_reg?=?vaddq_f32(c_reg,?c_p0_sum);
          ??vst1q_f32(c_pntr,?c_reg);

          ??c_pntr?=?&C(1,?0);
          ??c_reg?=?vld1q_f32(c_pntr);
          ??c_reg?=?vaddq_f32(c_reg,?c_p1_sum);
          ??vst1q_f32(c_pntr,?c_reg);

          ??c_pntr?=?&C(2,?0);
          ??c_reg?=?vld1q_f32(c_pntr);
          ??c_reg?=?vaddq_f32(c_reg,?c_p2_sum);
          ??vst1q_f32(c_pntr,?c_reg);

          ??c_pntr?=?&C(3,?0);
          ??c_reg?=?vld1q_f32(c_pntr);
          ??c_reg?=?vaddq_f32(c_reg,?c_p3_sum);
          ??vst1q_f32(c_pntr,?c_reg);
          }

          經(jīng)過(guò)這個(gè)優(yōu)化我們?cè)贉y(cè)試一下當(dāng)前版本(MMult_4x4_10)的gflops表現(xiàn):

          4x4_10 gflops

          在矩陣長(zhǎng)寬小于200時(shí)是有明顯提升的,且最高的浮點(diǎn)峰值提升到了2.5gflops,說(shuō)明這個(gè)優(yōu)化在矩陣規(guī)模不大時(shí)是比較有用的。

          5. 為什么需要分塊&以及什么是分塊?

          前面的兩個(gè)關(guān)鍵的優(yōu)化在矩陣規(guī)模變大之后gflops就快速衰減,這是為什么呢?

          Fig6

          這就和第3節(jié)講到的計(jì)算機(jī)存儲(chǔ)體系結(jié)構(gòu)有關(guān)了,如Fig6所示。當(dāng)我們的AB矩陣的大小比L2 Cache小時(shí),我們的程序只需要從RAM中讀取一次AB大小的內(nèi)存,然后A,B矩陣的數(shù)據(jù)都可以被塞進(jìn)Cache中。但是隨著矩陣的大小增大,當(dāng)AB矩陣的大小超過(guò)了L2 Cache時(shí),由于行主序情況下的B矩陣或者列主序下的A矩陣不是內(nèi)存連續(xù)的,那么程序就要從RAM讀取多次AB矩陣的數(shù)據(jù),這樣數(shù)據(jù)存取將成為整個(gè)程序gflops上升的瓶頸。

          因此,為了解決上一問(wèn)題,gemm論文提出了矩陣分塊的做法,直擊核心,這篇論文針對(duì)矩陣乘法主要提出了下面6種不同的分塊計(jì)算方法,如下圖所示:

          矩陣分塊的不同拆分方法

          這個(gè)圖中透漏了兩個(gè)非常重要的點(diǎn)。

          第一個(gè)是行主序下的A的一行乘以一列獲得C的元素這個(gè)過(guò)程(A*B=C,其中A矩陣大小為,B矩陣大小為,C矩陣大小為)可以等價(jià)為A 的一列和 B 的一行操作得到 大小的一個(gè) C 的“扇面”,多個(gè)“扇面”疊加就是完整的 C。所以這里的分塊策略指的并不是在原始矩陣的長(zhǎng)寬維度上分段計(jì)算,而是類似于一個(gè)z軸上拆分的思路,比較巧妙,所謂z軸就是垂直于矩陣長(zhǎng)寬的維度。可以參考MMult_4x4_10的代碼進(jìn)行理解。

          MMult_4x4_10的結(jié)果來(lái)看,這個(gè)改進(jìn)后的版本在矩陣規(guī)模變大時(shí)gflops也要好于之前的各個(gè)版本。另外為了驗(yàn)證上面的想法(當(dāng)AB矩陣的大小超過(guò)了L2 Cache時(shí),由于行主序情況下的B矩陣或者列主序下的A矩陣不是內(nèi)存連續(xù)的,那么程序就要從RAM讀取多次AB矩陣的數(shù)據(jù),這樣數(shù)據(jù)存取將成為整個(gè)程序gflops上升的瓶頸),我又做了一個(gè)對(duì)比試驗(yàn),即在上面的z軸分塊的版本下進(jìn)一步對(duì)行列兩個(gè)方向也進(jìn)行分塊,設(shè)置的步長(zhǎng)和how-to-optimize-gemm一致,即:

          #define?mc?256?
          #define?kc?128

          void?InnerKernel(?int?m,?int?n,?int?k,?float?*a,?int?lda,?
          ???????????????????????????????????????float?*b,?int?ldb,
          ???????????????????????????????????????float?*c,?int?ldc?)

          {
          ??int?i,?j;

          ??for?(?j=0;?j4?){????????/*?Loop?over?the?columns?of?C,?unrolled?by?4?*/
          ????for?(?i=0;?i4?){????????/*?Loop?over?the?rows?of?C?*/
          ??????/*?Update?C(?i,j?),?C(?i,j+1?),?C(?i,j+2?),?and?C(?i,j+3?)?in
          ??one?routine?(four?inner?products)?*/


          ??????AddDot4x4(?k,?&A(?i,0?),?lda,?&B(0,?j),?ldb,?&C(?i,j?),?ldc?);
          ????}
          ??}
          }

          void?MY_MMult_4x4_11(?int?m,?int?n,?int?k,?float?*a,?int?lda,?
          ????????????????????????????????????float?*b,?int?ldb,
          ????????????????????????????????????float?*c,?int?ldc?)
          ?
          {
          ??int?i,?p,?pb,?ib;?
          ??for?(p?=?0;?p?????pb?=?min(k?-?p,?kc);
          ????for?(i?=?0;?i???????ib?=?min(m?-?i,?mc);
          ??????InnerKernel(ib,?n,?pb,?&A(i,?p),?lda,?&B(p,?0),?ldb,?&C(i,?0),?ldc);
          ????}
          ??}
          }

          然后我們?cè)贉y(cè)一下這個(gè)版本(MMult_4x4_11)的gflops:

          4x4_11 gflops

          對(duì)比一下4x4_10的結(jié)果可以發(fā)現(xiàn),在矩陣規(guī)模變大時(shí),這個(gè)版本的gflops又好了不少,說(shuō)明分塊的確是利用Cache的一個(gè)好辦法,畢竟Cache的容量是非常有限的。

          在Figure4中透漏的第二個(gè)非常重要的點(diǎn)就是數(shù)據(jù)重排,也即數(shù)據(jù)Pack,之前我已經(jīng)講到2次這個(gè)技巧了,在這個(gè)矩陣乘法優(yōu)化中同樣適用。因?yàn)槲覀兎謮K后的AB仍然是內(nèi)存不連續(xù)的,為了提高內(nèi)存的連續(xù)性,在做矩陣乘法之前先對(duì)A,B做了數(shù)據(jù)重排,將第二行要操作的數(shù)放在第一行的末尾,這樣Neon中的數(shù)據(jù)預(yù)取指令將會(huì)生效,極大提高數(shù)據(jù)存取效率。基于這個(gè)想法獲得了改進(jìn)后的版本MMult_4x4_13.c,代碼實(shí)現(xiàn)見(jiàn):https://github.com/BBuf/ArmNeonOptimization/blob/master/optimize_gemm/MMult_4x4_13.h

          測(cè)一下gflops:

          4x4_11 gflops

          可以看到相對(duì)于MMult_4x4_11 在矩陣規(guī)模變大時(shí),這個(gè)版本的gflops提升明顯,已經(jīng)不會(huì)比這個(gè)版本的最高浮點(diǎn)峰值低太多了,說(shuō)明這個(gè)優(yōu)化是十分有效果的。

          6. 總結(jié)

          這篇文章講到的優(yōu)化方法都是有理論支撐的,也就是第5節(jié)展示的gemm論文中的那個(gè)Figure4。gemm論文我打算放到我后面的文章中進(jìn)行解讀,另外會(huì)再分享一些優(yōu)化程度更大的算法,感興趣的請(qǐng)關(guān)注一下我們的公眾號(hào),謝謝。

          為了感謝讀者朋友們的長(zhǎng)期支持,我們今天將送出 3 本由北京大學(xué)出版社提供的《Python最優(yōu)化算法實(shí)戰(zhàn)》書(shū)籍,對(duì)本書(shū)感興趣的可以在上方的留言區(qū)留言,我們將抽取其中三位讀者送出一本正版書(shū)籍。

          書(shū)籍封面

          7. 參考

          • https://blog.csdn.net/qq_21125183/article/details/80590934
          • https://zhuanlan.zhihu.com/p/65436463
          • https://www.cs.utexas.edu/users/pingali/CS378/2008sp/papers/gotoPaper.pdf
          • https://github.com/flame/how-to-optimize-gemm
          • https://github.com/tpoisonooo/how-to-optimize-gemm

          歡迎關(guān)注GiantPandaCV, 在這里你將看到獨(dú)家的深度學(xué)習(xí)分享,堅(jiān)持原創(chuàng),每天分享我們學(xué)習(xí)到的新鮮知識(shí)。( ? ?ω?? )?

          有對(duì)文章相關(guān)的問(wèn)題,或者想要加入交流群,歡迎添加BBuf微信:

          二維碼

          為了方便讀者獲取資料以及我們公眾號(hào)的作者發(fā)布一些Github工程的更新,我們成立了一個(gè)QQ群,二維碼如下,感興趣可以加入。

          公眾號(hào)QQ交流群


          瀏覽 67
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费又黄又爽又色的视频 | 日韩国产欧美成人 | 男女黄色免费网站 | 国产区精品豆花在线 | 看操屄 |