使用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
吗?
使用的编译器是GCC 4.5.2和 -O3 -msse
.
2 回答
_mm_set_ss
直接映射到汇编指令(movss
) . 但_mm_set1_ps
没有 .从我在GCC,MSVC和ICC上看到的:
将一对一映射到汇编指令的SSE内在函数通常被视为“原样” - 黑盒子 . 因此编译器只会优化适用于整个指令本身 . 但它不会尝试进行任何需要对各个向量元素进行数据流/依赖性分析的优化 .
_mm_set1_ps
和_mm_set_ps
内在函数不映射到单个指令,并且大多数编译器都处理特殊情况 . 从我上面列出的内容开始尝试对各个元素执行数据流分析优化 .当你把它们放在一起时,第二个例子离开了
movss
,因为编译器并不重要 . (它没有尝试"open up"_mm_set_ss
内在 . )你正在遇到窥视孔优化器的怪癖 . 出于某种原因,在第一种情况下,它确定它可以将
mov
折叠到cvttss2si
中,而在第二种情况下它会失败 . 问题是,这有关系吗?额外的移动指令几乎是免费的 - 它在指令流中占用额外的4个字节和一个额外的解码槽,但是这两个序列需要相同数量的执行槽和相同数量的加载/存储槽(这通常是通常的)事项) . 唯一可能的关键点是ifetch的4个额外字节 - 但由于ftrunc1使用10个字节而ftrunc2使用14个,因此两者都适合单个缓存行,所以你更加关注不需要的%ebp cruft(2)你用-fno-omit-frame-pointer编译? - 我的-O3包括-fomit-frame-pointer默认情况下) . 如果没有编译更大的程序并查看汇编代码,你就无法判断 .最重要的是,两者之间不可能存在任何可测量的速度差异......