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