我已经完成了相当多的线程级和进程级并行,现在我试图进入与英特尔C ++编译器的指令级并行,这是一个非常大的挑战。
在进行循环的自动矢量化和分析编译器日志时,我发现了一些"估计循环的最大行程数"我无法理解。
示例:
double a[100],x[100],y[100]
...
for (i=0; i< 100; i++) {
a[i] = x[i] + y[i];
}
此循环输出12次行程的最大行程计数估计。 我在某处读到,矢量化过程每次可以处理总共8个元素,因为每个周期的过程成本低于6个u操作,从我可以看出,这个示例循环的成本为1存储,2次读取和1次算术运算。
所以理论上,我的行程计数应该是100/8 = 12.5次旅行,因此,13次旅行。
这是编译器的汇总吗?或者是否在后台进行任何其他优化,允许该过程少于13次旅行?
还有一个问题,我的每个周期的6次操作假设是否正确?是否有任何不适用的情况?
提前致谢
答案 0 :(得分:3)
与其深入了解英特尔如何实现每个循环,让我们尝试回答有关指令级并行的问题。
您的操作受读取和写入限制,因此您可以在确定循环次数时忽略算法。以下是Core2到Broadwell可以做的事情:
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到Ivy Bridge,其中一个负载可以与一个微融合微操作成本相加。另一个负载需要一个微操作,负载一个微操作。如果你想在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到Ivy Bridge,两个负载都需要相同的端口,或者加载和存储需要相同的端口。这需要两个时钟周期。对于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
根据我的经验,英特尔编译器会展开两次,以便迭代次数减半。展开两次的迭代次数是
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
答案 1 :(得分:0)
不确定如何一次获得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