c - n 段求根算法的性能

标签 c algorithm benchmarking numerical-methods

我编写了一个用于查找函数根的 n 部分算法。工作原理和二分法完全一样,只是把范围分成N等份。这是我的 C 代码:

/*
    Compile with: clang -O3 -o nsect nsect.c -Wall -DCOUNT=5000000 -DNSECT=? -funroll-loops
*/

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#define N 6

#ifndef COUNT
#error COUNT not defined!
#endif

#ifndef NSECT
#define NSECT 2
#endif

float polynomial[N];
float horner( const float poly[N], float x )
{
    float val = poly[N-1];
    for ( int i = N - 2; i >= 0; i-- )
        val = poly[i] + x * val;
    return val;
}

float f( float x )
{
    return horner( polynomial, x );
}

float nsect( float a, float b, const float eps_x )
{
    float fa = f( a );
    float fb = f( b );
    if ( fa == 0 ) return a;
    else if ( fb == 0 ) return b;
    else if ( fa * fb > 0 ) return 0;

    float x[NSECT];
    float fx[NSECT];

    while ( b - a > eps_x )
    {   
        x[0] = a;
        if ( ( fx[0] = f( x[0] ) ) == 0 ) return x[0];

        int found = 0;
        for ( int i = 0; i < NSECT - 1; i++ )
        {
            x[i + 1] = a + ( b - a ) * (float)( i + 1 ) / NSECT;
            if ( ( fx[i + 1] = f( x[i + 1] ) ) == 0 )
                return x[i + 1];
            else if ( fx[i] * fx[i + 1] < 0 )
            {
                a = x[i];
                b = x[i + 1];
                found = 1;
                break;
            }
        }

        if ( !found )
            a = x[NSECT - 1]; 

    }

    return ( a + b ) * 0.5f;
}

int main( int argc, char **argv )
{
    struct timeval t0, t1;
    float *polys = malloc( COUNT * sizeof( float ) * N );
    float *p = polys;
    for ( int i = 0; i < COUNT * N; i++ )
        scanf( "%f", p++ ); 

    float xsum = 0; // So the code isn't optimized when we don't print the roots
    p = polys;
    gettimeofday( &t0, NULL );
    for ( int i = 0; i < COUNT; i++, p += N )
    {
        memcpy( polynomial, p, N * sizeof( float ) );
        float x = nsect( -100, 100, 1e-3 );
        xsum += x;

        #ifdef PRINT_ROOTS
            fprintf( stderr, "%f\n", x );
        #endif
    }
    gettimeofday( &t1, NULL );

    fprintf( stderr, "xsum: %f\n", xsum );
    printf( "%f ms\n", ( ( t1.tv_sec - t0.tv_sec ) * 1e6 + ( t1.tv_usec - t0.tv_usec ) ) * 1e-3 );
    free( polys );
}

编辑:这是我用来编译代码的命令:clang -O3 -o nsect nsect.c -Wall -DCOUNT=5000000 -DNSECT=? -funroll-loops。 我在 i7-8700k 上运行了一切。

我决定测试不同 N 值的算法性能。该测试包括测量为 5,000,000 个 5 次多项式中的每一个找到范围 (-100;100) 中的任何根所需的时间。多项式是随机生成的,并且具有从 -5 到 5 的实数系数。多项式值是使用以下公式计算的霍纳法。

这些是我对每个 N(x=N,y=time [ms])运行代码 10 次得到的结果:

我对此处最坏情况性能的理解是,主 while 循环中要完成的工作量与 N 成正比。主循环需要 logN(C)(其中 C > 1 是一个常数 - 初始搜索范围与请求精度的比率)迭代完全的。这产生以下等式:

Equation

该图看起来与我在上面用来大致匹配数据的紫色曲线非常相似:

现在,我有一些(希望是正确的)结论和问题:

  • 首先,我的方法是否有效?
  • 我想出的函数是否真的描述了操作次数与N之间的关系?
  • 我认为这是最有趣的一个 - 我想知道是什么导致了 N=2 和所有其他人之间如此显着的差异?对于我的所有测试数据,我始终得到这种模式。

另外:

  • 该函数的最小值在 e≈2.718... 中,它更接近 3 而不是 2。此外,f(3) < f(2) 成立.鉴于我得出的方程式是正确的,我认为这意味着三等分法实际上可能比二等分法更有效。这似乎有点违反直觉,但测量结果似乎承认了这一点。这是对的吗?
  • 如果是这样,为什么与二分法相比,三分法似乎如此不受欢迎?

谢谢

最佳答案

我评论的是一般问题,而不是我不完全理解的特定代码。

假设在长度为 L 的区间内有一个已知的单根,并且所需的精度为 ε,您将需要 log(L/ε)/log(N) 个 segmentation 阶段。每个 segmentation 阶段都需要对函数进行 N-1 次评估(不是 N),以确定 N 中哪个子区间包含根。

因此,忽略开销,总成本与 (N-1)/log(N) 成正比。这个比率的值是,从 N=2 开始:

1.44, 1.82, 2.16, 2.49, 2.79...

及更高。

因此,理论最优值是 N=2。这就是不使用三等分的原因。

关于c - n 段求根算法的性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58033873/

相关文章:

无法将 char 复制到 char*(字符串)的最后地址?

c 按值传递数组?

C - 如何限制堆中的地址访问?

python - 我的分割序列代码有什么问题

使用映射值的 Java 比较器排序问题

c - 如何使用 C 语言中的文件编写凯撒密码加密代码

c# - 使用 List<T>.Sort 和 IEnumerable 的算法加速

websocket - 如何优化Tornado?

r - 分支预测如何影响 R 中的性能?

java - 图表示基准