c++ - 为什么标准的“abs”功能比我的快?
问题描述
我想尝试制作自己的绝对值函数。我认为计算绝对值的最快方法是简单地屏蔽符号位(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(不稳定分支)上用 g++ 编译的。g++ --version
输出:
g++ (Debian 10.2.1-6) 10.2.1 20210110
如果这两个版本的代码都以相同的方式(通过 an )计算绝对值and
,为什么优化器不生成相同的代码?具体来说,为什么mov
在优化我的实现时感觉需要包含额外的内容?
解决方案
我的组装有点不同。根据 x86_64 Linux ABI,float
参数通过xmm0
. 使用标准fabs
,按位AND
运算直接在此寄存器上执行(英特尔语法):
andps xmm0, XMMWORD PTR .LC0[rip] # .LC0 contains 0x7FFFFFFF
ret
但是,在您的情况下,按位AND
对 type 的对象执行unsigned int
。因此,GCC 做同样的事情,需要先xmm0
移到eax
:
movd eax, xmm0
and eax, 2147483647
movd xmm0, eax
ret
现场演示:https ://godbolt.org/z/xj8MMo
我还没有找到任何方法来强制GCC优化器仅使用纯 C/C++ 源代码AND
直接执行。xmm0
似乎需要在汇编代码或 Intel 内在代码上构建有效的实现。
相关问题:如何对浮点数执行按位运算。所有提出的解决方案基本上都会产生相同的结果。
我也尝试使用该copysign
功能,但结果更糟。然后生成的机器代码包含 x87 指令。
无论如何,非常有趣的是,Clang优化器足够聪明,可以使所有 3 种情况下的程序集等效:https ://godbolt.org/z/b6Khv5 。
推荐阅读
- go - 使用缓冲通道发送和接收
- java - Jpos java.net.SocketException
- python - 使用 twilio python api 进行调用时出错
- android - 当我按下系统后退按钮时,Android App 主题没有改变
- jenkins - Jenkins 扩展电子邮件宏扩展失败(非法状态)
- windows - 使用 Power Shell 更改 Windows“列表分隔符”
- c - 这里有人使用赛普拉斯 PSoC6 吗?运行闪烁程序时遇到问题
- python - 如何以有效的方式提取遵循相同模式的特定数量和特定类型的字符串?
- python - 为什么在代表深度学习任务的棋盘时使用逻辑移位?
- scala - 大摇大摆如何定义 map[String, Boolean] 作为响应