C++ 是否有一种无分支的方法可以快速找到两个双精度浮点值的最小值/最大值?
我有两个替身,C++ 是否有一种无分支的方法可以快速找到两个双精度浮点值的最小值/最大值?,c++,bit-manipulation,numerical-methods,C++,Bit Manipulation,Numerical Methods,我有两个替身,a和b,都在[0,1]中。由于性能原因,我希望a和b的最小/最大值不进行分支 鉴于a和b均为正值,且均小于1,是否有一种有效的方法来获取两者的最小值/最大值?理想情况下,我不需要分支。是的,有一种方法可以计算两个doubles的最大值或最小值,而不需要任何分支。这样做的C++代码看起来是这样的: #include <algorithm> double FindMinimum(double a, double b) { return std::min(a, b)
a
和b
,都在[0,1]中。由于性能原因,我希望a
和b
的最小/最大值不进行分支
鉴于
a
和b
均为正值,且均小于1,是否有一种有效的方法来获取两者的最小值/最大值?理想情况下,我不需要分支。是的,有一种方法可以计算两个double
s的最大值或最小值,而不需要任何分支。这样做的C++代码看起来是这样的:
#include <algorithm>
double FindMinimum(double a, double b)
{
return std::min(a, b);
}
double FindMaximum(double a, double b)
{
return std::max(a, b);
}
这是从所有面向x86的流行编译器中得到的。使用SSE2指令集,特别是minsd
/maxsd
指令,它无分支地计算两个双精度浮点值的最小值/最大值
所有64位x86处理器都支持;它是AMD64扩展所必需的。即使是大多数没有64位支持SSE2的x86处理器。它于2000年发行。要找到一个不支持SSE2的处理器,您需要走很长的路。但是如果你做了呢?即使在那里
fucomi
指令执行比较,设置标志,然后fcmovnbe
指令根据这些标志的值执行条件移动。这完全是无分支的,并且依赖于1995年Pentium Pro引入x86 ISA的指令,自Pentium II以来,所有x86芯片都支持这些指令
这里唯一不会生成无分支代码的编译器是MSVC,因为。相反,你会得到:
double FindMinimum(double, double) PROC
fld QWORD PTR [a]
fld QWORD PTR [b]
fcom st(1) ; compare "b" to "a"
fnstsw ax ; transfer FPU status word to AX register
test ah, 5 ; check C0 and C2 flags
jp Alt
fstp st(1) ; return "b"
ret
Alt:
fstp st(0) ; return "a"
ret
double FindMinimum(double, double) ENDP
double FindMaximum(double, double) PROC
fld QWORD PTR [b]
fld QWORD PTR [a]
fcom st(1) ; compare "b" to "a"
fnstsw ax ; transfer FPU status word to AX register
test ah, 5 ; check C0 and C2 flags
jp Alt
fstp st(0) ; return "b"
ret
Alt:
fstp st(1) ; return "a"
ret
double FindMaximum(double, double) ENDP
注意分支JP
指令(奇偶校验位设置时跳转)。FCOM
指令用于进行比较,这是基本x87 FPU指令集的一部分。不幸的是,这会在FPU状态字中设置标志,因此为了在这些标志上进行分支,需要提取这些标志。这就是FNSTSW
指令的目的,它将x87 FPU状态字存储到通用AX
寄存器(它也可以存储到内存,但…为什么?)。然后,代码测试相应的位,并相应地进行分支,以确保返回正确的值。除了分支之外,检索FPU状态字的速度也相对较慢。这就是为什么奔腾Pro引入了FCOM
指令
但是,通过使用位旋转操作来确定最小值/最大值,不太可能提高这些代码的速度。有两个基本原因:
FindMinimumOfTwoPositiveDoubles(double a, double b):
mov rax, QWORD PTR [a]
mov rdx, QWORD PTR [b]
sub rax, rdx ; subtract bitwise representation of the two values
shr rax, 63 ; isolate the sign bit to see if the result was negative
ret
FindMaximumOfTwoPositiveDoubles(double a, double b):
mov rax, QWORD PTR [b] ; \ reverse order of parameters
mov rdx, QWORD PTR [a] ; / for the SUB operation
sub rax, rdx
shr rax, 63
ret
或者,为了避免内联装配:
bool FindMinimumOfTwoPositiveDoubles(double a, double b)
{
static_assert(sizeof(a) == sizeof(uint64_t),
"A double must be the same size as a uint64_t for this bit manipulation to work.");
const uint64_t aBits = *(reinterpret_cast<uint64_t*>(&a));
const uint64_t bBits = *(reinterpret_cast<uint64_t*>(&b));
return ((aBits - bBits) >> ((sizeof(uint64_t) * CHAR_BIT) - 1));
}
bool FindMaximumOfTwoPositiveDoubles(double a, double b)
{
static_assert(sizeof(a) == sizeof(uint64_t),
"A double must be the same size as a uint64_t for this bit manipulation to work.");
const uint64_t aBits = *(reinterpret_cast<uint64_t*>(&a));
const uint64_t bBits = *(reinterpret_cast<uint64_t*>(&b));
return ((bBits - aBits) >> ((sizeof(uint64_t) * CHAR_BIT) - 1));
}
bool FindMinimumOfTwoPositiveDoubles(双a,双b)
{
静态断言(sizeof(a)=sizeof(uint64\u t),
“一个双精度计数器必须与uint64_t的大小相同,才能进行位操作。”);
const uint64_t aBits=*(重新解释类型(&a));
const uint64_t bBits=*(重新解释类型(&b));
返回((aBits-bBits)>>((sizeof(uint64\u t)*字符位)-1);
}
布尔查找两个正整数的最大值(双a,双b)
{
静态断言(sizeof(a)=sizeof(uint64\u t),
“一个双精度计数器必须与uint64_t的大小相同,才能进行位操作。”);
const uint64_t aBits=*(重新解释类型(&a));
const uint64_t bBits=*(重新解释类型(&b));
返回((bBits-aBits)>>((sizeof(uint64\u t)*字符位)-1));
}
请注意,此实现存在严重的警告。特别是,如果两个浮点值具有不同的符号,或者如果两个值都为负值,则会中断。如果两个值都为负值,则可以修改代码以翻转它们的符号,进行比较,然后返回相反的值。为了处理两个值具有不同符号的情况,可以添加代码来检查符号位
// ...
// Enforce two's-complement lexicographic ordering.
if (aBits < 0)
{
aBits = ((1 << ((sizeof(uint64_t) * CHAR_BIT) - 1)) - aBits);
}
if (bBits < 0)
{
bBits = ((1 << ((sizeof(uint64_t) * CHAR_BIT) - 1)) - bBits);
}
// ...
/。。。
//执行二的补码词典排序。
if(aBits<0)
{
aBits=((1)听起来有点像过早优化。除非你已经有了一些东西,经过测量,发现这是一个热门的sp
bool FindMinimumOfTwoPositiveDoubles(double a, double b)
{
static_assert(sizeof(a) == sizeof(uint64_t),
"A double must be the same size as a uint64_t for this bit manipulation to work.");
const uint64_t aBits = *(reinterpret_cast<uint64_t*>(&a));
const uint64_t bBits = *(reinterpret_cast<uint64_t*>(&b));
return ((aBits - bBits) >> ((sizeof(uint64_t) * CHAR_BIT) - 1));
}
bool FindMaximumOfTwoPositiveDoubles(double a, double b)
{
static_assert(sizeof(a) == sizeof(uint64_t),
"A double must be the same size as a uint64_t for this bit manipulation to work.");
const uint64_t aBits = *(reinterpret_cast<uint64_t*>(&a));
const uint64_t bBits = *(reinterpret_cast<uint64_t*>(&b));
return ((bBits - aBits) >> ((sizeof(uint64_t) * CHAR_BIT) - 1));
}
// ...
// Enforce two's-complement lexicographic ordering.
if (aBits < 0)
{
aBits = ((1 << ((sizeof(uint64_t) * CHAR_BIT) - 1)) - aBits);
}
if (bBits < 0)
{
bBits = ((1 << ((sizeof(uint64_t) * CHAR_BIT) - 1)) - bBits);
}
// ...