"signbit"与乘法检测到的符号变化,并将其与零进行比较

Sign change detected by `signbit` vs multiplication and comparizon with zero

本文关键字:quot 比较 signbit 检测 符号 变化      更新时间:2023-10-16

我正在做一些实验,研究检测两个数字之间符号变化的方法。给定两个数字xy,比如double,我们想知道它们是否有不同的符号。我一直在模仿我通常看到的

x*y > 0

但这读起来并不完全像人们通常会做的那样。它读起来像是将两个数字相乘并检查结果的符号。然而,我们真正要做的是检查每个数字的符号,并根据通常的规则决定符号的变化。这与相似

signbit(x) ^ signbit(y)

我很好奇,看看必须将数字相乘是否会对表演产生一些影响。我本以为会有负面影响。

比较性能时,前者的计算速度更快。

我不明白为什么。是因为编译器能够用signbit(x) ^ signbit(y)的语义替换x*y > 0,即xy的符号位的xor吗?解释是什么?

注意:signbit(x) ^ signbit(y)不是要取代x*y,而是整个x*y > 0

使用的代码:(在Visual Studio中编译)

#include <iostream>
#include <math.h>
#include <string>
#include <chrono>
using namespace std;
using namespace std::chrono;
#define N 1000000
int main() {
double x, y;
cout << "x = ";
cin >> x;
cout << "y = ";
cin >> y;
bool answer;
high_resolution_clock::time_point t1 = high_resolution_clock::now();
for (auto i = 0; i < N; ++i) {
answer = signbit(x) ^ signbit(y);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
auto diffBit = std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count();

high_resolution_clock::time_point t3 = high_resolution_clock::now();
for (auto i = 0; i < N; ++i) {
answer = (x*y > 0);
}
high_resolution_clock::time_point t4 = high_resolution_clock::now();
auto diffMult = std::chrono::duration_cast<std::chrono::nanoseconds>(t4 - t3).count();

cout << "Bit function lasted   = " << diffBit << endl;
cout << "Multiplication lasted = " << diffMult << endl;
}

您的测试并没有达到您想象的效果。

简化以删除尽可能多的代码:

#include <math.h>
#include <string>
#include <chrono>
#include <utility>
#include <tuple>
using namespace std;
using namespace std::chrono;
#define N 1000000
std::pair<double, double> getxy();
template<class T> void out(T t);
int main() {
double x, y;
std::tie(x,y) = getxy();

bool answer;
high_resolution_clock::time_point t1 = high_resolution_clock::now();
for (auto i = 0; i < N; ++i) {
answer = signbit(x) ^ signbit(y);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
auto diffBit = std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count();

high_resolution_clock::time_point t3 = high_resolution_clock::now();
for (auto i = 0; i < N; ++i) {
answer = (x*y > 0);
}
high_resolution_clock::time_point t4 = high_resolution_clock::now();
auto diffMult = std::chrono::duration_cast<std::chrono::nanoseconds>(t4 - t3).count();

out(diffBit);
out(diffMult);
}

然后使用带有-O2的gcc5.3进行编译,生成以下汇编程序:

main:
pushq   %rbp
pushq   %rbx
subq    $8, %rsp
call    getxy()
call    std::chrono::_V2::system_clock::now()
movq    %rax, %rbx
movl    $1000000, %eax
.L2:
subl    $1, %eax
jne     .L2
call    std::chrono::_V2::system_clock::now()
subq    %rbx, %rax
movq    %rax, %rbp
call    std::chrono::_V2::system_clock::now()
movq    %rax, %rbx
call    std::chrono::_V2::system_clock::now()
subq    %rbx, %rax
movq    %rbp, %rdi
movq    %rax, %rbx
call    void out<long>(long)
movq    %rbx, %rdi
call    void out<long>(long)
addq    $8, %rsp
xorl    %eax, %eax
popq    %rbx
popq    %rbp
ret

请注意,所有的计算都被忽略了,因为它们没有实质性的副作用。

第二个循环被完全忽略了。

编译时是否进行了优化?在C++11中,signbit是一个重载函数,因此除非它是内联的,否则会调用函数调用开销。

在任何情况下,表达式(x*y>0)都将比signbit从双精度中提取符号的操作简单得多,因此即使signbit是内联的,它也会产生比(x*y>0)更多的代码。我一点也不惊讶,更简单的表达更有效!

要深入了解发生了什么,请查看为每个表达式生成的代码的汇编。