为什么自动矢量化器无法找到“可矢量化类型信息”?

问题描述 投票:0回答:1

我正在尝试对我的一些代码进行矢量化,但我不断遇到

info C5002: loop not vectorized due to reason '1305'
。根据本页

// 当编译器无法识别此循环的正确可向量化类型信息时,将发出代码 1305。

(我正在使用 Visual Studio Community 2022)

我决定尝试一些非功能代码,以更好地理解为什么会发生这种情况,但这个错误似乎出现在应该明显键入且易于矢量化的代码中。这是我的代码:

int vecTest() {
    int v0[128] alignas(16);
    int v1[128] alignas(16);
    int v2[128] alignas(16);
    int sum = 0;

    for (int i = 0; i < 128; i++) {
        v0[i] = i-1;
        v1[i] = i*2;
    }
    
    for (int i = 0; i < 128; i++) {
        v2[i] = v0[i] + v2[i];
    }

    #ifdef CASE_TWO
    int* pv0 = &v0[0];
    int* pv1 = &v1[0];
    int* pv2 = &v2[0];

    for (int i = 0; i < 128; i++) {
        pv2[i] = pv0[i] + pv2[i];
    }
    #endif

    sum += v2[0];
    return sum;
}

int main(int argc, char* argv[])
{
    int sum = vecTest();
    sum = sum + 1;
}

如果 CASE_TWO 不存在,第一个(初始化)循环将进行矢量化,但第二个循环将返回代码 1305。但是,添加 CASE_TWO 的内容会导致所有三个循环正确矢量化!此外,包含 CASE_TWO 代码并排除第二个循环会导致 CASE_TWO 返回 1305。

在我看来,这些循环都不应该在矢量化方面遇到困难,并且它们不应该相互影响。我错过了什么?

代码 1305 和“正确的可向量化类型信息”的实际含义是什么?编译器实际上是否按照文档建议的方式运行?

我使用默认编译器设置,除了

/O2
/Qvec-report:2

c++ visual-c++ vectorization compiler-optimization auto-vectorization
1个回答
0
投票

如果您查看 asm(在 Godbolt 上),我们可以看到 MSVC 将两个循环折叠在一起,因此没有单独的 init 循环。它只是动态计算

v0[i]
,添加到未初始化的
v2[i]
(向量从它分配但从未写入的空间加载和存储)。

它报告第一个循环已矢量化,第二个循环未矢量化,但实际上它将它们融合到一个 asm 循环中。 这些循环中的工作全部被矢量化,因此这可以说是其报告中的一个错误。 (除了优化掉那些从未读取过的未使用的

v1[i] = i*2;
。)

// x64 MSVC 19.37 -O2 
v2$ = 0
int vecTest(void) PROC                                    ; vecTest, COMDAT
... function prologue
        movdqa  xmm2, XMMWORD PTR __xmm@00000003000000020000000100000000  ; _mm_setr_epi32(0,1,2,3)
        xor     eax, eax     ; i = 0
        movdqa  xmm3, XMMWORD PTR __xmm@00000001000000010000000100000001  ; _mm_set1_epi32(1)
        mov     ecx, eax     ; byte_offset = 0, could have just used a scaled-index addr mode
        npad    3
$LL4@vecTest:
        movdqu  xmm0, XMMWORD PTR v2$[rsp+rcx]  ; load uninitialized v2[i]
        lea     rcx, QWORD PTR [rcx+16]         ; byte_offset += 16
        movd    xmm1, eax
        add     eax, 4
        pshufd  xmm1, xmm1, 0     ; _mm_set1_epi32(i) = movd+pshufd
        paddd   xmm1, xmm2        ; add [3,2,1,0] to get [i+3, i+2, i+1, i+0]
        psubd   xmm1, xmm3        ; v0[i] = i - 1  for i+0..3
        paddd   xmm1, xmm0        ; v2[i] += v0[i]
        movdqu  XMMWORD PTR v2$[rsp+rcx-16], xmm1
        cmp     eax, 128                      ; 00000080H
        jl      SHORT $LL4@vecTest

        mov     eax, DWORD PTR v2$[rsp]   # retval = v2[0]
... epilogue

相比之下,GCC 并不是那么聪明,它确实为 v2 和 v1 分配了空间(

sub rsp, 928
,加上 128 字节的红色区域,刚刚超过 1024 = 2x
128 * sizeof(int)
)。 MSVC 为
v2
分配了空间,而不是
v0
sub rsp, 536
刚刚超过 128 * sizeof(int) = 512)。两个编译器都没有为未使用的
v1
分配空间,我不知道为什么这会让你的示例变得混乱。

Clang 优化掉了所有内容(包括返回值,因为读取未初始化的

v2[]
在 C++ 中是 UB,或者至少是不确定的,因此它可以在 EAX 中留下任何它想要的垃圾作为返回值)。使用
alignas(16) int v2[128] = {};
,clang 仍然优化掉数组,只是返回
-1
https://godbolt.org/z/E9v1evE94 - clang 需要标准
alignas(128) int v0[];
语法,不允许
alignas
位于声明之后。 GCC 和 MSVC 允许这样做。

随着

v2
的 init,MSVC 确实会为此调用
memset
,但仍然会进行相同的单个循环,即时实现
v0[i]
以添加到
v2[i]
中。

© www.soinside.com 2019 - 2024. All rights reserved.