The Effect of Architecture When Using SSE / AVX Intrinisics

前端 未结 2 881
一个人的身影
一个人的身影 2021-01-16 09:34

I wonder how does a Compiler treats Intrinsics.

If one uses SSE2 Intrinsics (Using #include ) and compile with -mavx fla

相关标签:
2条回答
  • 2021-01-16 10:06

    GCC and clang require that you enable all extensions you use. Otherwise it's a compile-time error, like error: inlining failed to call always_inline error: inlining failed in call to always_inline ‘__m256d _mm256_mask_loadu_pd(__m256d, __mmask8, const void*)’: target specific option mismatch

    Using -march=haswell or whatever is preferred over enabling specific extensions, because that also sets appropriate tuning options. And you don't forget useful ones like -mpopcnt that will let std::bitset::count() inline a popcnt instruction, and make all variable-count shifts more efficient with BMI2 shlx / shrx (1 uop vs. 3)


    MSVC and ICC do not, and will let you use intrinsics to emit instructions that they couldn't auto-vectorize with.

    You should definitely enable AVX if you use AVX intrinsics. I think I've read / seen that without that, MSVC won't always use vzeroupper where it should.


    For compilers that support GNU extensions (GCC, clang, ICC), you can use stuff like __attribute__((target("avx"))) on specific functions in a compilation unit. Or better, __attribute__((target("arch=haswell"))) to also set tuning options. (But that also enables AVX2 and FMA, which you might not want. I'm not sure if target attributes can set -mtune=xx)

    https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes (and also

    __attribute__((target())) will prevent them from inlining into functions with other target options, so be careful to use this on functions they will inline into, if the function itself is too small.

    See also https://gcc.gnu.org/wiki/FunctionMultiVersioning for using different target options on multiple definitions of the same function name, for compiler supported runtime dispatching. But I don't think there's a portable (to MSVC) way to do that.


    With MSVC you don't need anything, although like I said I think it's normally a bad idea to use AVX intrinsics without -arch:AVX, so you might be better off putting those in a separate file. But for AVX vs. AVX2 + FMA, or SSE2 vs. SSE4.2, you're fine without anything.

    Just #define AVX2_FUNCTION to the empty string instead of __attribute__((target("avx2,fma")))

    e.g.

    #if defined(__GNUC__) && !defined(__INTEL_COMPILER)
    // apparently ICC doesn't support target attributes
    #define TARGET_HASWELL __attribute__((target("arch=haswell")))
    #else
    #define TARGET_HASWELL   // empty
     // maybe warn if __AVX__ isn't defined for functions where this is used?
     // if you need to make sure MSVC uses vzeroupper everywhere needed.
    #endif
    
    
    TARGET_HASWELL
    void foo_avx(float *__restrict dst, float *__restrict src) {
        __m256 v = _mm256_loadu_ps(src);
        ...
        ...
    }
    

    With GCC and clang, the macro expands to the __attribute__((target)) stuff; with MSVC and ICC it doesn't.


    ICC pragma:

    https://software.intel.com/en-us/cpp-compiler-developer-guide-and-reference-optimization-parameter documents a pragma which you'd want to put before AVX functions to make sure vzeroupper is used properly in functions that use _mm256 intrinsics.

    #pragma intel optimization_parameter target_arch=AVX
    

    For ICC, you could #define TARGET_AVX as this, and always used it on a line by itself before the function, where you can put an __attribute__ or a pragma. You might also want separate macros for defining vs. declaring functions, if ICC doesn't want this on declarations. And a macro to end a block of AVX functions, if you want to have non-AVX functions after them. (For non-ICC compilers, this would be empty.)

    0 讨论(0)
  • 2021-01-16 10:07

    If you compile code with -mavx2 enabled your compiler will (usually) generate so-called "VEX encoded" instructions. In case of _mm_loadu_ps, this will generate vmovups instead of movups, which is almost equivalent, except that the latter will only modify the lower 128 bit of the target register, whereas the former will zero-out everything above the lower 128 bits. However, it will only run on machines which support at least AVX. Details on [v]movups are here.

    For other instructions like [v]addps, AVX has the additional advantage of allowing three operands (i.e., the target can be different from both sources), which in some cases can avoid copying registers. E.g.,

    _mm_mul_ps(_mm_add_ps(a,b), _mm_sub_ps(a,b));
    

    requires a register copy (movaps) when compiled for SSE, but not when compiled for AVX: https://godbolt.org/z/YHN5OA


    Regarding using AVX-intrinsics but compiling without AVX, compilers either fail (like gcc/clang) or silently generate the corresponding instructions which would then fail on machines without AVX support (see @PeterCordes answer for details on that).


    Addendum: If you want to implement different functions depending on the architecture (at compile-time) you can check that using #ifdef __AVX__ or #if defined(__AVX__): https://godbolt.org/z/ZVAo-7

    Implementing them in the same compilation unit is difficult, I think. The easiest solutions are to built different shared-libraries or even different binaries and have a small binary which detects the available CPU features and loads the corresponding library/binary. I assume there are related questions on that topic.

    0 讨论(0)
提交回复
热议问题