这里的循环依赖在哪里?

Jam*_*nne 12 c++ vectorization compiler-optimization visual-c++ visual-c++-2012

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

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

原因1200在MSDN中记录为:

循环包含阻止矢量化的循环携带数据依赖性.循环的不同迭代相互干扰,使得对循环进行矢量化将产生错误的答案,并且自动矢量化器不能向自身证明不存在这样的数据依赖性.

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

这些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]);
}
Run Code Online (Sandbox Code Playgroud)

每个周围的parens source[]都是函数调用的残余,我在这里已经省略了,因为在没有函数调用的情况下,循环仍然不会以最简单的形式自动向量化.

编辑:

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

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

这仍然产生相同的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]);
}
Run Code Online (Sandbox Code Playgroud)

Mys*_*ial 11

让我们说,除了几个阻止这个循环矢量化的东西之外......

考虑一下:

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];
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器输出:

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'
Run Code Online (Sandbox Code Playgroud)

让我们打破这个:

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

  2. 第3个循环与第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]);
}
Run Code Online (Sandbox Code Playgroud)

矢量化如下:

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);
}
Run Code Online (Sandbox Code Playgroud)

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