使用较新的 SIMD 版本时是否可以使用较旧的 SIMD 版本?

Are older SIMD-versions available when using newer ones?

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

总的来说,这些都是附加的,但请记住,多年来英特尔和 AMD 对这些的支持有所不同。

如果您有 AVX,那么您也可以使用 SSE、SSE2、SSE3、SSSE3、SSE4.1 和 SSE 4.2。请记住,要使用 AVX,您还需要验证 OSXSAVE CPUID 位是否已设置,以确保您使用的 OS 实际上也支持保存 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 本机的弃用指令集,因此您不应再在较新的代码中主动使用它们。一个好的经验法则是避免使用任何 returns a __m64 或采用 __m64 数据类型的内在函数。

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

注意 (*) - 所有 AMD 3DNow!除 PREFETCHPREFETCHW 外,指令已弃用。第一代 Intel64 处理器不支持这些指令,但后来添加了它们,因为它们被认为是核心 X64 指令集的一部分。 Windows 8.1 和 Windows 10 x64 特别需要 PREFETCHW,尽管测试有点奇怪。 Broadwell 之前的大多数英特尔 CPU 实际上并未通过 CPUID 报告对 PREFETCHW 的支持,但他们将操作码视为空操作而不是抛出 'illegal instruction'例外。因此,这里的测试是 (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,因此对 x86 代码使用 /arch:SSE2;此设置是隐式的对于 x64 代码),一个针对 AVX 优化的不同 EXE(使用 /arch:AVX),然后使用 CPU 检测来确定给定系统使用哪个 EXE。

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

更新 2: 对于 clang/LLVM,您应该对 CPUID 使用轻微的复杂内在特性:

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

作为一般规则 - 除非万不得已,否则不要混用不同代的 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

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


AVX 支持绝对保证支持所有 Intel SSE* 指令集,因为它包括所有指令集的 VEX 编码版本。正如 Chuck 指出的那样,您可以使用位掩码同时检查以前的代码,而不会膨胀您的代码,但不要担心。

请注意,POPCNT、TZCNT 和类似的东西都不是 SSE 的一部分。不过,POPCNT has its own feature bit. LZCNT has its own feature bit, too, since AMD introduced it separately from BMI1. TZCNT 只是 BMI1 的一部分。由于某些 BMI1 指令使用 VEX 编码,因此即使是最新一代 Pentium/Celeron CPUs(如 Skylake Pentium)也没有 BMI1。 :( 我认为英特尔只是想省略 AVX/AVX2,可能这样他们就可以将执行单元上层故障的 CPU 作为奔腾出售,他们通过禁用解码器中的 VEX 支持来做到这一点。


英特尔 SSE 支持在目前发布的所有 CPU 中都是递增的。 SSE4.1 表示 SSSE3、SSE3、SSE2 和 SSE。 SSE4.2 暗示了上述所有内容。我不确定是否有任何官方 x86 文档排除了支持 SSE4.1 但不支持 SSSE3 的 CPU 的可能性。 (即省去 PSHUFB,这可能实施起来很昂贵。)不过,这在实践中极不可能,因为这会违反许多人的假设。说了也有可能是官方禁止的,没仔细看


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


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

SSE指令集包括一些操作MMX寄存器的指令。 (例如 PMAXSW mm1, mm2/m64 was new with SSE. The XMM version is part of SSE2.) Even a 32-bit CPU supporting SSE needs to have MMX registers. It would be madness to have MMX registers but only support the SSE instructions that use them, not the original MMX instructions (e.g. movq mm0, [mem]). However, I haven't found anything definitive that rules out the possibility of an x86-based Deathstation 9000 with SSE but not MMX CPUID feature bits, but I didn't wade into Intel's official x86 manuals. (See the 标记 wiki 链接)。

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