Optimization 如何加速近似自然对数函数?

Optimization 如何加速近似自然对数函数?,optimization,rust,natural-logarithm,Optimization,Rust,Natural Logarithm,我实现了一个基于截断泰勒级数的Padé近似的近似自然对数函数。精度可以接受(±0.000025),但尽管进行了几轮优化,其执行时间仍然是标准库ln函数的2.5倍左右!如果它不是更快,也不是那么准确,那它就一文不值了!尽管如此,我还是用它来学习如何优化我的Rust代码。(我的计时来自于使用标准板条箱。我使用blackbox,对循环中的值求和,并根据结果创建一个字符串以击败优化器。) 在铁锈操场上,我的代码是: 算法 我的算法概述,该算法处理无符号整数的比率: 通过除以不超过以下值的两个最大幂,将

我实现了一个基于截断泰勒级数的Padé近似的近似自然对数函数。精度可以接受(±0.000025),但尽管进行了几轮优化,其执行时间仍然是标准库
ln
函数的2.5倍左右!如果它不是更快,也不是那么准确,那它就一文不值了!尽管如此,我还是用它来学习如何优化我的Rust代码。(我的计时来自于使用
标准
板条箱。我使用blackbox,对循环中的值求和,并根据结果创建一个字符串以击败优化器。)

在铁锈操场上,我的代码是:

算法 我的算法概述,该算法处理无符号整数的比率:

  • 通过除以不超过以下值的两个最大幂,将范围缩小到间隔[1,2]:
    • 分子的变化表示→ <代码>2ⁿ·N其中1≤ N≤ 2
    • 改变分母的表示→ <代码>2ᵈ·D其中1≤ D≤ 2
  • 这使得结果
    log(分子/分母)=log(2ⁿ·N/2ᵈ·D) =(n-D)·日志(2)+日志(n)-日志(D)
  • 为了计算对数(N),泰勒级数不在零附近收敛,但在一附近收敛
  • 。。。因为N接近于1,所以替换x=N-1,这样我们现在需要计算log(1+x)
  • 执行替换
    y=x/(2+x)
  • 考虑相关函数
    f(y)=Log((1+y)/(1-y))
    • =Log((1+x/(2+x))/(1-x/(2+x)))
    • =Log((2+2x)/2)
    • =Log(1+x)
  • f(y)具有泰勒展开式,其收敛速度必须快于对数(1+x)的展开式。。。
    • 对于对数(1+x)→ <代码>x-x²/2+x³/3-y⁴/4+…
    • 对于对数((1+y)/(1-y))→ <代码>y+y³/3+y⁵/5+…
  • 对截断序列
    y+y³/3+y使用Padé近似⁵/5…
  • 。。。这是
    2y·(15-4y²)/(15-9y²)
  • 对分母重复上述操作,然后合并结果
  • 帕德近似 以下是代码的Padé近似部分:

    
    ///近似一加一个范围(0..1)内的数字的自然对数。
    /// 
    ///对对数((1+y)/(1-y))的截断泰勒级数使用Padé近似。
    /// 
    ///-x-必须是介于0和1之间的值(包括0和1)。
    #[内联]
    fn log_1_plus_x(x:f64)->f64{
    //这是私有的,它的调用者已经检查了底片,所以不需要在这里再次检查。
    //此外,尽管ln(1+0)==0是一个简单的情况,但它不太可能是参数
    //与其他值不同,因此无需进行特殊测试。
    设y=x/(2.0+x);
    设y_平方=y*y;
    //原来的公式是:2y·(15-4y²)/(15-9y²)
    //2.0*y*(15.0-4.0*y_平方)/(15.0-9.0*y_平方)
    //减少乘法:(8/9)y·(3.75-y²)/(5/3-y²)
    0.888888888989*y*(3.75-y_平方)/(1.6666666667-y_平方)
    }
    
    显然,没有更多的加速

    最高有效位 到目前为止影响最大的变化是优化我的计算,以获得最高有效位的位置。我需要它来缩小射程

    以下是我的
    msb
    函数:

    
    ///为数值类型提供“msb”方法以获取基于零的
    ///最高有效位集的位置。
    /// 
    ///基于本文使用的算法:
    /// https://prismoskills.appspot.com/lessons/Bitwise_Operators/Find_position_of_MSB.jsp
    最重要的一点{
    ///获取整数类型的最高有效位的从零开始的位置。
    ///如果数字为零,则返回零。
    /// 
    ///##示例:
    /// 
    /// ```
    ///使用ClusterFebia::clustering::msb::MostSignificanBit;
    /// 
    ///断言!(0_u64.msb()==0);
    ///断言!(1_64.msb()==0);
    ///断言!(2_64.msb()==1);
    ///断言!(3_64.msb()==1);
    ///断言!(4_64.msb()==2);
    ///断言!(255_u64.msb()==7);
    ///断言!(1023_u64.msb()==9);
    /// ```
    fn msb(自我)->使用;
    }
    #[内联]
    ///返回是否为楼层(log2(x))=楼层(log2(y))
    ///零表示假,1表示真,因为这来自C!
    fn ld_neq(x:u64,y:u64)->u64{
    设neq=(x^y)>(x&y);
    如果neq{1}否则{0}
    }
    u64的impl MOSTSIMGNIFICANTBIT{
    #[内联]
    fn msb(自身)->使用{
    /*
    //我替换的较慢代码:
    //二分法保证了O(logb)的性能,其中B是整数中的位数。
    让mut high=63_usize;
    让mut low=0_usize;
    而(高-低)>1
    {
    设mid=(高+低)/2;
    
    让mask_high=(1一次优化将时间缩短一半

    我重写了我的msb(最高有效位)函数,以使用库函数
    u64::leading_zeroes
    ,该函数在内部使用内部函数:

    fn msb(self)->usize{
    //第三次尝试
    设z=self.leading_zero();
    如果z==64{0}
    else{63-z as usize}
    }
    
    现在我的对数近似只比内在ln函数长6%,我不太可能做得更好


    经验教训:使用内置日志!

    一次优化可将时间缩短一半

    我重写了我的msb(最高有效位)函数,以使用库函数
    u64::leading_zeroes
    ,该函数在内部使用内部函数:

    fn msb(self)->usize{
    //第三次尝试
    设z=self.leading_zero();
    如果z==64{0}
    else{63-z as usize}
    }
    
    现在我的对数近似只比内在ln函数长6%,我不太可能做得更好