0.Introduction
Superword Level Parallelism (SLP)矢量化是llvm auto-vectorization中的一种,另一种是loop vectorizer,详见于Auto-Vectorization in LLVM[1]。 它在2000年由Larsen 和 Amarasinghe首次作为basic block矢量化提出。SLP矢量化的目标是将相似的独立指令组合成向量指令,内存访问、算术运算、比较运算、PHI节点都可以使用这种技术进行矢量化。它和循环矢量化最大的差异在于,循环矢量化关注迭代间的矢量化机会,而SLP更关注于迭代内basic block中的矢量化的机会。
一个简单的小例子 case.cpp[1]:
void foo(float a1, float a2, float b1, float b2, float *A) {
A[0] = a1*(a1 + b1);
A[1] = a2*(a2 + b2);
A[2] = a1*(a1 + b1);
A[3] = a2*(a2 + b2);
}
命令:clang++ case.cpp -O3 -S ;SLP在clang中是默认使能的,可以看到汇编中已出现使用矢量寄存器的fadd和fmul。
如果编译命令中加上选项-fno-slp-vectorize 或者 -mllvm -vectorize-slp=false 关闭该优化,则只能得到标量的版本。
让我们来跟随《Exploiting Superword Level Parallelism with Multimedia Instruction Sets》[2]这篇经典论文来探究一下SLP矢量化的奥秘。
1.原始SLP算法介绍
1.1概述
论文中用一张图来解释了SLP要做的事情:
原始SLP例子
[2]
这四条语句中的位置相对应的操作数,比如(b,e,s,x)可以pack到一个向量寄存器 Vb中,同样的,(c,f,t,y)可以pack到 Vc,(z[i+0]~z[i+3])可以到 Vd。然后可以利用simd指令进行相应的矢量化计算。最后根据Va 中(a,d,r,w)的被使用方式,可能还需要将他们从向量寄存器中load出来,称为unpack。
所以,如果pack操作数的开销 + 矢量化执行的开销 + unpack操作数的开销小于原本执行的开销,那就证明SLP矢量化具有性能收益[3]。
1.2 优化场景
为了进一步说明SLP和循环矢量化在优化场景上的差异,论文[2]中给了两个例子(可以通过https://godbolt.org/z/EWr4zTc3P直接查看汇编情况)。
(1)对于原始循环 a,既可以通过 scalar expansion (a method of converting scalar data to match the dimensions of vector or matrix data.) 和 loop fission (the opposite of loop fusion: a loop is split into two or more loops. ) 后被转换为可以进行循环向量化的形式 b,一个induction和一个reduction;也可以经过unroll和rename之后变为 d 这样的形式,做SLP。但其实由于论文比较老了,目前llvm编译器对于a这样形式的循环可以直接做矢量化。
for (i=0; i<16; i++) {
localdiff = ref[i] - curr[i];
diff += abs(localdiff);
}
(a) Original loop.
for (i=0; i<16; i++) {
T[i] = ref[i] - curr[i];
}
for (i=0; i<16; i++) {
diff += abs(T[i]);
}
(b) After scalar expansion and loop fission.
for (i=0; i<16; i+=4) {
localdiff = ref[i+0] - curr[i+0];
diff += abs(localdiff);
localdiff = ref[i+1] - curr[i+1];
diff += abs(localdiff);
localdiff = ref[i+2] - curr[i+2];
diff += abs(localdiff);
localdiff = ref[i+3] - curr[i+3];
diff += abs(localdiff);
}
(c) Superword level parallelism exposed after unrolling.
for (i=0; i<16; i+=4) {
localdiff0 = ref[i+0] - curr[i+0];
localdiff1 = ref[i+1] - curr[i+1];
localdiff2 = ref[i+2] - curr[i+2];
localdiff3 = ref[i+3] - curr[i+3];
diff += abs(localdiff0);
diff += abs(localdiff1);
diff += abs(localdiff2);
diff += abs(localdiff3);
}
(d) Packable statements grouped together after renaming.
(2)但是对于如下例子,循环向量化需要将do while循环转换为for循环,恢复归纳变量,将展开后的循环恢复为未展开的形式(loop rerolling)。而SLP只需要将计算 dst[{0, 1, 2, 3}] 的这四条语句组合成一条 使用向量化指令的语句即可。
do {
dst[0] = (src1[0] + src2[0]) >> 1;
dst[1] = (src1[1] + src2[1]) >> 1;
dst[2] = (src1[2] + src2[2]) >> 1;
dst[3] = (src1[3] + src2[3]) >> 1;
dst += 4;
src1 += 4;
src2 += 4;
}
while (dst != end);
看到这里,可以了解到哪些是SLP的优化机会。论文中提出了一种简单的算法来实现,简而言之是通过寻找independent(无数据依赖)、isomorphic(相同操作)的指令组合成一条向量化指令。
那么如何找呢?
1.3 算法描述
作者注意到如果被 pack 的指令的操作数引用的是相邻的内存,那么特别适合 SLP 执行。所以核心算法就是从识别 adjacent memory references 开始的。
当然寻找这样的相邻内存引用前也需要做一些准备工作,主要是三部分:(1) Loop Unrolling;(2) Alignment analysis;(3) Pre-Optimization(主要是一些死代码和冗余代码消除)。具体不展开讲。
接下来我们来看看核心算法,主要分为以下4步:
- Identifying Adjacent Memory References
- Extending the PackSet
- Combination
- Scheduling
伪代码[4]是:
S L P _ e x t r a c t : B a s i c B l o c k B → B a s i c B l o c k P a c k S e t P ← ∅ P ← f i n d _ a d j _ r e f s ( B , P ) P ← e x t e n d _ p a c k l i s t ( B , P ) P ← c o m b i n e _ p a c k s ( P ) r e t u r n s c h e d u l e ( B , [ ] , P ) \begin{array}{l} SLP\_extract: BasicBlock B \rightarrow BasicBlock \\ \quad \quad PackSet P \leftarrow \emptyset \\ \quad \quad P \leftarrow find\_adj\_refs (B, P) \\ \quad \quad P \leftarrow extend\_packlist (B, P) \\ \quad \quad P \leftarrow combine\_packs (P) \\ \quad \quad return \quad schedule (B,[], P) \\ \end{array} SLP_extract:BasicBlockB→BasicBlockPackSetP←∅P←find_adj_refs(B,P)P←extend_packlist(B,P)P←combine_packs(P)returnschedule(B,[],P)
(1)第一步 find_adj_refs
先来看第一步:Identifying Adjacent Memory References
f i n d _ a d j _ r e f s : B a s i c B l o c k B × P a c k S e t P → P a c k S e t f o r e a c h S t m t s ∈ B d o f o r e a c h S t m t s ′ ∈ B w h e r e s ≠ s ′ d o i f h a s _ m e m _ r e f ( s ) ∧ h a s _ m e m _ r e f ( s ′ ) t h e n i f a d j a c e n t ( s , s ′ ) t h e n I n t a l i g n ← g e t _ a l i g n m e n t ( s ) i f s t m t s _ c a n _ p a c k ( B , P , s , s ′ , a l i g n ) t h e n P ← P ∪ { ⟨ s , s ′ ⟩ } r e t u r n
今天的文章矢量化编码_安卓应用编译优化的三种方式分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/88125.html