【问题标题】:can I assign the result of intrinsic that returns __m128i to variable of the type__m128i_u?我可以将返回 __m128i 的内在结果分配给类型为 __m128i_u 的变量吗?
【发布时间】:2021-10-26 13:48:43
【问题描述】:

如标题所示 - 我想做如下:

__m128i_u* avxVar = (__m128i_u*)Var;  // Var allocated with alloc
*avxVar = _mm_set_epi64(...);         // is that ok to assign __m128i to __m128i_u ?

【问题讨论】:

    标签: simd sse intrinsics sse2


    【解决方案1】:

    是的,但请注意 __m128i_u 不可移植(例如,可移植到 MSVC);这是 GCC/clang 在内部使用来实现未对齐的 loadu/storeu 内在函数。 完全等同于以正常方式进行:

    _mm_storeu_si128((__m128i*)Var, vec);
    

    (其中vec 是任何__m128i。例如,它可以是_mm_set_epi64x 或变量。)

    GCC 11 的 emmintrin.h 实现 _mm_storeu_si128 是这样定义的,采用 __m128i_u* 指针 arg,因此取消引用会进行未对齐的访问(如果未优化)。

    // GCC internals, quoted for reference only.
    // Just use #include <immintrin.h> in your own code
    extern __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__))
    _mm_storeu_si128 (__m128i_u *__P, __m128i __B)
    {
      *__P = __B;
    }
    

    所以是的,GCC 的标头取决于 __m128i*__m128i_u* 是否兼容且可隐式转换。

    _mm_storeu_si128movdqu 的内在属性,__m128i_u* 取消引用也是如此。但实际上这些内在函数只是为了向编译器传达对齐信息而存在,由编译器决定何时实际加载和存储,就像 char* 的 deref 一样。

    (有趣的事实:__m128i* 是一个may_alias 类型,就像char*,所以你可以在不违反严格别名的情况下将它指向任何东西。Is `reinterpret_cast`ing between hardware SIMD vector pointer and the corresponding type an undefined behavior?


    还请注意,_mm_set_epi64 采用 __m64 参数:它用于从两个 MMX 向量构建 SSE2 向量,而不是从标量 int64_t。你可能想要_mm_set_epi64x


    它们的编译方式相同

    void foo(void *Var) {
        __m128i_u* avxVar = (__m128i_u*)Var;
        *avxVar = _mm_set_epi64x(1, 2); 
    }
    
    void bar(void *Var) {
        _mm_storeu_si128((__m128i*)Var, _mm_set_epi64x(1, 2) );
    }
    

    这两个函数在 gcc/clang/MSVC 中编译相同(并且在语义上是等效的,因此在内联后总是相同)。 但是只有第二个使用 MSVC 编译,正如您在 Godbolt 编译器资源管理器中看到的那样:https://godbolt.org/z/Y8Wq96Pqs。如果禁用#ifdef __GNUC__,则会在 MSVC 上出现编译器错误。

    ## GCC -O3
    foo:
            movdqa  xmm0, XMMWORD PTR .LC0[rip]
            movups  XMMWORD PTR [rdi], xmm0
            ret
    bar:
            movdqa  xmm0, XMMWORD PTR .LC0[rip]
            movups  XMMWORD PTR [rdi], xmm0
            ret
    .LC0:
            .quad   2
            .quad   1
    

    对于更复杂的周围代码,_mm_loadu_si128 可以折叠到仅用于 AVX 的 ALU 的内存源操作数(例如 vpaddb xmm0, xmm1, [rdi],但 _mm_load_si128 对齐的负载可以折叠到 SSE 内存源中,例如 paddb xmm0, [rdi]

    【讨论】:

    • 谢谢。那么我应该期望这两种方法的延迟和吞吐量是相同的吗?我找不到任何关于 __m128i_u 的信息,这就是我要问的原因。
    • 即使在不支持 avx2 的 CPU 上,我也可以将我的方法与 256 位寄存器一起使用。但我不能用那里_mm256_storeu_si256。我说的对吗?
    • @vela18:没有记录它,因为它不打算使用,除非在 GCC/clang 的 _mm_loadu_si128 实现中。 (这很有效。它只是让 C 编译器发出未对齐的负载/存储的一种机制,请参阅更新的答案。在您发表评论之前,我已经编写了一些更新。)
    • @vela18: vmovdqa / vmovdqu 有点令人惊讶的是只需要 AVX1,所以是的,如果您有没有 AVX2 的 AVX1(例如 Sandybridge 或 Bulldozer),您可以使用 _mm256_storeu_si256。如果您没有 AVX2,通常情况下您只需将 _mm256_storeu_ps 与 AVX1 一起使用。但请注意,所有没有 AVX2 的现有 CPU 实际上会将 256 位加载/存储作为两个 16 位半,而 SnB 对未对齐有额外的惩罚。
    • 因此,如果您正在编写仅适用于没有 AVX2 的 CPU 的函数版本,则使用 16 字节整数向量复制未对齐的内存可能会更好。如果代码可以在任何带有 AVX 的东西上运行,包括具有 AVX2 的现代 CPU,那么是的,可能只使用 __m256__m256i。当您编译时,请注意调整选项-mavx256-split-unaligned-load,不幸的是默认情况下:Why doesn't gcc resolve _mm256_loadu_pd as single vmovupd?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-06
    • 1970-01-01
    • 2016-10-10
    • 2021-05-22
    • 2021-10-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多