【问题标题】:Are older SIMD-versions available when using newer ones?使用较新的 SIMD 版本时是否可以使用较旧的 SIMD 版本?
【发布时间】:2015-08-02 01:14:30
【问题描述】:

当我可以使用 SSE3 或 AVX 时,是否可以使用旧的 SSE 版本作为 SSE2 或 MMX -
还是我还需要单独检查?

【问题讨论】:

  • 作为一般规则,您应该在使用之前检查功能。但是,决定您是否拥有 SSE3 或 AVX 的 CPUID 指令也将决定您是否拥有 SSE2 或 MMX。如果您只是将这些 CPUID 指令的输出保存到适当的变量中,则可以在您想要使用特定指令时执行单位测试。
  • 这之前在 SO 上出现过,但我现在似乎找不到重复的...
  • 英特尔 CPU 始终向后兼容。因此,如果它支持指令集,那么它将支持所有旧版本

标签: c++ c sse simd avx


【解决方案1】:

一般来说,这些都是附加的,但请记住,多年来英特尔和 AMD 对这些的支持存在差异。

如果您有 AVX,那么您也可以假设 SSE、SSE2、SSE3、SSSE3、SSE4.1 和 SSE 4.2。请记住,要使用 AVX,您还需要验证 OSXSAVE CPUID 位是否已设置,以确保您使用的操作系统实际上也支持保存 AVX 寄存器。

您仍应明确检查您在代码中使用的所有 CPUID 支持的稳健性(例如同时检查 AVX、OSXSAVE、SSE4、SSE3、SSSE3 以保护您的 AVX 代码路径)。

#include <intrin.h>

inline bool IsAVXSupported()
{
#if defined(_M_IX86 ) || defined(_M_X64)
   int CPUInfo[4] = {-1};
   __cpuid( CPUInfo, 0 );

   if ( CPUInfo[0] < 1  )
       return false;

    __cpuid(CPUInfo, 1 );

    int ecx = 0x10000000 // AVX
              | 0x8000000 // OSXSAVE
              | 0x100000 // SSE 4.2
              | 0x80000 // SSE 4.1
              | 0x200 // SSSE3
              | 0x1; // SSE3

    if ( ( CPUInfo[2] & ecx ) != ecx )
        return false;

    return true;
#else
    return false;
#endif
}

所有支持 x64 本机的处理器都需要 SSE 和 SSE2,因此它们是所有代码的良好基线假设。 Windows 8.0、Windows 8.1 和 Windows 10 明确要求 SSE 和 SSE2 支持,即使对于 x86 架构也是如此,因此这些指令集非常普遍。换句话说,如果您未能通过 SSE 或 SSE2 检查,只需退出应用并出现致命错误即可。

#include <windows.h>

inline bool IsSSESupported()
{
#if defined(_M_IX86 ) || defined(_M_X64)
   return ( IsProcessorFeaturePresent( PF_XMMI_INSTRUCTIONS_AVAILABLE ) != 0 && IsProcessorFeaturePresent( PF_XMMI64_INSTRUCTIONS_AVAILABLE ) != 0 );
#else
    return false;
#endif
}

-或-

#include <intrin.h>

inline bool IsSSESupported()
{
#if defined(_M_IX86 ) || defined(_M_X64)
   int CPUInfo[4] = {-1};
   __cpuid( CPUInfo, 0 );

   if ( CPUInfo[0] < 1  )
       return false;

    __cpuid(CPUInfo, 1 );

    int edx = 0x4000000 // SSE2
              | 0x2000000; // SSE

    if ( ( CPUInfo[3] & edx ) != edx )
        return false;

    return true;
#else
    return false;
#endif
}

另外,请记住,MMX、x87 FPU 和 AMD 3DNow!* 都是 x64 本机的已弃用指令集,因此您不应再在较新的代码中积极使用它们。一个好的经验法则是避免使用任何返回 __m64 或采用 __m64 数据类型的内部函数。

您可能想查看此DirectXMath blog series,其中包含有关其中许多指令集和相关处理器支持要求的说明。

注意 (*) - 所有 AMD 3DNow!除了 PREFETCHPREFETCHW 被继承,其他指令已被弃用。第一代 Intel64 处理器缺乏对这些指令的支持,但后来被添加,因为它们被认为是核心 X64 指令集的一部分。 Windows 8.1 和 Windows 10 x64 尤其需要 PREFETCHW,尽管测试有点奇怪。 Broadwell 之前的大多数 Intel CPU 实际上并不通过 CPUID 报告对PREFETCHW 的支持,但它们将操作码视为无操作,而不是抛出“非法指令”异常。因此,这里的测试是(a)它是否受 CPUID 支持,以及(b)如果不支持,PREFETCHW 至少不会抛出异常。

这里有一些 Visual Studio 测试代码,演示了 PREFETCHW 测试以及 x86 和 x64 平台的许多其他 CPUID 位。

#include <intrin.h>
#include <stdio.h>
#include <windows.h>
#include <excpt.h>

void main()
{
   unsigned int x = _mm_getcsr();
   printf("%08X\n", x );

   bool prefetchw = false;

   // See http://msdn.microsoft.com/en-us/library/hskdteyh.aspx
   int CPUInfo[4] = {-1};
   __cpuid( CPUInfo, 0 );

   if ( CPUInfo[0] > 0 )
   {
       __cpuid(CPUInfo, 1 );

       // EAX
       {
           int stepping = (CPUInfo[0] & 0xf);
           int basemodel = (CPUInfo[0] >> 4) & 0xf;
           int basefamily = (CPUInfo[0] >> 8) & 0xf;
           int xmodel = (CPUInfo[0] >> 16) & 0xf;
           int xfamily = (CPUInfo[0] >> 20) & 0xff;

           int family = basefamily + xfamily;
           int model = (xmodel << 4) | basemodel;

           printf("Family %02X, Model %02X, Stepping %u\n", family, model, stepping );
       }

       // ECX
       if ( CPUInfo[2] & 0x20000000 ) // bit 29
          printf("F16C\n");

       if ( CPUInfo[2] & 0x10000000 ) // bit 28
          printf("AVX\n");

       if ( CPUInfo[2] & 0x8000000 ) // bit 27
          printf("OSXSAVE\n");

       if ( CPUInfo[2] & 0x400000 ) // bit 22
          printf("MOVBE\n");

       if ( CPUInfo[2] & 0x100000 ) // bit 20
          printf("SSE4.2\n");

       if ( CPUInfo[2] & 0x80000 ) // bit 19
          printf("SSE4.1\n");

       if ( CPUInfo[2] & 0x2000 ) // bit 13
          printf("CMPXCHANG16B\n");

       if ( CPUInfo[2] & 0x1000 ) // bit 12
          printf("FMA3\n");

       if ( CPUInfo[2] & 0x200 ) // bit 9
          printf("SSSE3\n");

       if ( CPUInfo[2] & 0x1 ) // bit 0
          printf("SSE3\n");

       // EDX
       if ( CPUInfo[3] & 0x4000000 ) // bit 26
           printf("SSE2\n");

       if ( CPUInfo[3] & 0x2000000 ) // bit 25
           printf("SSE\n");

       if ( CPUInfo[3] & 0x800000 ) // bit 23
           printf("MMX\n");
   }
   else
       printf("CPU doesn't support Feature Identifiers\n");

   if ( CPUInfo[0] >= 7 )
   {
       __cpuidex(CPUInfo, 7, 0);

       // EBX
       if ( CPUInfo[1] & 0x100 ) // bit 8
         printf("BMI2\n");

       if ( CPUInfo[1] & 0x20 ) // bit 5
         printf("AVX2\n");

       if ( CPUInfo[1] & 0x8 ) // bit 3
         printf("BMI\n");
   }
   else
       printf("CPU doesn't support Structured Extended Feature Flags\n");

   // Extended features
   __cpuid( CPUInfo, 0x80000000 );

   if ( CPUInfo[0] > 0x80000000 )
   {
       __cpuid(CPUInfo, 0x80000001 );

       // ECX
       if ( CPUInfo[2] & 0x10000 ) // bit 16
           printf("FMA4\n");

       if ( CPUInfo[2] & 0x800 ) // bit 11
           printf("XOP\n");

       if ( CPUInfo[2] & 0x100 ) // bit 8
       {
           printf("PREFETCHW\n");
           prefetchw = true;
       }

       if ( CPUInfo[2] & 0x80 ) // bit 7
           printf("Misalign SSE\n");

       if ( CPUInfo[2] & 0x40 ) // bit 6
           printf("SSE4A\n");

       if ( CPUInfo[2] & 0x1 ) // bit 0
           printf("LAHF/SAHF\n");

       // EDX
       if ( CPUInfo[3] & 0x80000000 ) // bit 31
           printf("3DNow!\n");

       if ( CPUInfo[3] & 0x40000000 ) // bit 30
           printf("3DNowExt!\n");

       if ( CPUInfo[3] & 0x20000000 ) // bit 29
           printf("x64\n");

       if ( CPUInfo[3] & 0x100000 ) // bit 20
           printf("NX\n");
   }
   else
       printf("CPU doesn't support Extended Feature Identifiers\n");

   if ( !prefetchw )
   {
       bool illegal = false;

       __try
       {
           static const unsigned int s_data = 0xabcd0123;

           _m_prefetchw(&s_data);
       }
       __except (EXCEPTION_EXECUTE_HANDLER)
       {
           illegal = true;
       }

       if (illegal)
       {
           printf("PREFETCHW is an invalid instruction on this processor\n");
       }
   }
}

更新:当然,根本的挑战是如何处理不支持 AVX 的系统?虽然指令集很有用,但拥有支持 AVX 的处理器的最大好处是能够使用 /arch:AVX 构建开关,它可以在全球范围内使用 VEX prefix 以获得更好的 SSE/SSE2 代码生成。唯一的问题是生成的代码 DLL/EXE 与缺乏 AVX 支持的系统不兼容。

因此,对于 Windows,理想情况下,您应该为非 AVX 系统构建一个 EXE(假设仅 SSE/SSE2,因此使用 /arch:SSE2 而不是 x86 代码;此设置对于 x64 代码是隐含的),另一种 EXE 是针对 AVX 进行了优化(使用 /arch:AVX),然后使用 CPU 检测来确定给定系统使用哪个 EXE。

幸运的是,有了 Xbox One,我们总是可以使用 /arch::AVX 构建,因为它是一个固定平台...

更新 2: 对于 clang/LLVM,您应该对 CPUID 使用轻微的 dikyfferent intriniscs:

if defined(__clang__) || defined(__GNUC__)
    __cpuid(1, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]);
#else
    __cpuid(CPUInfo, 1);
#endif
if defined(__clang__) || defined(__GNUC__)
    __cpuid_count(7, 0, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]);
#else
    __cpuidex(CPUInfo, 7, 0);
#endif

【讨论】:

  • 我认为为不同的指令集制作单独的可执行文件不是一个好主意。在我看来,使用CPU dispatcher 的可执行文件更理想。
  • /arch:AVX 开关适用于整个模块,而不仅仅是一个函数,但理论上是的,您可以为每个函数创建不同的 cpp 文件并使用不同的构建设置进行编译。
  • 他的主要问题是使用虚函数(或函数指针)会增加开销,因此这实际上取决于您在“调度”函数中所做的工作量。最初的 D3DXMath 库使用了这种设计。针对特定 CPU 进行优化并在运行时检测它们同样容易,但结果会在小型操作中损失很多性能。这就是 Windows 版 DirectXMath 仅使用 SSE 和 SSE2 的原因,因此它可以积极内联并且没有“保护路径”或“虚拟函数”可供使用。
  • 我同意您可能不希望函数指针用于通常使用静态内联的函数。在这种情况下,我不确定如何使用调度程序。在我上面的链接中,有人提到使用带有memcpy 的自我修改代码。这可以解决函数指针问题并仍然分派。我还没有这样做。
  • 任何涉及自修改代码的解决方案都是有问题的,因为这基本上与恶意软件没有区别。您必须创建一个非 NX 内存区域并跳转到它。
【解决方案2】:

作为一般规则 - 除非必须,否则不要混合使用不同代的 SSE / AVX。如果这样做,请确保使用 vzeroupper 或类似的状态清除指令,否则您可能会拖动部分值并在不知不觉中创建错误的依赖关系,因为大多数寄存器在模式之间共享 即使在清除时,模式之间的切换也可能会导致惩罚,具体取决于确切的微架构。

进一步阅读 - https://software.intel.com/sites/default/files/m/d/4/1/d/8/11MC12_Avoiding_2BAVX-SSE_2BTransition_2BPenalties_2Brh_2Bfinal.pdf

【讨论】:

  • 公平地说,这种混合问题仅在您使用代码将非 VEX 代码与 VEX 混合时发生(即,将 AVX 或 AVX2 与之前的 2 个操作数 SSE 指令混合)。除此之外,在几代人之间混合是很好和必要的——这是必要的,因为每个扩展本身并不是一个完全有用的美国,而是建立在最后一个之上。
  • @BeeOnRope,可以混用,但你需要保护自己免受我所说的问题的影响。见 - stackoverflow.com/questions/7839925/…
  • 没错,但即使是这个问题也有点不清楚,因为它没有指出几乎所有有趣的 SSE 指令都有 VEX 编码版本 - 包括 128 位变体。许多人仍然松散地称这些“SSE”指令。例如,您会听到 pshufb 是一个 SSE3 指令。事实上,像 phsufb xmm0, xmm1 这样的调用会创建一个非 VEX 编码,这会导致您提到的性能问题。只需将其更改为行为相同的 pshufb xmm0, xmm0, xmm1,即可将其更改为 VEX 编码并避免此问题。
【解决方案3】:

请参阅 Chuck 的回答以获取有关您应该做什么的好建议。如果您好奇,请参阅此答案以获得所提问题的字面答案。


AVX 支持绝对保证支持所有英特尔 SSE* 指令集,因为它包括所有这些指令集的 VEX 编码版本。正如 Chuck 指出的那样,您可以使用位掩码同时检查以前的代码,而不会使您的代码臃肿,但不要出汗。

请注意,POPCNT、TZCNT 和类似的东西不是 SSE-anything 的一部分。 POPCNT 有自己的功能位。 LZCNT 也有自己的功能位,因为 AMD 将它与 BMI1 分开引入。不过,TZCNT 只是 BMI1 的一部分。由于某些 BMI1 指令使用 VEX 编码,即使是最新一代的 Pentium/Celeron CPU(如 Skylake Pentium)也没有 BMI1。 :( 我认为英特尔只是想省略 AVX/AVX2,可能是因为他们可以将执行单元的上层通道错误的 CPU 作为 Pentium 出售,他们通过禁用解码器中的 VEX 支持来做到这一点。


英特尔 SSE 支持已在迄今为止发布的所有 CPU 中增加。 SSE4.1 意味着 SSSE3、SSE3、SSE2 和 SSE。而 SSE4.2 则暗示了上述所有内容。我不确定是否有任何官方 x86 文档排除了支持 SSE4.1 但不支持 SSSE3 的 CPU 的可能性。 (即省略 PSHUFB,这可能实施起来可能很昂贵。)但在实践中极不可能,因为这会违反许多人的假设。正如我所说,它甚至可能被官方禁止,但我没有仔细检查。


AVX 不包括 AMD SSE4a 或 AMD XOP。 AMD 扩展必须特别检查。另请注意,最新的 AMD CPU 正在放弃 XOP 支持。 (英特尔从未采用它,所以大多数人不会编写代码来利用它,所以对于 AMD 来说,这些晶体管大多被浪费了。它确实有一些不错的东西,比如 2 源字节置换,允许一个字节 LUT 两倍与 PSHUFB 一样宽,没有 AVX2 的 VPSHUFB ymm 的车道内限制。


SSE2 是 x86-64 架构的基准。您不必在 64 位版本中检查 SSE 或 SSE2 支持。我忘记了 MMX 是否也是基线。几乎可以肯定。

SSE 指令集包括一些对 MMX 寄存器进行操作的指令。 (例如,PMAXSW mm1, mm2/m64 是 SSE 的新版本。XMM 版本是 SSE2 的一部分。)即使是支持 SSE 的 32 位 CPU 也需要具有 MMX 寄存器。拥有 MMX 寄存器但只支持使用它们的 SSE 指令,而不是原始的 MMX 指令(例如movq mm0, [mem]),这将是疯狂的。但是,我还没有找到任何明确的东西来排除基于 x86 的 Deathstation 9000 的可能性,它具有 SSE 但不具有 MMX CPUID 功能位,但我没有涉足英特尔的官方 x86 手册。 (有关链接,请参阅 标签 wiki)。

无论如何都不要使用 MMX,即使您一次只能处理 64 位,它通常会更慢,在 XMM 寄存器的低半部分。最新的 CPU(如 Intel Skylake)对于某些指令的 MMX 版本的吞吐量低于 XMM 版本的吞吐量。在某些情况下,甚至更糟糕的延迟。例如,根据Agner Fog's testingPACKSSWB mm0, mm1 在 Skylake 上是 3 uop,具有 2c 延迟。 128b 和 256b XMM / YMM 版本为 1 uop,延迟为 1c。

【讨论】:

    猜你喜欢
    • 2012-07-29
    • 2019-02-18
    • 1970-01-01
    • 2019-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    相关资源
    最近更新 更多