12

有没有人看到下面的循环代码有什么明显的东西,我没有看到为什么 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]);
}

每个周围的括号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]);
}
4

2 回答 2

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];
    }
}

编译器输出:

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. 第 3 个循环与第 2 个循环相同。然而编译器给出了不同的原因1203

    循环体包括对数组的非连续访问

    好吧...为什么不同的原因?我不知道。但这一次,理由是正确的。

  3. 第 4 个循环给出1101

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

    所以 VC++ 不够聪明,无法发出 SSE4.1pmovzxbd指令。

    这是一个非常小众的案例,我没想到任何现代编译器都能够做到这一点。如果可以,您需要指定 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);
}

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

于 2012-12-23T01:12:13.567 回答
2

从 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 并行化器和矢量化器消息

于 2012-12-23T01:11:57.650 回答