什么比std::pow快?

What is faster than std::pow?

本文关键字:pow 什么 std      更新时间:2023-10-16

我的程序在std::pow(double,int)函数上花费了90%的CPU时间。准确性不是这里的主要关注点,所以我想知道是否有更快的替代方案。我想尝试的一件事是铸造浮动,执行操作,然后回到双(还没有尝试过);我担心这不是一种提高性能的可移植方式(大多数cpu不是本质上对双精度操作吗?)

欢呼

看起来像Martin Ankerl有一些关于这方面的文章,优化近似pow()在C/c++中是一个,它有两个快速版本,一个如下:

inline double fastPow(double a, double b) {
  union {
    double d;
    int x[2];
  } u = { a };
  u.x[1] = (int)(b * (u.x[1] - 1072632447) + 1072632447);
  u.x[0] = 0;
  return u.d;
}

依赖于通过联合的类型双关语,这是c++中未定义的行为,来自标准草案9.5 [class.union]:

在union中,任何时候最多只能有一个非静态数据成员处于活动状态,即at的值大多数非静态数据成员可以随时存储在联合中。[…]

但是包括GCC在内的大多数编译器都支持这个定义良好的行为:

从不同于最近写入的联合成员(称为"类型双关语")中读取的做法很常见。即使使用- strict-aliasing,只要通过联合类型

访问内存,也允许使用类型双关语。

但这并不普遍,正如本文所指出的,正如我在这里的回答中所指出的,使用memcpy应该生成相同的代码,并且不会调用未定义的行为。

他还链接到第二个针对Java、C/c++和c#的优化pow()近似。

第一篇文章还链接到他的微基准测试

根据您需要做的事情,在对数域中操作可能会起作用-也就是说,您将所有值替换为它们的对数;乘法变成加法,除法变成减法,取幂变成乘法。但是现在加法和减法变得昂贵并且有些容易出错。

你的整数有多大?它们在编译时是已知的吗?将x^2计算为x*x而不是pow(x,2)要好得多。注意:几乎所有pow()的整数次幂的应用都涉及到一些数的二次或三次幂(或者在负指数的情况下的乘法逆)。在这种情况下使用pow()是多余的。使用一个模板来处理这些小的整数幂,或者直接使用x*x

如果整数很小,但在编译时不知道,比如在-12和+12之间,乘法仍然会胜过pow(),并且不会失去精度。你不需要11次乘法来计算x^12。四个就行了。使用这一事实x ^ (2 n) = (x ^ n) ^ 2 x ^ (2 n + 1) = x * ((x ^ n) ^ 2)。例如,x^12等于((x*x*x)^2)^2。两次乘法计算x^3 (x*x*x),再一次乘法计算x^6,最后一次乘法计算x^12。

YES!非常快,如果你只需要'y'/'n'作为一个长/int,这允许你避免缓慢的FPU FSCALE函数。这是Agner Fog的x86手动优化版本,如果你只需要'y'/'n'作为INT的结果。为了速度/大小,我将其升级为__fastcall/__declspec(裸),使用ECX传递'n'(浮点数总是在32位msvc++的堆栈中传递),所以对我来说非常小的调整,这主要是Agner的工作。它是在MS Visual vc++ 2005 Express/Pro上测试/调试/编译的,所以应该可以在较新的版本中插入。相对于通用CRT的pow()函数,精度非常好。

extern double __fastcall fs_power(double x, long n);
// Raise 'x' to the power 'n' (INT-only) in ASM by the great Agner Fog!
__declspec(naked) double __fastcall fs_power(double x, long n) { __asm {
    MOV  EAX, ECX     ;// Move 'n' to eax
;// abs(n) is calculated by inverting all bits and adding 1 if n < 0:
    CDQ               ;// Get sign bit into all bits of edx
    XOR  EAX, EDX     ;// Invert bits if negative
    SUB  EAX, EDX     ;// Add 1 if negative. Now eax = abs(n)
    JZ   RETZERO      ;// End if n = 0
    FLD1              ;// ST(0) = 1.0 (FPU push1)
    FLD  QWORD PTR [ESP+4] ;// Load 'x' : ST(0) = 'x', ST(1) = 1.0 (FPU push2)
    JMP  L2           ;// Jump into loop
L1: ;// Top of loop
    FMUL ST(0), ST(0) ;// Square x
L2: ;// Loop entered here
    SHR  EAX, 1       ;// Get each bit of n into carry flag
    JNC  L1           ;// No carry. Skip multiplication, goto next
    FMUL ST(1), ST(0) ;// Multiply by x squared i times for bit # i
    JNZ  L1           ;// End of loop. Stop when nn = 0
    FSTP ST(0)        ;// Discard ST(0) (FPU Pop1)
    TEST EDX, EDX     ;// Test if 'n' was negative
    JNS  RETPOS       ;// Finish if 'n' was positive
    FLD1              ;// ST(0) = 1.0, ST(1) = x^abs(n)
    FDIVR             ;// Reciprocal
RETPOS:    ;// Finish, success!
    RET  4 ;//(FPU Pop2 occurs by compiler on assignment
RETZERO:
    FLDZ   ;// Ret 0.0, fail, if n was 0
    RET  4
}}