代码之家  ›  专栏  ›  技术社区  ›  EJG89

Visual Studio 2012中的立方根函数cbrt()

  •  4
  • EJG89  · 技术社区  · 11 年前

    我正在Visual Studio 2012 Professional(Windows)中用C/C++编写一个程序,包括使用 pow() 。我运行了探查器以了解为什么运行需要如此长的时间,我发现 功率() 是瓶颈。

    我已经改写了诸如

    pow(x,1.5) x*sqrt(x)

    pow(x,1.75) sqrt(x*x*x*sqrt(x))

    这显著提高了程序的速度。

    有几种力量是这样的 pow(x,1.0/3.0) 所以我寻找三次根函数 cbrt() 加快速度,但它似乎在Visual Studio中不可用,我很难想象,因此我的问题是:

    我在哪里可以找到 cbrt() Visual Studio 2012 Professional中的函数,如果没有,除了 功率(x,1.0/3.0) ?

    诚挚的问候,

    恩斯特·扬

    2 回复  |  直到 11 年前
        1
  •  5
  •   TypeIA    11 年前

    This site 探索了在C语言中高效计算立方体根的几种计算方法,并提供了一些可以下载的源代码。

    ( 编辑 :谷歌搜索“ fast cube root “推出了几款更具前景的热门产品。”

    立方体根是一个令人感兴趣的话题,因为它们被用于许多常见的公式中,而且Microsoft Visual Studio中没有快速的立方体根函数。

    在没有特殊的立方根函数的情况下,一种典型的策略是通过幂函数(例如,pow(x,1.0/3.0))进行计算。如果不能正确处理负数,这在速度和精度方面都会有问题。

    他的网站有一些使用方法的基准。所有这些都比 pow() .

    32-bit float tests
    ----------------------------------------
    cbrt_5f      8.8 ms    5 mbp   6.223 abp
    pow        144.5 ms   23 mbp  23.000 abp
    halley x 1  31.8 ms   15 mbp  18.961 abp
    halley x 2  59.0 ms   23 mbp  23.000 abp
    newton x 1  23.4 ms   10 mbp  12.525 abp
    newton x 2  48.9 ms   20 mbp  22.764 abp
    newton x 3  72.0 ms   23 mbp  23.000 abp
    newton x 4  89.6 ms   23 mbp  23.000 abp
    

    有关可下载源,请参阅网站。

        2
  •  0
  •   huseyin tugrul buyukisik    2 年前

    下面的实现比std::pow快4倍,在AVX-512 CPU上具有相对较高的容差(0.000001)。它由每个基本操作(如乘法和除法)的垂直自动矢量化循环组成,因此它一次计算8、16、32个元素,而不是水平矢量化Newton-Raphson循环。

    #include <cmath> 
    /* 
       Newton-Raphson iterative solution
       f_err(x) = x*x*x - N
       f'_err(x) = 3*x*x
       x = x - (x*x*x - N)/(3*x*x)
       x = x - (x - N/(x*x))/3   <--- repeat until error < tolerance
       but with vertical-parallelization 
    */
    template < typename Type, int Simd, int inverseTolerance> 
        inline 
    void cubeRootFast(Type *const __restrict__ data, 
            Type *const __restrict__ result) noexcept 
    { 
        // alignment 64 required for AVX512 vectorization
        alignas(64) 
        Type xd[Simd]; 
    
        alignas(64) 
        Type resultData[Simd]; 
    
        alignas(64) 
        Type xSqr[Simd]; 
    
        alignas(64) 
        Type nDivXsqr[Simd]; 
    
        alignas(64) 
        Type diff[Simd]; 
    
        // cube root checking mask
        for (int i = 0; i < Simd; i++) 
        { 
            xd[i] = data[i] <= Type(0.000001);
        } 
    
        // skips division by zero if input is zero or close to zero
        for (int i = 0; i < Simd; i++) 
        { 
            resultData[i] = xd[i] ? Type(1.0) : data[i]; 
        } 
    
        // Newton-Raphson Iterations in parallel 
        bool work = true; 
        while (work) 
        { 
            // compute x*x
            for (int i = 0; i < Simd; i++) 
            { 
                xSqr[i] = resultData[i] *resultData[i]; 
            } 
    
            // compute N/(x*x)
            for (int i = 0; i < Simd; i++) 
            { 
                nDivXsqr[i] = data[i] / xSqr[i]; 
            } 
    
            // compute x - N/(x*x)
            for (int i = 0; i < Simd; i++) 
            { 
                nDivXsqr[i] = resultData[i] - nDivXsqr[i]; 
            } 
    
            // compute (x-N/(x*x))/3
            for (int i = 0; i < Simd; i++) 
            { 
                nDivXsqr[i] = nDivXsqr[i] / Type(3.0); 
            } 
    
            // compute x - (x-N/(x*x))/3
            for (int i = 0; i < Simd; i++) 
            { 
                diff[i] = resultData[i] - nDivXsqr[i]; 
            } 
    
            // compute error
            for (int i = 0; i < Simd; i++) 
            { 
                diff[i] = resultData[i] - diff[i]; 
            } 
    
            // compute absolute error
            for (int i = 0; i < Simd; i++) 
            { 
                diff[i] = std::abs(diff[i]); 
            } 
    
            // compute condition to stop looping (error < tolerance)?
            for (int i = 0; i < Simd; i++) 
            { 
                diff[i] = diff[i] > Type(1.0/inverseTolerance); 
            } 
    
            // all SIMD lanes have to have zero work left to end
            Type check = 0; 
            for (int i = 0; i < Simd; i++) 
            { 
                check += diff[i]; 
            } 
            work = (check > Type(0.0)); 
    
            // compute the next x guess
            for (int i = 0; i < Simd; i++) 
            { 
                resultData[i] = resultData[i] - nDivXsqr[i]; 
            } 
        } 
    
        // if input was close to zero, output zero
        // output result otherwise
        for (int i = 0; i < Simd; i++) 
        { 
            result[i] = xd[i] ? Type(0.0) : resultData[i]; 
        } 
    } 
    #include <iostream> 
     
    int main() 
    { 
        constexpr int n = 8192; 
        constexpr int simd = 16; 
        constexpr int inverseTolerance = 1000;
        float data[n]; 
        for (int i = 0; i < n; i++) 
        { 
            data[i] = i; 
        } 
        for (int i = 0; i < n; i += simd) 
        { 
            cubeRootFast<float, simd, inverseTolerance> (data + i, data + i); 
        } 
        for (int i = 0; i < 10; i++) 
            std::cout << data[i *i *i] << std::endl; 
        return 0; 
    } 
    

    它只使用GCC进行测试,因此可能需要在每个循环上使用额外的MSVC pragma来强制自动矢量化。如果你有OpenMP,那么你也可以使用 #pragma omp simd safelen(Simd) 实现同样的目标。

    性能仅在[0,1]范围内。要使用更大的值,应使用以下范围缩减:

    // example: max value is 1000
    for(auto & input:inputs)
        input = input/1000.0f // normalize
    
    for(..)
        cubeRootFast<float, simd, inverseTolerance> (input + i, input + i)
    
     for(auto & input:inputs)
        input = 10.0f*input // de-normalize  (1000 = 10 x 10 x 10) 
    

    如果在16x加速的低范围(如[0100])上只需要0.005个误差,您可以尝试使用多项式近似的以下实现(Horner Scheme用于使用FMA指令进行计算,不需要显式自动矢量化,因为它不包含任何分支/循环):

    // optimized for [0,1] range: ~1 cycles on AVX512, 0.003 average error 
    // polynomial approximation with Horner Scheme for FMA optimization 
    template<typename T> 
    T cubeRootFast(T x) 
    { 
     T xd = x-T(1.0); 
     T result = T(-55913.0/4782969.0); 
     result *= xd; 
     result += T(21505.0/1594323.0);  
     result *= xd; 
     result += T(-935.0/59049.0);  
     result *= xd; 
     result += T(374.0/19683.0);  
     result *= xd; 
     result += T(-154.0/6561.0);  
     result *= xd; 
     result += T(22.0/729.0);  
     result *= xd; 
     result += T(-10.0/243.0); 
     result *= xd; 
     result += T(5.0/81.0); 
     result *= xd; 
     result += T(-1.0/9.0);  
     result *= xd; 
     result += T(1.0/3.0); 
     result *= xd; 
     result += T(1.0); 
     return result; 
    } 
     
    // range reduction + dereduction: ~ 1 cycles on AVX512 
     for(int i=0;i<8192;i++) 
     {  
         float inp = input[i]; 
         // scaling + descaling for range [1,999] 
         float scaling = (inp>333.0f)?(1000.0f):(333.0f); 
         scaling = (inp>103.0f)?scaling:(103.0f); 
         scaling = (inp>29.0f)?scaling:(29.0f); 
         scaling = (inp>7.0f)?scaling:(7.0f); 
         scaling = (inp>3.0f)?scaling:(3.0f); 
          
         output[i] = powf(scaling,0.33333333333f)*cubeRootFast<float>(inp/scaling);  
     }