C++ 如何在支持旧CPU的同时利用新CPU上的AVX2?

C++ 如何在支持旧CPU的同时利用新CPU上的AVX2?,c++,visual-studio,optimization,simd,C++,Visual Studio,Optimization,Simd,我有一些图像处理算法,我用三个版本实现: 使用x64指令集(rax、rbx等寄存器) 使用SSE指令集(xmm寄存器) 使用AVX2指令集(ymm寄存器) 每个优化步骤都会提高性能。但是,我需要在只支持SSE的旧CPU上运行它(我在VisualStudio上使用x64平台,所以我所有的CPU都支持SSE) 在Visual Studio中,有一个名为“启用增强的指令集”的设置,我必须将其设置为/arch:AVX2,才能在较新的CPU上获得最佳性能。但是,使用此设置,可执行文件会在我的旧CPU上崩溃

我有一些图像处理算法,我用三个版本实现:

  • 使用x64指令集(rax、rbx等寄存器)
  • 使用SSE指令集(xmm寄存器)
  • 使用AVX2指令集(ymm寄存器)
  • 每个优化步骤都会提高性能。但是,我需要在只支持SSE的旧CPU上运行它(我在VisualStudio上使用x64平台,所以我所有的CPU都支持SSE)

    在Visual Studio中,有一个名为“启用增强的指令集”的设置,我必须将其设置为
    /arch:AVX2
    ,才能在较新的CPU上获得最佳性能。但是,使用此设置,可执行文件会在我的旧CPU上崩溃。如果我将“Enable Enhanced Instruction set”(启用增强指令集)设置为
    /arch:SSE2
    ,则我的可执行文件可以在较旧的CPU上工作,但在较新的CPU上无法获得最佳性能

    我使用较新的CPU测量了编译器标志和指令集的所有组合的执行速度。总结如下表所示

    Instruction set || Compilation flags which I use || /arch:SSE /arch:AVX2 ----------------++------------------------------------ x64 || bad (4.6) bad (4.5) SSE || OK (1.9) bad (5.3) AVX2 || bad (3.2) good (1.4) 我猜如果VisualStudio获得
    /arch:AVX2
    编译标志,它将执行所有必要的特定于AVX2的优化,如。因此,我不知道如何使用相同的编译可执行文件在这两种类型的CPU上获得最佳性能


    这可能吗?如果是,我需要向Visual Studio编译器提供哪些编译标志?

    英特尔这样做的方式是CPU调度(请查看英特尔编译器文档中的
    ax
    标志)。
    ax
    标志特定于英特尔编译器,并进行隐式CPU调度。它在VS上不可用,因此您必须手动执行

    在代码开始时,检查CPU特性,并在某个地方设置一些全局标志

    然后,当您调用其中一个函数时,首先检查标志状态以查看您实际要调用的函数

    因此,最终会得到不同风格的函数。要解决这个问题,可以将它们放在不同的特定名称空间中(如libsimdpp),也可以手动更改函数名(如英特尔编译器)


    此外,任何64位CPU都支持SSE2的构造,因此案例1不存在。

    英特尔性能原语对每个CPU系列使用单独的二进制实现,在运行时从DLL中选择和加载,因此您可能必须这样做。我很惊讶AVX2 CPU运行SSE代码的速度很慢。VisualStudio不会自动复制代码,他们会根据实际的CPU使用合适的版本,所以你必须自己处理这个问题。最终,您需要提供多个代码路径,具体如何做到这一点取决于您。当您想利用
    /arch
    优化时,您很可能希望构建同一DLL的多个版本,并在检测到实际CPU能力后加载最合适的版本。SSE2是x86-64的基线,而不仅仅是SSE。@RomanR.:不过,其他编译器可以对使用不同目标选项编译的克隆执行运行时调度。ICC仅为未修改的源代码提供选项,GCC将使用
    ifunc
    工具进行操作。
    ax
    标志特定于英特尔编译器,并进行隐式CPU调度。它在VS上不可用,您必须手动执行。
    // AVX2 - conversion from 32-bit to 16-bit
    temp = _mm256_packus_epi32(input[0], input[1]);
    output = _mm256_permute4x64_epi64(temp, 0xd8);
    
    // SSE - choosing one of two results using a mask
    result = _mm_blendv_epi8(result0, result1, mask);