1:性能分析
1.1性能对比
oneapi 与hygonGcc性能对比发现,544课题中的eff.c 1761循环处,oneapi 进行了循环向量化, gcc使用标量,循环源码前加 #pragma clang loop vectorize(disable) 找出oneapi在该循环处关闭和开启loop vect 的性能差距,在15%左右。
mme34.0.svg(图中的svml_log4_mask)

1.2源代码
 1761           for (k = 0; k < lpears[i] + upears[i]; k++) {
1762
1763             if (pearlist[i] == NULL) {
1764                fprintf(nabout,
1765                        "NULL pair list entry in egb loop 1, taskid = %d\n",
1766                        mytaskid);
1767                fflush(nabout);
1768             }
1769             j = pearlist[i][k];
1770
1771             xij = xi - x[dim * j];
1772             yij = yi - x[dim * j + 1];
1773             zij = zi - x[dim * j + 2];
1774             r2 = xij * xij + yij * yij + zij * zij;
1775
1776             if (dim == 4) {                     // delete
1777                wij = wi - x[dim * j + 3];
1778                r2 += wij * wij;
1779             }
1780
1781             if (r2 > rgbmaxpsmax2)      //  %hir.cmp.4310 ule
1782                continue;
1783             dij1i = 1.0 / sqrt(r2);
1784             dij = r2 * dij1i;
1785             sj = fs[j] * (rborn[j] - BOFFSET);   //   select fast
1786             sj2 = sj * sj;
1787
1788             /*
1789              * ---following are from the Appendix of Schaefer and Froemmel,
1790              * JMB 216:1045-1066, 1990;  Taylor series expansion for d>>s
1791              * is by Andreas Svrcek-Seiler; smooth rgbmax idea is from
1792              * Andreas Svrcek-Seiler and Alexey Onufriev.
1793              */
1794
1795             if (dij > rgbmax + sj)      // rgbmax = 20;   %hir.cmp.4333 ule
1796                continue;
1797
1798             if ((dij > rgbmax - sj)) {      //    %hir.cmp.4349  ogt
1799                uij = 1. / (dij - sj);
1800                sumi -= 0.125 * dij1i * (1.0 + 2.0 * dij * uij +
1801                                         rgbmax2i * (r2 -
1802                                                     4.0 * rgbmax *
1803                                                     dij - sj2) +
1804                                         2.0 * log((dij - sj) * rgbmax1i));
1805
1806             } else if (dij > 4.0 * sj) {
1807                dij2i = dij1i * dij1i;
1808                tmpsd = sj2 * dij2i;
1809                dumbo =
1810                    TA + tmpsd * (TB +
1811                                  tmpsd * (TC +
1812                                           tmpsd * (TD + tmpsd * TDD)));
1813                sumi -= sj * tmpsd * dij2i * dumbo;
1814
1815             } else if (dij > ri + sj) {
1816                sumi -= 0.5 * (sj / (r2 - sj2) +
1817                               0.5 * dij1i * log((dij - sj) / (dij + sj)));
1818
1819             } else if (dij > fabs(ri - sj)) {
1820                theta = 0.5 * ri1i * dij1i * (r2 + ri * ri - sj2);
1821                uij = 1. / (dij + sj);
1822                sumi -= 0.25 * (ri1i * (2. - theta) - uij +
1823                                dij1i * log(ri * uij));
1824
1825             } else if (ri < sj) {
1826                sumi -= 0.5 * (sj / (r2 - sj2) + 2. * ri1i +
1827                               0.5 * dij1i * log((sj - dij) / (sj + dij)));
1828
1829             }
1830
1831          }
  | 
2 向量化 遇到的问题
2.1 if printf
                if (pearlist[i] == NULL) {
1764                fprintf(nabout,
1765                        "NULL pair list entry in egb loop 1, taskid = %d\n",
1766                        mytaskid);
1767                fflush(nabout);
1768             }j = pearlist[i][k];  | 
问题:分析不出内存关系,无法ifcvt。引入了控制流,否则control flow in loop。
 eff.c:2072:16: missed: statement clobbers memory: fprintf (nabout.934_2047, "NULL pair list entry in egb loop 1, taskid = %d\n", 0);  | 
解决办法:循环无关的判断,若 pearlist[i] == NULL 则求j = pearlist[i][k]为非法,按照逻辑在if判断里面插入abort()(AOCC 插入了),符合逻辑同时 loop unswitch 可以将其提到循环外
2.2 ifcvt pass
ifcvt pass 是loop vect pass 的必须经过的前置pass, 二者绑定,中间一般不能插入其他pass , 目的为了对if else 分支做conversion , 将分支跳转转化为条件选择,以便于做 loop vect.
对于if else 的结构,ifcvt pass可以将其转化为由原来 多个控制流和bb块的形式,消除控制流合并到到同一个bb块,通过条件运算符?或者 mask_load的方式,使其满足loop vect pass 的 loop form analysis。最内层的loop只能由两个bb块组成的要求,进而进行下一步的loop vect。
ifcvt 消除if中的control flow。
 /* Predicate each write to memory in LOOP. 2401 2402 This function transforms control flow constructs containing memory 2403 writes of the form: 2404 2405 | for (i = 0; i < N; i++) 2406 | if (cond) 2407 | A[i] = expr; 2408 2409 into the following form that does not contain control flow: 2410 2411 | for (i = 0; i < N; i++) 2412 | A[i] = cond ? expr : A[i];  | 
2.3 dim =3 常量传播
 1776             if (dim == 4) {
1777                wij = wi - x[dim * j + 3];
1778                r2 += wij * wij;
1779             }  | 
问题:wij的计算会向量化遇到问题。
解决办法:由于dim 是静态全局变量,尝试过inline 做常量传播,发现并未能实现。调整loop unswitch 参数--param=max-unswitch-insns=150,将其提到循环外面。
2.4 phi 节点的参数超过限制
问题:sumi 的结果的phi节点参数过多,无法进行ifcvt

 BB 90 has complicated PHI with more than 4 args.  | 
解决:aggressive_if_conv = true; 让ifcvt 做激进的优化,不受phi节点参数个数限制。
 3300   /* Apply more aggressive if-conversion when loop or its outer loop were
3301      marked with simd pragma.  When that's the case, we try to if-convert
3302      loop containing PHIs with more than MAX_PHI_ARG_NUM arguments.  */
3303  // aggressive_if_conv = loop->force_vectorize;
3304   aggressive_if_conv = true;
3305   if (!aggressive_if_conv)
3306     {
3307       class loop *outer_loop = loop_outer (loop);
3308       if (outer_loop && outer_loop->force_vectorize)
3309   aggressive_if_conv = true;
3310     }  | 
2.5 gcc在zen架构下,不支持mask gather
 1769 j = pearlist[i][k]; 1781 if (r2 > rgbmaxpsmax2) 1782 continue; 1783 dij1i = 1.0 / sqrt(r2); 1784 dij = r2 * dij1i; 1785 sj = fs[j] * (rborn[j] - BOFFSET); 1786 sj2 = sj * sj;  | 
问题:对于每一轮的j 的值 是不连续的,因此fs[j] 也不连续, 并且在if continue 之后需要进行mask, 不支持使用mask从不连续的内存向量化的取数据。
 467 /* X86_TUNE_USE_GATHER_4PARTS: Use gather instructions for vectors with 4 468 elements. */ 469 /*DEF_TUNE (X86_TUNE_USE_GATHER_4PARTS, "use_gather_4parts", 470 ~(m_ZNVER1 | m_ZNVER2 | m_ZNVER3 | m_ZNVER4 | m_ALDERLAKE | m_GENERIC))*/  | 
解决方法:可以使用-mtune-ctrl=use_gather_4parts 使其支持builtin 形式的mask gather操作。
2.6 多分支下的同一个reduction var 问题
问题:nphi_def_loop_uses > 1 一个reduction 变量在loop 中使用次数超过一个,被认为不是simple reduction 。loop vect pass 不会对其向量化。
 69628 eff.c:1761:24: note: Analyze phi: sumi_1654 = PHI <sumi_1557(320), 0.0(402)>69629 eff.c:1761:24: missed: reduction used in loop.  | 
解决办法:在ifcvt pass 中加上一个函数,实现如下述源码中的,将同一个reduction 变为多个不同的reduction ,实现向量化。
     
1763          double temp0 = 0;
1764          double temp1 = 0;
1765          double temp2 = 0;
1766          double temp3 = 0;
1767          double temp4 = 0;
1805 
1806             if ((dij > rgbmax - sj)) {
1807                uij = 1. / (dij - sj);
1808             
1813             /*   sumi -= 0.125 * dij1i * (1.0 + 2.0 * dij * uij +
1814                                         rgbmax2i * (r2 -
1815                                                     4.0 * rgbmax *
1816                                                     dij - sj2) +
1817                                         2.0 * log((dij - sj) * rgbmax1i));*/
1818            
1823                temp0 -= 0.125 * dij1i * (1.0 + 2.0 * dij * uij +
1824                                         rgbmax2i * (r2 -
1825                                                     4.0 * rgbmax *
1826                                                     dij - sj2) +
1827                                         2.0 * log((dij - sj) * rgbmax1i));
1828 
1829             } else if (dij > 4.0 * sj) {
1830                dij2i = dij1i * dij1i;
1831                tmpsd = sj2 * dij2i;
1832                dumbo =
1833                    TA + tmpsd * (TB +
1834                                  tmpsd * (TC +
1835                                           tmpsd * (TD + tmpsd * TDD)));
1836           
1837           //     sumi -= sj * tmpsd * dij2i * dumbo;
1839                temp1 -= sj * tmpsd * dij2i * dumbo;
1840 
1841             } 
1879          sumi = temp0 + temp1 + temp2+ temp3+temp4;
1880   | 
能够实现向量化,但是性能相对与标量下降了
| 544 | base | 向量化 | 加上分块 | 
| 32 copies | 100 | 92 | 107 | 
向量化的代价太大,临时变量,mask gather load带来的性能下降太显著。(性能下降的loop vect 为什么能通过,cost的计算,没有把mask gather 算进去)。
3. 向量化后优化
3.1:循环分块
问题:gcc 向量化后所有分支的代码在一个bb块里面,所以所有的分支都会走一遍,对最后运算的结果根据分支条件进行选择,带来冗余运算。
解决方法:发现gcc loop pass 上有对optimize_mask_stores,向量化后进行分块的函数。在此基础上拓展添加了对 VEC_COND_EXPR的分块。
添加对于每个分支判断变量全为0,也就是所有元素都不满足该分支,则不需要进入该分支计算,省去了以部分冗余运算。
3.2:向量化因子
使用该选择 -mtune-ctrl=^avx256_split_regs,^avx128_optimal,256_unaligned_store_optimal 打开256非对其存储优化,可以使VF 由 4 变为8, 提高性能。
| 544 | base | VF4 | VF8 | 
| 32 copies | 100 | 101 | 107 | 
4:优化详细说明
4.1 添加的编译选项
 --param=max-unswitch-insns=150 -ftree-insert-abort -mtune-ctrl=^avx256_split_regs,^avx128_optimal,256_unaligned_store_optimal,use_gather_4parts  | 
1:loop unswitch insns 参数调整,默认是50,调整到150
2:插入一个新的pass 来插入abort()
3: 使能256bit的非对齐存储优化,VF为8.(^avx256_split_regs 不加这个也可)
4:使能256bit 的 mask gather 指令。
4.2:添加的优化代码
4.2.1 激进的ifcvt优化 (找到选项来控制)?
将 aggressive_if_conv 标志位修改为true。(能否通过某些选项来控制)
 3303 // aggressive_if_conv = loop->force_vectorize; 3304 aggressive_if_conv = true;  | 
4.2.2 插入abort() 优化
新建一个gimple pass, 放在adjust_alignment pass 后。(107 pass)
 199 NEXT_PASS (pass_adjust_alignment); 200 NEXT_PASS (pass_insert_abort); 201 NEXT_PASS (pass_all_optimizations);  | 
设计思路:识别对0进行判断gimple_cond,并且判断为true的bb 以及其single suc,有对该gimple cond lhs 定义的stmt进行解引用,则认为是对空指针解引用回出现问题,插入abort()
识别的pattern:
(1) :识别一个gimple_cond 语句,并且是和0进行相等判断
(2) :找到判断为true的bb块以及其后继bb块中,是否有对该判断条件lhs的def的stmt进行解引用使用。
transform:
(1):找到后在gimple cond条件为true的bb 末尾插入abort()。
 14044 <bb 148> [local count: 919275880]: 14045 _2044 = _127 + _2039; 14046 _2045 = *_2044; 14047 if (_2045 == 0B) 14048 goto <bb 149>; [17.43%] 14049 else 14050 goto <bb 150>; [82.57%] 14051 14052 <bb 149> [local count: 160229786]: 14053 _2046 = 0; 14054 _2047 = nabout; 14055 fprintf (_2047, "NULL pair list entry in egb loop 1, taskid = %d\n", _2046); 14056 _2048 = nabout; 14057 fflush (_2048); 14058 14059 <bb 150> [local count: 919275880]: 14060 _2049 = *_2044; 14061 _2051 = (long unsigned int) k_2050; 14062 _2052 = _2051 * 4; 14063 _2053 = _2049 + _2052; 14064 j_2054 = *_2053;  | 
4.2.3 消除reduction use in loop (改变一种方式,消除reduction,重新修改代码),如果是sumi的加减混合运算?
使用其他方法修改reduction 后性能没有原方法好,因为原方法都是在循环外面,新方法需要在循环里面。
在ifcvt pass 中添加函数,(尝试在loop vect pass 中当出现reduction use in loop 后transform , 新建的reduction 会错过 loop vect 中对reduction 的分析)
思路:识别reduction 在 loop 中的使用次数超过1次的情况,找到该reduction 的phi 节点,在每次使用的地方进行替换成为不同的reduction,需要对是否进行ifcvt的两个版本都进行转化。标量和向量版本都会走到(如何识别是reduction?) 复用发现reduction 的接口,在一个新的pass 里面加上这部分代码。
(该优化是根据先修改源码后,产生的IR,进行对照修改,涉及到的修改较多,目前想仅仅针对该课题pattern,其他情况比较难以覆盖)
 1761           for (k = 0; k < lpears[i] + upears[i]; k++) {
1762
1763             if (pearlist[i] == NULL) {
1764                fprintf(nabout,
1765                        "NULL pair list entry in egb loop 1, taskid = %d\n",
1766                        mytaskid);
1767                fflush(nabout);
1768             }
1769             j = pearlist[i][k];
1770
1771             xij = xi - x[dim * j];
1772             yij = yi - x[dim * j + 1];
1773             zij = zi - x[dim * j + 2];
1774             r2 = xij * xij + yij * yij + zij * zij;
1775
1776             if (dim == 4) {                     // delete
1777                wij = wi - x[dim * j + 3];
1778                r2 += wij * wij;
1779             }
1780
1781             if (r2 > rgbmaxpsmax2)      //  %hir.cmp.4310 ule
1782                continue;
1783             dij1i = 1.0 / sqrt(r2);
1784             dij = r2 * dij1i;
1785             sj = fs[j] * (rborn[j] - BOFFSET);   //   select fast
1786             sj2 = sj * sj;
1787
1788             /*
1789              * ---following are from the Appendix of Schaefer and Froemmel,
1790              * JMB 216:1045-1066, 1990;  Taylor series expansion for d>>s
1791              * is by Andreas Svrcek-Seiler; smooth rgbmax idea is from
1792              * Andreas Svrcek-Seiler and Alexey Onufriev.
1793              */
1794
1795             if (dij > rgbmax + sj)      // rgbmax = 20;   %hir.cmp.4333 ule
1796                continue;
1797             double temp = 0;
1798             if ((dij > rgbmax - sj)) {      //    %hir.cmp.4349  ogt
1799                uij = 1. / (dij - sj);
1800                temp -= 0.125 * dij1i * (1.0 + 2.0 * dij * uij +
1801                                         rgbmax2i * (r2 -
1802                                                     4.0 * rgbmax *
1803                                                     dij - sj2) +
1804                                         2.0 * log((dij - sj) * rgbmax1i));
1805
1806             } else if (dij > 4.0 * sj) {
1807                dij2i = dij1i * dij1i;
1808                tmpsd = sj2 * dij2i;
1809                dumbo =
1810                    TA + tmpsd * (TB +
1811                                  tmpsd * (TC +
1812                                           tmpsd * (TD + tmpsd * TDD)));
1813                temp -= sj * tmpsd * dij2i * dumbo;
1814
1815             } else if (dij > ri + sj) {
1816                temp -= 0.5 * (sj / (r2 - sj2) +
1817                               0.5 * dij1i * log((dij - sj) / (dij + sj)));
1818
1819             } else if (dij > fabs(ri - sj)) {
1820                theta = 0.5 * ri1i * dij1i * (r2 + ri * ri - sj2);
1821                uij = 1. / (dij + sj);
1822                temp -= 0.25 * (ri1i * (2. - theta) - uij +
1823                                dij1i * log(ri * uij));
1824
1825             } else if (ri < sj) {
1826                temp -= 0.5 * (sj / (r2 - sj2) + 2. * ri1i +
1827                               0.5 * dij1i * log((sj - dij) / (sj + dij)));
1828
1829             }sumi+=temp;
1830
1831          }  | 
进行ifcvt后的部分(向量)(初始化为0,不为0 的情况 应该直接将其初始化结果加入到新建的reduction 变量中)
(1):在loop header bb 中识别phi stmt,找到其lhs中使用次数等于该分支数量,并且初始化为0的phi stmt。
transfrom:
(1):新建与分支数量相等的phi stmt ,初始值为0和该循环计算后的结果,作为一个新的reduction.
(2) : 新建该reduction 和 每个分支运算结果的gimple assign。
(3):对最后结果累加运算。
 48399 # k_1747 = PHI <k_2147(234), 0(279)> 48400 # sumi_1470 = PHI <sumi_2753(234), 0.0(279)> 48401 # RANGE [0, 2147483647] NONZERO 2147483647 48488 sumi_2087 = sumi_1470 + _2085; 48489 _3187 = dij_2056 <= _2068;48507 sumi_2102 = sumi_1470 - _2101; 48508 _3169 = dij_2056 <= _2088; 48509 _3168 = _3169 & _3184;  | 
转化为:
 51162 # temp_value.1035_2087 = PHI <tmp_var.1036_2090(320), 0.0(402)> 51164 # temp_value.1043_2073 = PHI <tmp_var.1044_2063(320), 0.0(402)>51320 _ifc__2108 = _1303 ? _1605 : 0.0;51321 tmp_var.1036_2090 = _ifc__2108 + temp_value.1035_2087; 51324 _ifc__2064 = _1315 ? _1586 : 0.0;51325 tmp_var.1044_2063 = _ifc__2064 + temp_value.1043_2073;52177 # tmp_sumi.1045_2075 = PHI <tmp_var.1044_2063(304), tmp_sumi_2.1060_2061(399)>52175 # tmp_sumi.1037_2076 = PHI <tmp_var.1036_2090(304), tmp_sumi_2.1056_2057(399)>52190 # sumi_suc.1046_2042 = PHI <tmp_sumi.1045_2075(351), 0.0(74), tmp_sumi.1074_644(352)>52188 # sumi_suc.1038_2775 = PHI <tmp_sumi.1037_2076(351), 0.0(74), tmp_sumi.1068_704(352)> 52193 _2052 = sumi_suc.1038_2775 + sumi_suc.1046_2042;  | 
不进行ifcvt的部分(标量)
transform:
(1):新建phi stmt 初始化为0,与分支数量相同。
(2):修改每个分支的中间运算结果。
(3):对每个分支的结果用phi节点来控制。
 # sumi_3078 = PHI <0.0(280), sumi_2855(277)>sumi_2872 = sumi_3078 + _2911;sumi_2973 = sumi_3078 - _2974;sumi_2855 = PHI <sumi_3078(261), sumi_3078(263), sumi_3078(269), sumi_3016(271), sumi_2999(272), sumi_2988(273), sumi_2973(274), sumi_2872(275)>  | 
转化为:
 51867 # temp_value_2.1081_596 = PHI <0.0(426), tmp_sumi_2.1082_597(423)>51868 # temp_value_2.1083_598 = PHI <0.0(426), tmp_sumi_2.1084_575(423)>sumi_1004 = temp_value_2.1081_596 + _1003;sumi_866 = temp_value_2.1083_598 - _865;# tmp_sumi_2.1082_597 = PHI <temp_value_2.1081_596(407),temp_value_2.1081_596(409),temp_value_2.1081_596(412),temp_value_2.1081_596(414), temp_value_2.1081_596(416),temp_value_2.1081_596(418),temp_value_2.1081_596(419), sumi_1004(421)># tmp_sumi_2.1084_575 = PHI <temp_value_2.1083_598(407), temp_value_2.1083_598(409), temp_value_2.1083_598(412), temp_value_2.1083_598(414), temp_value_2.1083_598(416), temp_value_2.1083_598(418), sumi_866(419), temp_value_2.1083_598(421)>  | 
4.2.4 if else / if continue 向量化后分块优化 (添加cost计算?)
targetm.vectorize.empty_mask_is_expensive
aarch64_empty_mask_is_expensive 这里不做mask_store 的
default_empty_mask_is_expensive 其他情况都会进行
 26378 /* Implement TARGET_VECTORIZE_EMPTY_MASK_IS_EXPENSIVE.  Assume for now that
26379    it isn't worth branching around empty masked ops (including masked
26380    stores).  */
26381
26382 static bool
26383 aarch64_empty_mask_is_expensive (unsigned)
26384 {
26385   return false;
26386 }  | 
仿照loop vect pass 中的optimize_mask_stores,在loop vect pass 中 加上基于vec_cond_expr分块的函数
思路:(1)识别loop 中在同一个bb 中的 VEC_COND_EXPR,
(2)根据第二个参数找到每个分支最后计算的结果,从此处开始拆分bb。
(3)根据第一个参数mask,找到每个分支条件,将这两个mask相或,在此后新建一个该或结果与0进行比较的gimple_cond,插入到分支条件后,同时新建该结果判断为ture 和 false的edge,分别指向新建的bb和其下一个bb。
(4)在拆分bb 块后面新建一个空 bb ,并且gimple_cond 之后的stmt 移动到新建的bb 中,维护其edge,指向下一个bb.
  98615   vect__ifc__2106.1217_795 = VEC_COND_EXPR <mask__1612.1193_854, vect__1541.1206_870, { 0.0, 0.0, 0.0, 0.0 }>;98616   vect__ifc__2106.1217_796 = VEC_COND_EXPR <mask__1612.1193_821, vect__1541.1206_871, { 0.0, 0.0, 0.0, 0.0 }>; 98621   vect__ifc__2706.1220_803 = VEC_COND_EXPR <mask__1617.1210_879, vect__1555.1216_792, { 0.0, 0.0, 0.0, 0.0 }>;98622   vect__ifc__2706.1220_804 = VEC_COND_EXPR <mask__1617.1210_880, vect__1555.1216_793, { 0.0, 0.0, 0.0, 0.0 }>;   | 



5.其他优化探索
5.1带有mask的向量数学函数
在loop vect pass 后新建一个pass ,替换原有的数学函数调用带有mask的svml函数。
  98615   vect__ifc__2106.1217_795 = VEC_COND_EXPR <mask__1612.1193_854, vect__1541.1206_870, { 0.0, 0.0, 0.0, 0.0 }>;   | 
设计方案:找到一个VEC_COND_EXPR,在同一个基本块中,根据参数中的分支运算的结果,顺着运算的关系一步步往上找(SSA_NAME_DEF_STMT),进行深度优先遍历,直到找到了需要进行mask的数学函数。VEC_COND_EXPR中的参数mask就是数学函数需要进行mask的值。将数学函数和mask一起生成带有mask的数学函数的IR,替换掉原来的不带mask的。


由于次优化的性能和循环分块重叠,因此不需要加入。
5.2 与oneapi性能差距的探索:

对分支条件进行两次判断,一次判断全为false,则不进入分支里面的运算。一次判断全为true,如果正确,则只需要进入该分支的计算,其他分支不需要进入。相比于上述方案还省了分支条件全为true的时候的其他分支条件的运算。
由于涉及到的控制流和bb块的修改较多,且不好验证性能,暂且不做。
最终结果:性能在544 上提升7%。
| 544 | base | optimize | 
| 32 copies | 100 | 107 | 
targetm.vectorize.empty_mask_is_expensive
或者看ifcvt IR 里面有没有temp_value,loop vect里面有没有combined_mask