当前位置: 首页 > 知识库问答 >
问题:

如何将标量合并为向量,而编译器又不会浪费一条指令将上部元素清零?Intel的内在设计限制?

荣声
2023-03-14

我没有一个特定的用例;我在问,这是否真的是英特尔内部的设计缺陷/限制,或者我只是遗漏了什么。

如果您想将标量浮点与现有的向量结合起来,似乎没有一种方法可以不使用Intel Intrinsics对标量进行高元素归零或将其广播到向量中。我还没有研究过GNU C原生向量扩展和相关的内建。

如果额外的内部优化掉了,这也不会太糟,但对于gcc(5.4或6.2)就不是这样了。使用pmovzxinsertps作为加载也没有什么好方法,因为它们的内在属性仅采用向量参数。(而且gcc不会将标量->向量加载折叠到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))的指令。而不是编写一些编译器必须优化的东西,难道不应该有一种方法,以“高效”的编写它在第一时间?即使是最近的gcc也没有优化它,所以这是一个真正的(但很小的)问题。

如果存在__m256_mm256_castps128_ps256(__m128a)的标量->128b等效值,则这是可能的。即生成一个__m128,上面的元素中有未定义的垃圾,下面的元素中有float,如果标量float/double已经在xmm寄存器中,则编译为零asm指令。

以下这些本质都不存在,但它们应该存在。

>

  • 与前面描述的_mm256_castps128_ps256的标量->__m128等效。标量已入寄存器情况下的最通用解决方案

    __m128_mm_move_ss_scalar(__m128a,float s):用标量s替换向量a的低元素。如果有一个通用标量->__m128(前面的要点),这实际上是不必要的。(movss的reg-reg形式合并,这与load形式不同,load形式为零,而movd形式在这两种情况下都为上部元素为零。若要复制包含标量浮点的寄存器而不存在虚假依赖关系,请使用movaps)。

    __m128i_mm_loadzxbd(const uint8_t*four_bytes)和其他大小的PMOVZX/pmovsx:AFAICT,没有很好的安全方法来使用PMOVZX本征作为加载,因为这种不方便的安全方法没有优化gcc。

    __m128_mm_insertload_ps(__m128 a,float*S,const int imm8)。INSERTPS作为加载的行为不同:imm8的上2位被忽略,并且它总是取有效地址处的标量(而不是内存中向量中的元素)。这使得它可以处理不是16B对齐的地址,如果浮起就在未映射的页面之前,它甚至可以正常工作。

    与PMOVZX一样,gcc无法将上元素为零的_mm_load_ss()折叠到用于插入的内存操作数中。(注意,如果imm8的高2位不都是零,那么_mm_insert_ps(xmm0,_mm_load_ss(),imm8)可以编译为insertpsxmm0,xmm0,foo,使用不同的imm8将vec中的元素置零,就像--如果src元素实际上是MOVSS从内存中产生的零。在这种情况下,Clang实际上使用xorps/blendps)

    是否有任何可行的变通方法来仿效那些既安全(通过例如加载可能触及下一页和segfault的16b而不在-o0处中断),又高效(至少在-o3处没有浪费当前gcc和clang,最好也是其他主要编译器的指令)的方法?最好也是以可读的方式,但如果需要,它可以放在内联包装函数后面,如__m128 float_to_vec(float a){something(a);}

    英特尔有什么好的理由不推出那样的内含子吗?他们可以在添加_mm256_castps128_ps256的同时添加一个带有未定义的上部元素的float->__m128。这是编译器内部的问题使它难以实现吗?也许特别是ICC内部?

    x86-64上的主要调用约定(SysV或MS__vectorcall)使用xmm0中的第一个FP参数,并返回xmm0中的标量FP参数,上面的元素未定义。(有关ABI文档,请参阅x86标记wiki)。这意味着编译器在一个带有未知上部元素的寄存器中有一个标量float/double并不少见。在向量化的内部循环中,这将是很少见的,所以我认为避免这些无用的指令将大多只是节省一点代码大小。

    pmovzx的情况更为严重:这是您可能在内部循环中使用的东西(例如,对于VPERMD混洗掩码的LUT,与将每个索引填充到32位存储在内存中相比,在缓存占用空间上节省了4倍)。

    pmovzx-as-a-load问题已经困扰我一段时间了,这个问题的最初版本让我想到了在xmm寄存器中使用标量浮点的相关问题。pmovzx作为负载的用例可能比标量->__m128多。

  • 共有1个答案

    黎承颜
    2023-03-14

    这在GNU C内联asm中是可行的,但这是很难看的,并且使许多优化失败,包括常数传播(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转换成向量:

    gcc5.3
    float_to_vec_and_shuffle_asm(float __vector(4), float):
        shufps  xmm0, xmm1, 0       # tmp93, xv,
        ret
    

    请参阅Godbolt编译器资源管理器中的这段代码。

    这在纯粹的汇编语言中显然是微不足道的,在这里您不必与编译器斗争以使它不发出您不想要或不需要的指令。

    我还没有找到任何真正的方法来编写一个__m128 float_to_vec(float a){something(a);}来编译一个ret指令。使用_mm_undefined_pd()_mm_move_sd()尝试使用double实际上会使gcc的代码更糟糕(请参阅上面的Godbolt链接)。现有的float->__m128本征没有任何帮助。

    离题:actual_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
    
     类似资料:
    • 我经常注意到gcc在可执行文件中将乘法转换为移位。当将与相乘时,可能会发生类似的情况。例如,可能只是将的指数增加1,从而节省一些周期。如果有人要求编译器这样做(例如,通过),编译器通常会这样做吗? 编译器通常是否足够聪明来执行此操作,还是我需要自己使用或函数系列来执行此操作?

    • 本文向大家介绍C#中如何将容量设置为ArrayList中元素的实际数量,包括了C#中如何将容量设置为ArrayList中元素的实际数量的使用技巧和注意事项,需要的朋友参考一下 要将容量设置为ArrayList中元素的实际数量,代码如下- 示例 输出结果 这将产生以下输出- 示例 现在让我们来看另一个示例- 输出结果 这将产生以下输出-

    • 我有一个关于Spring WebFlux和Reactor的问题。我试图编写一个简单的场景,其中在GETendpoint中,我返回一个表示实体的DTO流,这些实体中的每一个都有一个表示另一个实体的其他DTO的集合。以下是详细信息。 我有两个实体,Person和Song,定义如下: 这些实体由以下DTO表示: 我的服务(为了简洁起见,这里没有显示)确实返回Mono和flux。然后我就有了以下RESTC

    • 问题内容: 假设我有一个numpy数组: 我有一个对应的“向量”: 我如何沿每一行进行减法或除法运算,所以结果是: 长话短说:如何使用对应于每一行的1D标量数组在2D数组的每一行上执行操作? 问题答案: 干得好。您只需要与广播结合使用(或):

    • 是否可以使用PDFBox(或其他库)将两个PDF中的元素合并为最终PDF? 我不是在寻找页面连接,而是在合并页面元素:

    • 变量定义 nsi脚本的变量定义用Var关键字,后跟变量名,变量是全局的并且是大小写敏感的。变量引用时需要加上前缀$。 除了用户自定义的变量外,nsi脚本中与定义寄存器变量$0~$9,$R0~$R9用于参数传递,以及系统变量用于特定用途,这些变量主要有: $INSTDIR,$OUTDIR,$CMDLINE,$LANGUAGE(这些变量都是可写的)。 $PROGRAMFILES,$COMMONFILE