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

标签 c++ c sse simd avx

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

最佳答案

一般来说,这些都是附加的,但请记住,多年来英特尔和 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 native 的处理器都需要 SSE 和 SSE2,因此它们是所有代码的良好基准假设。 Windows 8.0、Windows 8.1 和 Windows 10 明确要求 SSE 和 SSE2 支持,即使对于 x86 架构也是如此,因此这些指令集非常普遍。换句话说,如果您未能通过 SSE 或 SSE2 检查,只需退出应用程序并出现 fatal error 。

#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 native 的已弃用指令集,因此您不应再在较新的代码中主动使用它们。一个好的经验法则是避免使用任何返回 __m64 或采用 __m64 数据类型的内部函数。

您可能想看看这个 DirectXMath blog series附有许多这些指令集和相关处理器支持要求的注释。

注意 (*) - 所有 AMD 3DNow!除了 PREFETCHPREFETCHW 外,指令已弃用。第一代 Intel64 处理器不支持这些指令,但后来添加了它们,因为它们被认为是核心 X64 指令集的一部分。 Windows 8.1 和 Windows 10 x64 特别需要 PREFETCHW,尽管测试有点奇怪。 Broadwell 之前的大多数英特尔 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,因此对 x86 代码使用 /arch:SSE2;此设置对于 x64 是隐式的代码),一个针对 AVX 优化的不同 EXE(使用 /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

关于c++ - 使用较新的 SIMD 版本时是否可以使用较旧的 SIMD 版本?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30355620/

相关文章:

c++ - 例如为什么有人应该在 C/C++ 中使用三重指针?

c - C 中的命名管道示例

c - 如何在 C 中监控 HTTP 流量?

c++ - _mm_mul_epu32 与 _mm_mul_epi32

c++ - C++ 中的仅限 gRPC 的 Tensorflow 服务客户端

c++ - STL函数来测试一个值是否在某个范围内?

c++ - Windows.h CONDITION_VARIABLE 导致异常 (C/C++)

c - 为什么这只初始化第一个元素?

c - 如何使用 SSE2 添加数组中的所有元素?

assembly - MOVDQA 和 MOVAPS x86 指令之间的区别?