为什么gcc只在_mm_set_ss时添加这个movss指令?

5

考虑使用SSE的这两个函数:

#include <xmmintrin.h>

int ftrunc1(float f) {
    return _mm_cvttss_si32(_mm_set1_ps(f));
}

int ftrunc2(float f) {
    return _mm_cvttss_si32(_mm_set_ss(f));
}

对于任何输入,两者的行为都完全相同。但是汇编输出不同:

ftrunc1:
    pushl   %ebp
    movl    %esp, %ebp
    cvttss2si   8(%ebp), %eax
    leave
    ret

ftrunc2:
    pushl   %ebp
    movl    %esp, %ebp
    movss   8(%ebp), %xmm0
    cvttss2si   %xmm0, %eax
    leave
    ret

也就是说,ftrunc2ftrunc1 多使用了一个 movss 指令!

这种情况是否正常?是否有影响?当您只需要设置底部元素时,_mm_set1_ps 是否始终优于 _mm_set_ss


编译器使用的是带有 -O3 -msse 参数的 GCC 4.5.2。


1
看起来又是编译器优化失败的案例。或者说,编译器没有足够聪明地意识到前三个元素没有被使用。 - Mysticial
是的,在这两种情况下都不需要使用movss(另一方面,除了指令长度增加外,从性能上来看它可能并没有影响,因为CPU在内部将这两个序列视为相等)。编译器只是在优化SSE内置函数方面做得不太好。 - jalf
2个回答

5

_mm_set_ss 直接映射到汇编指令 (movss)。但是 _mm_set1_ps 不是。

根据我在 GCC、MSVC 和 ICC 上的观察:

将 SSE intrinsics 映射为一个汇编指令的通常被视为“原样” - 一个黑盒子。因此,编译器仅对整个指令本身适用的优化。但它不会尝试进行任何需要对单个矢量元素进行数据流/依赖性分析的优化。

_mm_set1_ps_mm_set_ps intrinsics 不映射到单个指令,并且大多数编译器都有特殊处理。从我所见过的情况来看,我列出的三个编译器都试图对单个元素执行数据流分析优化。


当你把所有东西放在一起时,第二个例子保留了 movss,因为编译器没有意识到前三个元素并不重要。(它没有尝试“打开”_mm_set_ss intrinsic。)


0
你遇到了滑动窗口优化器的一个怪癖。由于某种原因,在第一种情况下,它可以将mov折叠到cvttss2si中,在第二种情况下失败了。问题是,这有关系吗?额外的移动指令几乎是免费的 - 它占用了指令流中的额外4个字节和一个额外的解码插槽,但两个序列需要相同数量的执行插槽和相同数量的加载/存储插槽(这通常很重要)。唯一可能出现问题的地方是ifetch的4个额外字节 - 但由于ftrunc1使用10个字节,ftrunc2使用14个字节,两者都可以适应单个缓存行,因此您不会看到任何区别。为了最小化这种开销,我更关心不需要的%ebp碎片(您是否使用-fno-omit-frame-pointer进行编译? 我认为-O3默认包含-fomit-frame-pointer)。通过内联此函数,您的效果会更好,这很可能完全改变了滑动窗口优化器的视图,因此在任何情况下都可以使其工作得更好(甚至颠倒了它工作更好的情况) - 没有办法告诉,除非编译更大的程序并查看汇编代码。
底线是,这两者之间不太可能有任何可测量的速度差异...

第二段代码是否存在对 xmm0 旧值的错误依赖? - harold

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接