我想尝试编写自己的绝对值函数。我认为计算绝对值最快的方法是简单地屏蔽掉符号位(IEEE 754中的最后一位)。我想将其速度与标准的abs
函数进行比较。这是我的实现:
// Union used for type punning
union float_uint_u
{
float f_val;
unsigned int ui_val;
};
// 'MASK' has all bits == 1 except the last one
constexpr unsigned int MASK = ~(1 << (sizeof(int) * 8 - 1));
float abs_bitwise(float value)
{
float_uint_u ret;
ret.f_val = value;
ret.ui_val &= MASK;
return ret.f_val;
}
声明一下,我知道这种类型游戏不是标准的C++。 然而,这只是为了教育目的,并且根据文档,GCC支持这种操作。
我认为这应该是计算绝对值最快的方法,所以它至少应该和标准实现一样快。 然而,在计时100000000个随机值的迭代后,我得到了以下结果:
Bitwise time: 5.47385 | STL time: 5.15662
Ratio: 1.06152
我的abs
函数慢了约6%。
汇编输出
我使用了-O2
优化和-S
选项(汇编输出)进行编译,以确定发生了什么。我提取了相关部分:
; 16(%rsp) is a value obtained from standard input
movss 16(%rsp), %xmm0
andps .LC5(%rip), %xmm0 ; .LC5 == 2147483647
movq %rbp, %rdi
cvtss2sd %xmm0, %xmm0
movl 16(%rsp), %eax
movq %rbp, %rdi
andl $2147483647, %eax
movd %eax, %xmm0
cvtss2sd %xmm0, %xmm0
观察结果
我不太擅长汇编语言,但主要的问题是,标准函数直接在 xmm0
寄存器上操作。但我的函数会先将值移动到 eax
(由于某种原因),执行 and
,然后再将其移动到 xmm0
。我猜测这多出来的 mov
会使速度变慢。我还注意到,标准版本将位掩码存储在程序的其他位置,而非立即数。但我猜这并不重要。两个版本也使用了不同的指令(例如,movl
vs movss
)。
系统信息
这是在 Debian Linux (unstable branch) 上使用 g++ 编译的。g++ --version
输出:
g++ (Debian 10.2.1-6) 10.2.1 20210110
如果这两个版本的代码都是通过and
计算绝对值,为什么优化器不生成相同的代码?具体来说,为什么它在优化我的实现时感觉需要包含一个额外的mov
?
inline
关键字,那么您应该对现在inline
的含义进行一些研究。如果您在头文件中定义了一个函数并在多个编译单元中使用该头文件,则inline
非常重要。但是对于现代编译器来说,仅使用inline
关键字并不能改变内联函数的概率。 - t.niese1
应该被写作1u
,否则会出现实现定义的行为(将1
左移至符号位)。 - M.M