我已经做了相当多的线程级和进程级并行化,现在我尝试使用Intel C++编译器进行指令级并行处理,这是一个相当大的挑战。
在对循环进行自动矢量化和对编译器日志进行分析时,我发现了一些“循环最大行程计数的估计”,但我不太清楚。
示例:
double a[100],x[100],y[100]
...
for (i=0; i< 100; i++) {
a[i] = x[i] + y[i];
}
这个循环输出12次行程的最大行程数的估计值。我在某个地方读到,矢量化过程每次行程总共可以处理8个元素,只要每个周期的处理成本小于6u操作,据我所知,这个示例循环的成本为1存储、2读和1算术操作。
所以在理论上,我的旅行次数应该是100/8 = 12.5次,因此,13次。
这是编译器做的集合吗?或者,在后台是否还有其他优化,允许流程少于13次旅行?
还有一个问题,我的每个周期的6u运算正确吗?是否有不适用的情况?
提前感谢
发布于 2015-10-26 11:11:55
与其纠缠于Intel是如何实现每个循环的,不如让我们尝试回答您关于指令级并行性的问题。
您的操作受到读和写的限制,因此在确定周期数时可以忽略算法。以下是Core2通过布罗德韦尔所能做的事情:
Core2: two 16 byte reads one 16 byte write per 2 clock cycles -> 24 bytes/clock cycle
SB/IB: two 32 byte reads and one 32 byte write per 2 clock cycles -> 48 bytes/clock cycle
HSW/BDW: two 32 byte reads and one 32 byte write per clock cycle -> 96 bytes/clock cycle
正在读取和写入的字节总数为sizeof(double)*100*3=2400
。因此,快速估计所需时间是
Core2: 2400/24 = 100 clock cycles
SB/IB: 2400/48 = 50 clock cycles
HSW/BDW: 2400/96 = 25 clock cycles
现在的问题是如何在全带宽范围内实现这一点。
对于Core2,通过常春藤桥,其中的一个负荷可以与一个额外的成本,一个微型熔断微操作。另一个负载需要一个微操作,负载需要一个微操作。如果您想在每次迭代时都这样做,那么您可以使用need to decrease a pointer and do a conditional jump as well。由于Nehalem,这些可以宏融合,因此每次迭代的微融合/宏融合操作的总数是:
Core2 Nehalem through Broadwell
vector add + load 1 1
vector load 1 1
vector store 1 1
scalar add 1 ½
conditional jump 1 ½
--------------------------------------------
total 5 4
对于通过常春藤桥的Core2来说,要么两个负载都需要相同的端口,要么负载和存储需要相同的端口。这需要两个时钟周期。对于Haswell/Broadwell来说,每一个时钟周期都有可能发生这种情况。然而,due to limitations on port 7 only statically allocated arrays can achieve this使用绝对-32位地址+偏移地址(which incidentally is not possible on OSX).因此,对于Haswell/Broadwell,如果没有静态分配数组,则要么必须展开循环来执行每个时钟周期的操作,要么每次迭代需要1.5个时钟周期。以下是每个处理器每次迭代的时钟周期摘要:
Core2: 5 fused micro-ops/every two clock cycles
SB/IB: 4 fused micro-ops/every two clock cycles
HSW/BDW: 4 fused mirco-ops/every clock cycle for statically allocated array
HSW/BDW: 4 fused mirco-ops/every 1.5 clock cycles for non-statically allocated arrays
如果使用堆栈分配数组,则可能可以安全地读取缓冲区的末尾。否则,您应该将数组放置到SIMD宽度。然后循环的迭代次数是:
SSE2: (100+1)/2 = 51
AVX: (100+3)/4 = 26
在我的经验中,Intel编译器展开了两次,这样迭代次数就减少了一半。展开两次的迭代次数是
SSE2: (100+3)/4 = 26
AVX: (100+7)/8 = 13
最后,就时钟周期而言
Core2: 51*2 = 102 clock cycles
SB/IB: 26*2 = 51 clock cycles
HSW/BDW: 26*1.5 = 39 clock cycles for non-statically allocated arrays no-unroll
HSW/BDW: 26*1 = 26 clock cycles for statically allocated arrays no-unroll
HSW/BDW: 26*1 = 26 clock cycles with full unrolling
发布于 2015-10-24 02:51:34
6-uops听起来像是一个正确的估计,如果不展开。英特尔编译器通常在展开自动矢量化循环方面做得很好。在这种特殊情况下,即使是完全展开也可能是有意义的。
不确定如何一次获得8个元素,因为即使使用AVX,也只能在一个256位ymm
寄存器中获得4个双精度值。
关于行程的统计。即使一次可以执行8个元素,它也将是12个,而不是13个,因为最后几个元素(一次不能处理8个)将使用标量代码完成。
因此,从编译器的角度来看,它将如下所示:
int i=0;
for(; i<(100 & ~7); i+=8) // 12 iterations
// Do vector code
for(;i<100; ++i)
// Process loop remainder using scalar code
https://stackoverflow.com/questions/33303141
复制相似问题