【问题标题】:Visual Studio 2010 - 2015 does not use ymm* registers for AVX optimizationVisual Studio 2010 - 2015 不使用 ymm* 寄存器进行 AVX 优化
【发布时间】:2016-04-19 09:18:09
【问题描述】:

我的笔记本电脑 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 位操作系统和在 64 位机器上运行 32 位操作系统之间存在一些差异。在后一种情况下,某些操作系统可能不知道 ymm* 寄存器的存在,因此在上下文切换期间不会正确保留上半部分寄存器。因此,如果在 64 位机器上的 32 位操作系统上使用 ymm* 寄存器,如果发生上下文切换,如果另一个程序也在使用 ymm* 寄存器,则上半部分寄存器可能会静默损坏。在这种情况下,Visual Studio 有点保守。

【问题讨论】:

  • 您是否尝试过编译器知道数组是 32B 对齐的循环?我注意到它使用了未对齐的加载/存储指令。此外,AMD CPU 使用 256b AVX 代码比使用 128b AVX 代码做得更差,尤其是。 Piledriver 对 256b 商店有很大的问题。因此,如果您没有告诉编译器针对特定的微架构进行优化,那么 128b 向量“更安全”。
  • 我在 MSVC 2015 中使用 cl /c /O2 /arch:AVX 测试了 void void foo(float *a, float *b, float *c) { for(int i=0; i&lt;10000; i++) c[i] = (a[i]+b[i])*b[i]; },它使用了 ymm。不知道你遇到了什么问题。
  • @PeterCordes,在 AVX 中使用未对齐的加载指令不会受到任何惩罚。内存不是 32B 对齐的,但 Clang 和 MSVC 都不会为此调整(但 GCC 和 ICC 会)。
  • 顺便说一句,__restrict (void foo(float * __restrict a, float * __restrict b, float * __restrict c) for (int i = 0; i &lt; 10000; i++) c[i] = (a[i] + b[i])*b[i]; }在这种情况下对 MSVC 没有影响,但对 Clang 和 GCC 有很大影响。
  • 我发现了问题所在。您正在 32 位模式下编译。 Visual Studio 默认为 32 位模式。

标签: c++ visual-studio optimization visual-studio-2015 avx


【解决方案1】:

我做了一个文本文件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 位代码时不使用 YMM 寄存器呢? YMM0-YMM7 当然可以在 x86-32 模式下使用。
  • @CodyGray,我不知道。另一个问题:为什么即使操作系统是 64 位,Visual Studio 也默认为 32 位模式?创建新项目后必须去配置管理器并告诉它使用x64,这很烦人。我对 Visual Studio 的 GUI 编码不太方便,所以我现在主要使用命令行 (this helped)。我猜 Visual Studio 的调试器不错,但我还是用 printf 和程序集来调试。
  • 您的第二个问题很容易回答——许多应用程序仍然编译为 32 位以获得最大的兼容性。不是每个人都有 64 位处理器和/或运行 64 位操作系统。 Windows 本身仍有不支持 64 位应用程序的 32 位版本。
  • @PeterCordes,我没有在问题中读到任何关于 32 位目标的内容。我认为 OP 最好的教训是从现在开始使用 64 位模式。但我同意如果我能用 32 位模式解释这个问题,我的答案会更好。你做了一些很好的猜测,但它们只是猜测。你能解释一下为什么MSVC会像OP所说的那样在32位模式下将xmm用于AVX而将ymm用于AVX2(我没有对此进行测试,但我假设OP对两者都使用32位模式)?也许我们不得不问 MSVC 他们为什么这样做?
  • OP 询问为什么 MSVC 在这种情况下不使用 ymm 寄存器。 OP 没有发现 32 位与 64 位是一个因素,所以很好的发现。但是,对于 为什么 MSVC 会以这种方式设计,这并不是一个令人满意的解释。我正在查看OP的问题是“为什么MSVC使用这些选项不使用YMM Regs?”,当其中一个选项是32位模式时。它确实在 64 位模式下使用 ymm regs 的事实是更奇怪,而不是更少。此外,我不知道为什么 AVX2 会有所作为,除了在 Haswell 等较新的 CPU 上使用 256b 操作会带来更多性能优势。
【解决方案2】:

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

来自英特尔规范,http://www.felixcloutier.com/x86/ADDPS.html, 它说“在 64 位模式下,使用 REX.R 形式的 REX 前缀允许该指令访问其他寄存器 (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* 寄存器时会遇到一些问题(已经证明并非如此),以上-提到的限制是不必要的。而且我仍然希望可以改进 Visual C++ 编译器以使该优化选项可用,因为许多计算机仅支持 AVX 而不支持 AVX2,并且使用 ymm* 寄存器可以使浮点运算的性能提高一倍。

【讨论】:

  • 当有人回答你的问题时,正常的程序是如果你认为它回答了你的问题,就接受它。您似乎没有意识到这一点,因为您也没有为 @PaulR 的回答 here 这样做。
  • 对不起,我对stackoverflow很陌生,我刚刚发现排名上升/排名下降下方的大勾号也是可点击的。打勾!-:)
  • 没问题。我并没有真正完全回答你的问题,所以如果你不接受答案也没关系。您还可以更改已接受的答案,以防以后有人更满意地回答您的问题。
猜你喜欢
  • 2020-02-25
  • 1970-01-01
  • 2014-02-26
  • 1970-01-01
  • 2020-01-21
  • 1970-01-01
  • 1970-01-01
  • 2017-06-03
  • 2013-01-11
相关资源
最近更新 更多