我没有特定的用例;我在问这是否真的是英特尔内部特性的设计缺陷/局限性,或者我是否只是缺少一些东西。
如果要将标量浮点数与现有 vector 结合起来,似乎没有办法使用Intel内部函数将其进行高元素置零或将标量广播到 vector 中。我还没有研究过GNU C native vector 扩展和相关的内建函数。
如果优化了额外的内在函数,这并不太糟,但对于gcc(5.4或6.2)却不是这样。也没有很好的方法将pmovzxinsertps用作负载,原因是它们的内在函数仅采用 vector args。 (而且gcc不会将标量-> vector 加载折叠到asm指令中。)

__m128 replace_lower_two_elements(__m128 v, float x) {
  __m128 xv = _mm_set_ss(x);        // WANTED: something else for this step, some compilers actually compile this to a separate insn
  return _mm_shuffle_ps(v, xv, 0);  // lower 2 elements are both x, and the garbage is gone
}
gcc 5.3 -march = nehalem -O3输出,以启用SSE4.1并针对该Intel CPU进行调整:(如果没有SSE4.1,则更糟;使用多条指令将高位元素清零)。
    insertps  xmm1, xmm1, 0xe    # pointless zeroing of upper elements.  shufps only reads the low element of xmm1
    shufps    xmm0, xmm1, 0      # The function *should* just compile to this.
    ret
TL:DR:这个问题的其余部分只是询问您是否实际上可以有效地执行此操作,否则,为什么不这样做。

clang的shuffle-optimizer做到了这一点,并且不会浪费将高元素清零(_mm_set_ss(x))或将标量复制到其中的指令(_mm_set1_ps(x))。除了编写编译器必须进行优化的东西外,难道不应该有一种方法可以首先用C语言“有效”地编写它吗?即使是最近的gcc也无法对其进行优化,因此这是一个实际的(但次要的)问题。

如果有一个标量-> 128b等于 __m256 _mm256_castps128_ps256 (__m128 a) ,这将是可能的。即产生一个__m128,在高位元素中具有未定义的垃圾,在低位元素中具有浮点数,如果标量浮点数/ double 数已经在xmm寄存器中,则编译为0条asm指令。
以下内在函数不存在,但应为
  • 相当于_mm256_castps128_ps256的标量-> __ m128,如上所述。标量已经注册的情况的最通用解决方案。
  • __m128 _mm_move_ss_scalar (__m128 a, float s):用标量a替换 vector s的低位元素。如果存在通用标量-> __ m128(先前的项目符号点),则实际上并不需要这样做。 ( movss 的reg-reg形式合并,这与加载形式为零不同,并且与 movd 两种情况下都将上层元素归零不同。要复制不带虚假依赖关系的标量浮点寄存器,请使用 movaps )。
  • __m128i _mm_loadzxbd (const uint8_t *four_bytes)和其他大小的PMOVZX/PMOVSX:AFAICT, there's no good safe way to use the PMOVZX intrinsics as a load,因为不方便的安全方式无法通过gcc进行优化。
  • __m128 _mm_insertload_ps (__m128 a, float *s, const int imm8)INSERTPS作为负载的行为有所不同:imm8的高2位被忽略,并且它始终在有效地址(而不是内存中 vector 的元素)中采用标量。这样,它就可以使用未与16B对齐的地址,并且即使float就在未映射页面的前面,也可以正常工作。
    与PMOVZX一样,gcc无法将高零元素_mm_load_ss()折叠到INSERTPS的内存操作数中。 (请注意,如果imm8的高2位不都为零,则_mm_insert_ps(xmm0, _mm_load_ss(), imm8)可以编译为insertps xmm0,xmm0,foo,而另一个imm8可以将vec中的元素置零,如果src元素实际上是由MOVSS从内存中产生的零。在这种情况下实际上使用XORPS/BLENDPS)

  • 是否有任何可行的解决方法来模拟任何既安全(不会在-O0处中断,例如加载可能会触及下一页并出现段错误的16B),又有效的方法(在当前gcc中-O3处不会浪费指令)和至少clang,最好还有其他主要编译器)?最好还是以一种可读的方式,但是如果需要的话,可以将其放在像__m128 float_to_vec(float a){ something(a); }这样的内联包装函数的后面。
    英特尔是否有充分的理由不引入此类内在函数?他们可以在添加_mm256_castps128_ps256的同时添加一个float-> __ m128并带有未定义的上层元素。 这是编译器内部问题使其难以实现吗? 也许是ICC内部人员?

    x86-64上的主要调用约定(SysV或MS __vectorcall)采用xmm0中的第一个FP arg并返回xmm0中的标量FP args,而上半部元素未定义。 (有关ABI文档,请参见x86标签Wiki)。这意味着编译器在具有未知高位元素的寄存器中进行标量浮点/ double 运算并不少见。在矢量化的内部循环中这种情况很少见,因此我认为避免这些无用的指令通常只会节省一些代码大小。
    pmovzx的情况更为严重:您可能会在内部循环中使用这种情况(例如,对于VPERMD随机掩码的LUT,在缓存占用空间中节省4倍,而将每个索引填充到32位存储在内存中)。

    pmovzx即加载问题已经困扰了我一段时间,the original version of this question让我思考了在xmm寄存器中使用标量浮点数的相关问题。 pmovzx作为负载的用例可能要多于标量-> __ m128的用例。

    最佳答案

    它可以与GNU C内联汇编一起使用,但这很丑陋,并且无法进行许多优化,包括常量传播(https://gcc.gnu.org/wiki/DontUseInlineAsm)。 这将不是可接受的答案。我将其添加为答案,而不是部分问题,因此该问题仍然很短。

    // don't use this: defeating optimizations is probably worse than an extra instruction
    #ifdef __GNUC__
    __m128 float_to_vec_inlineasm(float x) {
      __m128 retval;
      asm ("" : "=x"(retval) : "0"(x));   // matching constraint: provide x in the same xmm reg as retval
      return retval;
    }
    #endif
    

    确实可以根据需要将其编译为单个ret,并将内联以让您将标量shufps转换为 vector :
    gcc5.3
    float_to_vec_and_shuffle_asm(float __vector(4), float):
        shufps  xmm0, xmm1, 0       # tmp93, xv,
        ret
    

    请在Godbolt compiler explorer 上查看此代码。

    在纯汇编语言中,这显然是微不足道的,在这种情况下,您不必与编译器进行斗争即可使它不必发出不需要或不需要的指令。

    我还没有找到任何真正的方法来编写可编译为__m128 float_to_vec(float a){ something(a); }指令的ret。使用double_mm_undefined_pd()尝试_mm_move_sd()实际上会使使用gcc的代码更糟(请参见上面的Godbolt链接)。 the existing float->__m128 intrinsics均无帮助。

    主题外:实际的_mm_set_ss()代码生成策略:当您编写的代码的上半部元素为零时,编译器会从一系列有趣的策略中进行选择。有些好,有些奇怪。在同一个编译器(gcc或clang)上,double和float的策略也有所不同,如您在上面的Godbolt链接上所见。

    一个示例:__m128 float_to_vec(float x){ return _mm_set_ss(x); }编译为:
        # gcc5.3 -march=core2
        movd    eax, xmm0      # movd xmm0,xmm0 would work; IDK why gcc doesn't do that
        movd    xmm0, eax
        ret
    
        # gcc5.3 -march=nehalem
        insertps        xmm0, xmm0, 0xe
        ret
    
        # clang3.8 -march=nehalem
        xorps   xmm1, xmm1
        blendps xmm0, xmm1, 14          # xmm0 = xmm0[0],xmm1[1,2,3]
        ret
    

    关于c - 如何将标量合并到 vector 中,而不会导致编译器浪费将上位元素清零的指令?英特尔固有特性中的设计限制?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/39318496/

    10-14 18:40