这里的循环依赖关系在哪里



有没有人看到任何明显的循环代码下面,我没有看到为什么这不能被VS2012的c++编译器自动矢量化?

当我使用/Qvec-report:2命令行开关时,编译器给我的是info C5002: loop not vectorized due to reason '1200'

原因1200在MSDN中记录为:

Loop包含循环携带的数据依赖,防止向量化。循环的不同迭代相互干扰其他诸如向量化循环会产生错误的答案,以及自动矢量化器不能向自己证明不存在这样的数据依赖性。

我知道(或者我很确定)没有任何循环携带的数据依赖,但我不确定是什么阻止编译器实现这一点。

这些sourcedest指针不会重叠也不会别名相同的内存,我试图通过__restrict向编译器提供该提示。

pitch总是一个正整数值,类似于4096,这取决于屏幕分辨率,因为这是一个8bpp->32bpp的渲染/转换函数,逐列操作。

byte  * __restrict source;
DWORD * __restrict dest;
int pitch;
for (int i = 0; i < count; ++i) {
    dest[(i*2*pitch)+0] = (source[(i*8)+0]);
    dest[(i*2*pitch)+1] = (source[(i*8)+1]);
    dest[(i*2*pitch)+2] = (source[(i*8)+2]);
    dest[(i*2*pitch)+3] = (source[(i*8)+3]);
    dest[((i*2+1)*pitch)+0] = (source[(i*8)+4]);
    dest[((i*2+1)*pitch)+1] = (source[(i*8)+5]);
    dest[((i*2+1)*pitch)+2] = (source[(i*8)+6]);
    dest[((i*2+1)*pitch)+3] = (source[(i*8)+7]);
}

每个source[]周围的父元素都是函数调用的残余,我在这里省略了它们,因为在最简单的形式下,如果没有函数调用,循环仍然不会自动矢量化。

编辑:

我已经将循环简化为最简单的形式:

for (int i = 0; i < 200; ++i) {
    dest[(i*2*4096)+0] = (source[(i*8)+0]);
}

这仍然产生相同的1200原因代码。

编辑(2):

这个具有局部分配和相同指针类型的最小测试用例仍然无法自动向量化。在这一点上,我简直不知所措。

const byte * __restrict source;
byte * __restrict dest;
source = (const byte * __restrict ) new byte[1600];
dest = (byte * __restrict ) new byte[1600];
for (int i = 0; i < 200; ++i) {
    dest[(i*2*4096)+0] = (source[(i*8)+0]);
}

我们可以说,有很多东西阻止了这个循环向量化…

考虑一下:

int main(){
    byte  *source = new byte[1000];
    DWORD *dest   = new DWORD[1000];
    for (int i = 0; i < 200; ++i) {
        dest[(i*2*4096)+0] = (source[(i*8)+0]);
    }
    for (int i = 0; i < 200; ++i) {
        dest[i*2*4096] = source[i*8];
    }
    for (int i = 0; i < 200; ++i) {
        dest[i*8192] = source[i*8];
    }
    for (int i = 0; i < 200; ++i) {
        dest[i] = source[i];
    }
}
编译器输出:

main.cpp(10) : info C5002: loop not vectorized due to reason '1200'
main.cpp(13) : info C5002: loop not vectorized due to reason '1200'
main.cpp(16) : info C5002: loop not vectorized due to reason '1203'
main.cpp(19) : info C5002: loop not vectorized due to reason '1101'

让我们来分析一下:

  1. 前两个循环是相同的。所以他们给出了最初的原因1200这是循环携带的依赖

  2. 第三个循环与第二个循环相同。然而编译器给出了一个不同的原因1203:

    循环体包含对数组的不连续访问

    好吧…为什么是不同的原因?我不晓得。但这一次,原因是正确的。

  3. 第四次循环得到1101:

    循环包含一个不可向化的转换操作(可能是隐式的)

    所以vc++不够聪明,不能发出SSE4.1 pmovzxbd指令。

    这是一个相当小众的情况,我不会期望任何现代编译器能够做到这一点。如果可以,你需要指定SSE4.1。


所以唯一不同寻常的是为什么初始循环报告一个循环携带的依赖项。嗯,这是一个艰难的决定…我想说的是,编译器只是没有发出正确的原因。(实际上应该是不连续访问)

言归正传,我不希望MSVC或任何编译器能够对原始循环进行矢量化。您的原始循环将访问分组为4块-这使得它足够连续以矢量化。但是期望编译器能够识别它是不太可能的。

所以如果有关系,我建议手动向量化这个循环。你需要的内在的是_mm_cvtepu8_epi32()


您的原始循环:

for (int i = 0; i < count; ++i) {
    dest[(i*2*pitch)+0] = (source[(i*8)+0]);
    dest[(i*2*pitch)+1] = (source[(i*8)+1]);
    dest[(i*2*pitch)+2] = (source[(i*8)+2]);
    dest[(i*2*pitch)+3] = (source[(i*8)+3]);
    dest[((i*2+1)*pitch)+0] = (source[(i*8)+4]);
    dest[((i*2+1)*pitch)+1] = (source[(i*8)+5]);
    dest[((i*2+1)*pitch)+2] = (source[(i*8)+6]);
    dest[((i*2+1)*pitch)+3] = (source[(i*8)+7]);
}

矢量化如下:

for (int i = 0; i < count; ++i) {
    __m128i s0 = _mm_loadl_epi64((__m128i*)(source + i*8));
    __m128i s1 = _mm_unpackhi_epi64(s0,s0);
    *(__m128i*)(dest + (i*2 + 0)*pitch) = _mm_cvtepu8_epi32(s0);
    *(__m128i*)(dest + (i*2 + 1)*pitch) = _mm_cvtepu8_epi32(s1);
}

免责声明:这是未经测试的,并且忽略对齐。

MSDN文档中报告错误1203的情况

void code_1203(int *A)
{
    // Code 1203 is emitted when non-vectorizable memory references
    // are present in the loop body. Vectorization of some non-contiguous 
    // memory access is supported - for example, the gather/scatter pattern.
    for (int i=0; i<1000; ++i)
    {
        A[i] += A[0] + 1;       // constant memory access not vectorized
        A[i] += A[i*2+2] + 2;  // non-contiguous memory access not vectorized
    }
}

可能是索引处的计算干扰了自动矢量化器。有趣的是,显示的错误代码不是1203。

MSDN并行化和矢量化消息