如何在没有编译器浪费指令归零上层元素的情况下将标量合并到向量中? 英特尔内在函数的设计限制?

我没有特定的用例; 我问这是否真的是英特尔内在函数中的设计缺陷/限制,或者我是否只是遗漏了某些内容。

如果你想将标量浮点数与现有向量相结合,那么使用英特尔内在函数时,如果没有高元素归零或将标量广播到向量中,似乎没有办法实现。 我没有研究过GNU C本机向量扩展和相关的内置函数。

如果额外的内在优化,这不会太糟糕,但它不与gcc(5.4或6.2)。 使用pmovzxinsertps作为载荷也没有好的方法,因为它们的内在函数只采用向量args的相关原因。 (并且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) )。 不是编写编译器必须优化的东西,而不应该首先在C中“高效”地编写它? 即使是最近的gcc 也没有优化它,所以这是一个真正的(但很小的)问题。


如果有一个标量 – > 128b相当于__m256 _mm256_castps128_ps256 (__m128 a)这将是可能的。 即在上部元素中生成带有未定义垃圾的__m128 ,并在低元素中生成浮点数,如果标量float / double已经在xmm寄存器中,则编译为零asm指令。

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

  • 标量 – > __ m128相当于_mm256_castps128_ps256 ,如上所述。 标量已登记案例的最通用解决方案。
  • __m128 _mm_move_ss_scalar (__m128 a, float s) :用标量s替换向量a低位元素。 如果有一个通用标量 – > __ m128(前一个要点),这实际上并不是必需的。 ( movss合并的reg-regforms,与零forms的加载forms不同,并且不同于在两种情况下都将上部元素归零的movss 。要复制一个包含标量浮点而没有错误依赖的寄存器,请使用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对齐的地址,并且如果float在未映射的页面之前,即使没有错误也可以工作。

    与PMOVZX一样,gcc无法将上元素归零_mm_load_ss()折叠为INSERTPS的内存操作数。 (注意,如果imm8的高2位不是零,那么_mm_insert_ps(xmm0, _mm_load_ss(), imm8)可以编译为insertps xmm0,xmm0,foo ,使用不同的imm8,在vec as-if中将元素insertps xmm0,xmm0,foo为零src元素实际上是MOVSS从内存中产生的零。在这种情况下,Clang实际上使用了XORPS / BLENDPS)


是否有任何可行的解决方法可以模拟任何安全的(不要在-O0中断,例如加载16B可能会触及下一页和段错误),并且效率高(在-O3上没有浪费的指令与当前的gcc和clang至少,最好也是其他主要编译器)? 最好也是以可读的方式,但如果有必要,它可以放在内联包装函数后面,如__m128 float_to_vec(float a){ something(a); } __m128 float_to_vec(float a){ something(a); }

英特尔没有任何理由不引入这样的内在函数吗? 他们可以在添加_mm256_castps128_ps256的同时添加一个浮点数 – > __ m128,其中包含未定义的上层元素。 这是编译器内部的问题,难以实现吗? 也许特别是ICC内部人员?


x86-64(SysV或MS __vectorcall )上的主要调用约定采用__vectorcall的第一个FP arg,并返回xmm0中的标量FP args,其中上部元素未定义。 (有关ABI文档,请参阅x86标记wiki)。 这意味着编译器在具有未知上层元素的寄存器中具有标量float / double并不罕见。 这在矢量化内循环中很少见,所以我认为避免这些无用的指令通常只会节省一些代码大小。

pmovzx情况更严重:这可能是你在内部循环中使用的东西(例如,对于VPERMD shuffle掩码的LUT,在高速缓存占用空间中保存4倍而在存储器中存储填充为32位的每个索引)。


pmovzx-as-a-load问题一直困扰着我一段时间, 这个问题的原始版本让我想到了在xmm寄存器中使用标量浮点的相关问题。 pmovzx作为一个负载可能比标量更多的用例 – > __ m128。

它适用于GNU C inline 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 ,并将内联以允许您将标量转换为向量:

 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内在函数都没有帮助。


偏离主题:实际的_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