通过修改的exp替换最快的pow()。通过计算较低功率时的平方

时间:2013-09-22 15:13:04

标签: c++ math optimization pow

编辑:

目标:
通过从公共变量的功率计算中重新使用预先计算/缓存的功率,生成一种无处不在的方法,以获得优于内置pow(double, uint)的自定义幂函数。

已经做了什么:
我已经推出了这样一个函数,它比内置函数大约快40%,但这是一个强力的手动派生函数 - 我想要一种自动生成这样的电源功能块的方法任意uint权力。


的已知,

要获得最佳自定义pow(double, uint),您需要一些知识。对于这个问题,知识(澄清)是:

  1. 功率将是一个整数。
  2. 可以知道最大功率(N_MAX)。
  3. 可以(重新)使用的预先计算的功率是已知的 在编译时(例如在我的示例中r2r4r6)。
  4. 可以假设方形r2始终是一直计算的 其他预先计算的权力。

  5. 解决方案要求

    需要单独程序编写case查找表或预处理程序逻辑以生成此类表的最佳解决方案是可接受的,但是,使用手动生成(即强力导出)查找表的非最佳解决方案手头的权力将不被接受(正如我已经拥有的那样,并且在我的例子中表明......想法是远离这个)。


    可能的解决途径

    作为建议,您知道N_MAX以及一组预先计算的权限B(我的示例为B={2,4,6})。您可以在单独的程序中或在预处理器中生成Sq(Bi, x)< = N_MAX . You can use this to form a basis set A , which you then search somehow to determine the least number of terms that can be summed to produce an arbitrary exponent of n>> 1 , where n&lt的所有方格的表格; = N_MAX`(转移是由于我们通过检查LSB并乘以sqrt(r2)来处理奇数情况)。


    理论背景

    我相信下面的方法是正方形的修改版本:方形:

    http://en.wikipedia.org/wiki/Exponentiation_by_squaring

    ....利用了某些低阶幂已经必然预先计算的事实,因此它通过平方(我假设pow(double, int)使用)从一个香草指数中移出最佳乘法集。

    然而,通过使用存储的小功率中间体而不是简单的exp,可以节省大量成本。通过r2上的方块。


    理论绩效

    例如,对于一组对象n=14 ....在此方案exp中。由权力提供

    double r4 = Sq(r2), r14=Sq(r4)*r4*r2; //4 op.
    

    ...需要 4 FP乘法 .....但是使用r2r6我们有

    double r14=Sq(r6)*r2; //2 op.
    

    .... 2 FP乘法 ....换句话说,来自" dumb"用方块表示我修改过的exp。通过使用常见指数预处理的正方形,我在乘法方面减少了50%的计算成本...至少在考虑内存成本之前。

    真实表现

    使用我当前的方法(使用gcc -O3编译),我得到 35.1秒。来运行我的程序100万个周期,而不是(没有其他修改) 56.6 s 使用内置的int pow(double, int) ....几乎是理论上的加速。

    此时,您可能会想到如何在单条指令行上减少50%的乘法可以提供约40%的加速。但基本上这行代码每个周期被称为1,000+次,是迄今为止整个程序中评估最多/最昂贵的代码行。因此,该程序似乎对此块中的小优化/改进非常敏感。


    原始发布和示例代码

    我需要替换pow(double, int)函数,因为我已经计算了第6个幂项,并且保存了第2个,第4个幂中间体,所有这些都可以用来减少第二个pow调用中的乘法,使用相同的double基础。

    更具体地说,在我的c ++代码中,我有一个性能关键的计算代码片段,其中我将3D点之间的距离的倒数提高到6次幂和n次幂。 e.g:

    double distSq = CalcDist(p1,p2), r2 = a/distSq, r6 = r2 * r2 * r2;
    results += m*(pow(sqrt(r2), n) - r6);
    

    ma是与拟合方程相关的常数,n是任意幂。

    稍微高效的形式是:

    double distSq = CalcDist(p1,p2), r2 = a/distSq, r6 = r2 * r2 * r2;
    results += m*(pow(r2, n)*(n&0x1?sqrt(r2):1.0) - r6);
    

    但是,这也不是最佳选择。我发现明显更快的是使用自定义pow函数,该函数使用r2,r4和r6的倍数,我必须在第二个任期内计算它。

    e.g:

    double distSq = CalcDist(p1,p2), r2 = a/distSq, r4 = r2 * r2, r6 = r4 * r2;
    results += m*(POW(r2, r4, r6 n) - r6);
    

    功能内部:

    double POW(double r2, double r4, double r6, uint n)
    {
       double results = (n&0x1 : sqrt(r2) : 1.0);
       n >>= 1;
       switch (n)
       {
         case 1:
         ....
         case 12:
            Sq(Sq(r6));
    
       }
       return result;
    }
    

    好处是我的功能在初步测试中显得很快。坏消息是,它不是无处不在,而且很长,因为我需要从caseint左右的8次幂50语句(可能甚至是未来更高)。进一步每个案例我都要检查并尝试不同的组合来通过强力推导找到r2r4r6的组合产生最少的乘法

    有没有人对pow(double, int)替代品有一个更普遍的解决方案,它使用基数的预先计算的功率来减少必要的乘法次数,和/或有一个无处不在的理论来说明如何确定理想的组合产生任意n和一组预先计算的倍数的最小乘法??

1 个答案:

答案 0 :(得分:1)

这是一个类似DP的算法,它可以为给定的n和可用的幂x^i提供最小数量的乘法,以及通过回溯的最佳策略。对于每个可能的指数n,将一对(minimum number of multiplications to get here, type of multiplication that gets you there)关联到第二个数字的位置,只需编写i或特殊符号S进行平方。

你显然是从1 -> (0, /)开始。

鉴于n -> (m_n, Action_m),如果n+i ->小于可能先前计算的最小移动次数(m_n + 1, i),则将m_n + 1设置为n+i。同样,如果这比先前可能的解决方案更好,则设置2n -> (m_n + 1, S)

此算法大致为O(n_max * #available powers)提供最佳策略。我并不认为算法本身具有最佳效率,但使用“动态”确实没有任何意义。它只有在你有一个合理的n_max(100,在你的情况下,当然没问题)和存储策略的有效方法时才有用。

要考虑两个想法:

(1)在进行基准测试之前,我不相信它会通过平方显着提高标准exp的性能(当然,严重依赖于可用的功率)。

(2)此类策略的数值误差行为(以及平方展示)与pow(double, double)完全不同。