Visual Studio 2010 - 2015 不使用 ymm* 寄存器进行 AVX 优化

Visual Studio 2010 - 2015 does not use ymm* registers for AVX optimization

我的笔记本电脑 CPU 仅支持 AVX(高级矢量扩展)但不支持 AVX2。对于 AVX,128 位 xmm* 寄存器已经扩展到 256 位 ymm* 寄存器以进行浮点运算。然而,我已经测试过 Visual Studio 的所有版本(从 2010 年到 2015 年)在 /arch:AVX 优化下不使用 ymm* 寄存器,尽管它们在 /arch:AVX2 优化下这样做。

下面显示了一个简单的 for 循环的反汇编。该程序在发布版本中使用 /arch:AVX 编译,并启用所有优化选项。

    float a[10000], b[10000], c[10000];
    for (int x = 0; x < 10000; x++)
1000988F  xor         eax,eax  
10009891  mov         dword ptr [ebp-9C8Ch],ecx  
        c[x] = (a[x] + b[x])*b[x];
10009897  vmovups     xmm1,xmmword ptr c[eax]  
100098A0  vaddps      xmm0,xmm1,xmmword ptr c[eax]  
100098A9  vmulps      xmm0,xmm0,xmm1  
100098AD  vmovups     xmmword ptr c[eax],xmm0  
100098B6  vmovups     xmm1,xmmword ptr [ebp+eax-9C78h]  
100098BF  vaddps      xmm0,xmm1,xmmword ptr [ebp+eax-9C78h]  
100098C8  vmulps      xmm0,xmm0,xmm1  
100098CC  vmovups     xmmword ptr [ebp+eax-9C78h],xmm0  
100098D5  add         eax,20h  
100098D8  cmp         eax,9C40h  
100098DD  jl          ComputeTempo+67h (10009897h)  


    const int   winpts = (int)(window_size*sr+0.5);
100098DF  vxorps      xmm1,xmm1,xmm1  
100098E3  vcvtsi2ss   xmm1,xmm1,ecx  

我还测试了我可以使用 ymm* 寄存器来进一步加速我的程序而不会崩溃。我使用 IMM 内在函数来做到这一点,例如_mm256_mul_ps.

微软的编译器开发者能给个解释吗?或者这可能是 Visual Studio 给出的代码比 gcc/g++ 编译器慢的原因之一?

=============已编辑==============

原来是32位机器上的运行32位OS和32位机器上的运行32位OS有一些区别64 位机器。在后一种情况下,某些 OS 可能不知道 ymm* 寄存器的存在,因此在上下文切换期间无法正确保留上半部分寄存器。因此,如果在 64 位机器上的 32 位 OS 上使用 ymm* 寄存器,如果发生上下文切换,如果另一个程序也在使用 ymm* 寄存器,则上半部分寄存器可能会悄无声息地损坏。 Visual Studio 在这种情况下有点保守。

我制作了一个文本文件vec.cpp

//vec.cpp
void foo(float *a, float *b, float *c) {
    for (int i = 0; i < 10000; i++) c[i] = (a[i] + b[i])*b[i];
}

进入启用 Visual Studio 2015 x86 x64 的命令行并执行

cl /c /O2 /arch:AVX /FA vec.cpp

查看文件 vec.asm 我明白了

$LL4@foo:
    vmovups ymm0, YMMWORD PTR [rax-32]
    lea rax, QWORD PTR [rax+64]
    vmovups ymm2, ymm0
    vaddps  ymm0, ymm0, YMMWORD PTR [rcx+rax-96]
    vmulps  ymm2, ymm0, ymm2
    vmovups YMMWORD PTR [r8+rax-96], ymm2
    vmovups ymm0, YMMWORD PTR [rax-64]
    vmovups ymm2, ymm0
    vaddps  ymm0, ymm0, YMMWORD PTR [rcx+rax-64]
    vmulps  ymm2, ymm0, ymm2
    vmovups YMMWORD PTR [r8+rax-64], ymm2
    sub rdx, 1
    jne SHORT $LL4@foo
    vzeroupper

问题是您在 32 位模式下编译。使用上面相同的函数但在 32 位模式下编译我得到

$LL4@foo:
    lea eax, DWORD PTR [ebx+esi]
    lea ecx, DWORD PTR [ecx+32]
    lea esi, DWORD PTR [esi+32]
    vmovups xmm1, XMMWORD PTR [esi-48]
    vaddps  xmm0, xmm1, XMMWORD PTR [ecx-32]
    vmulps  xmm0, xmm0, xmm1
    vmovups XMMWORD PTR [edx+ecx-32], xmm0
    vmovups xmm1, XMMWORD PTR [esi-32]
    vaddps  xmm0, xmm1, XMMWORD PTR [eax]
    vmulps  xmm0, xmm0, xmm1
    vmovups XMMWORD PTR [eax+edx], xmm0
    sub edi, 1
    jne SHORT $LL4@foo

是的,这是 32-bit/64-bit 的问题。在 x64 模式下编译没有问题。但是,出于某种原因,我的程序必须在 32 位模式下编译,因为它是某种插件,仅支持 32 位。尽管如此,仍然矛盾的是,即使在 32 位模式下,设置 /arch:AVX2 也会允许编译器访问 ymm* 寄存器。

根据英特尔规范,http://www.felixcloutier.com/x86/ADDPS.html, 它说 "in 64-bit mode, using a REX prefix in the form of REX.R permits this instruction to access additional registers (XMM8-XMM15)." 同样在 http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html 中,它指出 32 位程序可以在 32 位和 64 位操作系统中访问 ymm* 寄存器。唯一的限制是在 32 位模式下,您无法访问 xmm8-xmm15 或 ymm8-ymm15,因为指令更短。这就是为什么我能够手动使用内部函数访问 ymm* 寄存器而不会导致非法指令崩溃的原因。

所以综上所述,除非存在一些只支持AVX不支持AVX2的CPU,否则在32位模式下访问ymm*寄存器会遇到一些问题,(已经被证明不是这样), above-mentioned 不需要限制。我仍然希望 Visual C++ 编译器可以改进,使这个优化选项可用,因为许多计算机只支持 AVX 而不是 AVX2,并且使用 ymm* 寄存器可以使浮点运算的性能提高一倍。