使用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
也就是说,ftrunc2
额外使用一个movss
指令!
这是正常的吗?有关系吗?当您只需要设置底部元素时,_mm_set1_ps
是否应优先于_mm_set_ss
?
使用的编译器是带有-O3 -msse
的GCC 4.5.2。
答案 0 :(得分:5)
_mm_set_ss
直接映射到汇编指令(movss
)。但_mm_set1_ps
没有。
从我在GCC,MSVC和ICC上看到的:
将一对一映射到汇编指令的SSE内在函数通常被“按原样”处理 - 一个黑盒子。因此编译器只会优化适用于整个指令本身。但它不会尝试进行任何需要对各个向量元素进行数据流/依赖性分析的优化。
_mm_set1_ps
和_mm_set_ps
内在函数不会映射到单个指令,并且大多数编译器都会处理特殊情况。从我所看到的,我上面列出的所有三个编译器做都试图对各个元素进行数据流分析优化。
当你把它们放在一起时,第二个例子离开movss
,因为编译器没有意识到前3个元素无关紧要。 (它没有试图“打开”_mm_set_ss
内在的。)
答案 1 :(得分:0)
你正在遇到窥视孔优化器的怪癖。出于某种原因,在第一种情况下,它确定它可以将mov
折叠到cvttss2si
中,而在第二种情况下它会失败。问题是,这有关系吗?额外移动指令几乎空闲 - 它在指令流中占用额外的4个字节和一个额外的解码槽,但两个序列都需要相同数量的执行槽和相同数量的加载/商店老虎机(这通常很重要)。唯一可能的关键点是ifetch的4个额外字节 - 但由于ftrunc1使用10个字节而ftrunc2使用14个,因此两者都适合单个缓存行,因此您不会看到任何差异。为了最大限度地减少开销,我会更加关注不需要的%ebp cruft(你用-fno-omit-frame-pointer编译? - 我的-O3包括-fomit-frame-pointer默认情况下)。你可以通过内联这个功能做得更好,这可能会彻底改变窥视孔优化器所看到的内容,因此可以使它在任何一种情况下都能更好地工作(甚至可以逆转它工作得更好的情况) - 没有办法分辨无需编译大型程序并查看汇编代码。
最重要的是,两者之间不可能存在任何可测量的速度差异......