我们的服务器应用程序在热代码路径中执行了许多整数测试,目前我们使用以下函数:
inline int IsInteger(double n)
{
return n-floor(n) < 1e-8
}
这个函数在我们的工作负载中非常繁忙,因此我希望它尽可能快。如果可以的话,我还想消除“floor”库调用。有什么建议吗?
我们的服务器应用程序在热代码路径中执行了许多整数测试,目前我们使用以下函数:
inline int IsInteger(double n)
{
return n-floor(n) < 1e-8
}
这个函数在我们的工作负载中非常繁忙,因此我希望它尽可能快。如果可以的话,我还想消除“floor”库调用。有什么建议吗?
以下是几个答案:
#include <stdint.h>
#include <stdio.h>
#include <math.h>
int IsInteger1(double n)
{
union
{
uint64_t i;
double d;
} u;
u.d = n;
int exponent = ((u.i >> 52) & 0x7FF) - 1023;
uint64_t mantissa = (u.i & 0x000FFFFFFFFFFFFFllu);
return n == 0.0 ||
exponent >= 52 ||
(exponent >= 0 && (mantissa << (12 + exponent)) == 0);
}
int IsInteger2(double n)
{
return n - (double)(int)n == 0.0;
}
int IsInteger3(double n)
{
return n - floor(n) == 0.0;
}
还有一个测试工具:
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
int IsInteger1(double);
int IsInteger2(double);
int IsInteger3(double);
#define TIMEIT(expr, N) \
gettimeofday(&start, NULL); \
for(i = 0; i < N; i++) \
{ \
expr; \
} \
gettimeofday(&end, NULL); \
printf("%s: %f\n", #expr, (end.tv_sec - start.tv_sec) + 0.000001 * (end.tv_usec - start.tv_usec))
int main(int argc, char **argv)
{
const int N = 100000000;
struct timeval start, end;
int i;
double d = strtod(argv[1], NULL);
printf("d=%lf %d %d %d\n", d, IsInteger(d), IsInteger2(d), IsInteger3(d));
TIMEIT((void)0, N);
TIMEIT(IsInteger1(d), N);
TIMEIT(IsInteger2(d), N);
TIMEIT(IsInteger3(d), N);
return 0;
}
编译方式:
gcc isinteger.c -O3 -c -o isinteger.o
gcc main.c isinteger.o -o isinteger
我的测试结果,基于英特尔双核处理器:
$ ./isinteger 12345
d=12345.000000 1 1 1
(void)0: 0.357215
IsInteger1(d): 2.017716
IsInteger2(d): 1.158590
IsInteger3(d): 2.746216
结论:位操作并没有像我猜测的那么快。多余的分支可能是它的致命伤,即使它避免了浮点运算。如今,FPU的速度足够快,执行double到int转换或floor操作并不会很慢。
结论:位操作并没有像我猜测的那么快。多余的分支可能是它的致命伤,即使它避免了浮点运算。如今,FPU的速度足够快,执行double到int转换或floor操作并不会很慢。
IsInteger2
中写测试条件为n == (double)(int)n
呢? - caf前段时间我进行了一系列的定时测试,以找到最有效的浮点数和整数之间的转换方式,并将其写成文章。我还测试了舍入技术。
简而言之,将浮点数转换为整数或使用联合技巧,由于CPU存在负载命中存储器的风险,不太可能有所改进 -- 除非这些浮点数来自RAM而不是寄存器。
因为它是一个内在的特性,abs(floor(x)-eps) 可能是最快的解决方案。但是,由于这非常敏感于您的CPU体系结构的特定架构 -- 取决于非常敏感的事情,如流水线深度和存储器转发 -- 您需要测试各种解决方案的时间,以找到真正最佳的解决方案。
double
符合IEEE-754标准,那么这个联合体描述了double
的布局。union
{
double d;
struct
{
int sign :1
int exponent :11
int mantissa :52
};
} double_breakdown;
bool is_integer( double d )
{
const int exponent_offset = 1023;
const int mantissa_bits = 52;
double_breakdown *db = &d;
// See if exponent is too large to hold a decimal value.
if ( db->exponent >= exponent_offset + mantissa_bits )
return true; // d can't represent non-integers
// See if exponent is too small to hold a magnitude greater than 1.0.
if ( db->exponent <= exponent_offset )
return false; // d can't represent integers
// Return whether any mantissa bits below the decimal point are set.
return ( db->mantissa << db->exponent - exponent_offset == 0 );
}
floor
和(int)n
掩盖了很多复杂性。 - benzadounion {
float f;
unsigned int i;
}
这将使您可以按位访问尾数和指数。然后,您可以使用位操作来解决问题。
另一种选择:
inline int IsInteger(double n)
{
double dummy;
return modf(n, &dummy) == 0.0;
}
round
而不是floor
。例如,fabs(n-round(n)) < 1e-8
。否则,你可以尝试使用modf
函数。 - Craig McQueen