本文介绍了如何将一个标量合并到一个向量中,而不需要编译器浪费一条指令调零上层元素?英特尔内部函数的设计限制?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我没有特别的用例,我问这是否真的是英特尔内部函数的设计缺陷/限制,或者我只是错过了某些东西。



如果您想将标量浮点与现有的矢量,如果没有高度的元素归零或者使用英特尔内在函数将标量广播到矢量中,似乎没有办法做到这一点。我还没有调查过GNU C原生向量扩展和相关的内置函数。

如果额外的内部函数优化掉了,这不会太糟糕,但它不会gcc(5.4或6.2)。使用 pmovzx 插入作为加载也没有好方法,因为它们的内在函数仅使用向量参数。 (并且gcc不会将一个标量 - >向量加载到asm指令中。)

  __ m128 replace_lower_two_elements(__ m128 v,float x){
__m128 xv = _mm_set_ss(x); //通缉:这一步还有其他的东西,有些编译器实际上将它编译为一个单独的insn
return _mm_shuffle_ps(v,xv,0); //下面的2个元素都是x,并且垃圾不见了
}

gcc 5.3 -march = nehalem -O3输出,以启用SSE4.1并调整该CPU:(没有SSE4.1的情况更糟糕;多个指令将上面的元素归零)。

  insertps xmm1,xmm1,0xe#上层元素的无意义调零。 shufps只读取xmm1的低元素
shufps xmm0,xmm1,0#函数*应该*仅编译为此。
ret

TL:DR:这个问题的其余部分只是询问if你可以有效地做到这一点,如果不是的话。






clang的shuffle-optimizer获得这个权利,并且不会浪费高位元素置零指令( _mm_set_ss(x)),或者将标量复制到它们中( _mm_set1_ps(x) code>)。不应该编写一些编译器必须优化的东西,不应该有办法在C语言中高效地编写它?即使是最近的gcc 也没有对它进行优化,所以这是一个真正的(但是很小的)问题。






如果有一个标量 - > 128b等效的。即产生一个 __ m128 ,在上面的元素中包含未定义的垃圾,而在低元素中产生float,如果标量float / double已经在xmm寄存器中,则编译为零asm指令。



不存在以下内部函数,但它们应该


  • 标量 - > __ m128相当于 _mm256_castps128_ps256 ,如上所述。对于标量已在注册情况中最常用的解决方案。
  • __ m128 _mm_move_ss_scalar(__m128 a,float s):replace低元素矢量 a 标量 s 。如果有一个通用标量 - > __ m128(之前的项目符号),这实际上不是必需的。 (合并,不同于零的装载形式,而不像,它们在这两种情况下都为零。要复制一个包含标量浮点数的寄存器而不存在错误依赖性,请使用)。 (const uint8_t * four_bytes)和其他大小的 / PMOVSX:,因为不方便的安全方式不会使用gcc优化。

  • __ m128 _mm_insertload_ps(__m128 a,float * s,const int imm8)。 的行为与负载不同:imm8的高2位被忽略,它总是将有效地址处的标量(而不是来自存储器中的矢量的元素)。这使得它可以处理非16B对齐的地址,并且即使在未映射页面之前的浮动即使没有错误也能正常工作。



    与PMOVZX一样,gcc未能将上层元素置零 _mm_load_ss()折叠到INSERTPS的内存操作数中。 (请注意,如果imm8的高2位不为零,则 _mm_insert_ps(xmm0,_mm_load_ss(),imm8)可以编译为插入xmm0,xmm0,foo ,用不同的imm8将vec中的元素设置为零,如果src元素实际上是由内存中的MOVSS生成的零。 / p>







有没有可行的解决方法模拟任何一种既安全又不安全的方法(例如,加载16B可能会触及下一页和段错误),以及高效的(至少在-O3中没有当前gcc和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文档,请参阅标记wiki的问题)。这意味着编译器在具有未知上层元素的寄存器中具有标量浮点/双精度的情况并不少见。这在向量化内循环中很少见,所以我认为避免这些无用的指令大多会节省一些代码量。



pmovzx的情况更严重:是你可能在内部循环中使用的东西(例如,对于VPERMD shuffle mask的LUT,在缓存足迹中保存因子4,而将每个索引填充到内存中的32位)。




pmovzx-as-a-load问题一直困扰着我一段时间,并且)。 这不会被接受的答案。我将这个作为答案加入,而不是问题的一部分,所以 isn't huge.

// 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

This does compile to a single ret, as desired, and will inline to let you shufps a scalar into a vector:

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

See this code on the Godbolt compiler explorer.

This is obviously trivial in pure assembly language, where you don't have to fight with a compiler to get it not to emit instructions you don't want or need.


I haven't found any real way to write a __m128 float_to_vec(float a){ something(a); } that compiles to just a ret instruction. An attempt for double using _mm_undefined_pd() and _mm_move_sd() actually makes worse code with gcc (see the Godbolt link above). None of the existing float->__m128 intrinsics help.


Off-topic: actual _mm_set_ss() code-gen strategies: When you do write code that has to zero upper elements, compilers pick from an interesting range of strategies. Some good, some weird. The strategies also differ between double and float on the same compiler (gcc or clang), as you can see on the Godbolt link above.

One example: __m128 float_to_vec(float x){ return _mm_set_ss(x); } compiles to:

    # 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

这篇关于如何将一个标量合并到一个向量中,而不需要编译器浪费一条指令调零上层元素?英特尔内部函数的设计限制?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-14 01:40