美文网首页
算法性能黑洞

算法性能黑洞

作者: Eadren | 来源:发表于2018-06-10 21:11 被阅读0次

在软件层面分析一个program/function性能效率的时候,我们都会用算法分析中O(n), Θ(n),Ω(n)分别来描述算法在最差情况,平均情况,最好情况时的执行效率。但是如果算法量级相差常数级别的情况下,例如,O(2n)和O(n),反而会出现O(2n)的实际执行时间比O(n)时间短。这是因为我们只做了算法层面的分析,但是源代码如何被编译器编译的,处理器怎么处理指令的完全没有考虑。尤其是现代处理器中超标量,乱序执行以及编译器的优化技术会使得一些在算法层面明显快的,反而没有好的性能。接下来我们用一个多项式求值的例子,先进行算法分析,再给出实验结果,然后从处理器层来简单地解释下原因。

任务

实现对degree为n的多项求值的函数。


degree为n的多项式

普通实现


double poly(double a[], double x, long degree)
{
    long i;
    double result = a[0];
    double xpwr = x;
    for (i = 1; i <= degree; i++) {
        result += a[i] * xpwr;
        xpwr = x * xpwr;
    }
    return result;
}

这个实现,循环内一共2次乘法,1次加法,一共degree次循环。算法复杂度O(2*degree*a + degree*b). 其中a为一次加法执行的时间,b为一次乘法执行的时间,并且我们有理由认为b > a;

Horner 法

对多项式进行改变


Horner法
double polyh(double a[], double x, long degree)
{
    long i;
    double result = a[degree];
    for (i = degree - 1; i >= 0; i--)
        result += a[i] + x * result;
    return result;
}

这个实现,循环内一共1次乘法,2次加法,一共degree次循环。算法复杂度O(degree*a + 2*degree*b). 显然乘法次数少了,比普通实现是要快的。

计时实验代码

/*
 * degree = 1e6
 * a[] =  1000以内随机double浮点数
 * x = 1234.3
 */
int main()
{
    const long degree = 1e6;
    int i;
    double a[degree+1];
    double x;
    clock_t start, end;

    srand((unsigned) time(NULL)); 
    for (i = 0; i < degree + 1; i++)
        a[i] = rand() / ( (double)RAND_MAX/1000.0 );
;
    x = 1234.3; 
   
    start = clock();
    poly(a, x, degree);
    end = clock();

    printf("method 1: %lf\n", (double)(end - start)/CLOCKS_PER_SEC);

    start = clock();
    polyh(a, x, degree);
    end = clock();

    printf("method 2: %lf\n", (double)(end - start)/CLOCKS_PER_SEC);

    return 0;
}
  • 实验平台
    OS:macOS10.13
    CPU:Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
  • 编译工具以及指令
    clang
    cc source.c # cc = clang 没有任何优化选项
  • 实验结果
method 1: 0.003716
method 2: 0.005242

我们可以看到,Horner法实现的算法明显优于普通实现,但是实际执行就是慢了。接下来我们从处理器层分析为什么慢了!

处理器层分析

首先我们之前假设了乘法比加法要慢,但是由于现代处理器的乱序执行,使得指令能够并行执行。数据与控制无关的指令,只要功能单元足够,就可以并行执行。由于普通版的实现,下一次迭代要等待上一次迭代的xpwr计算出来才能进行。因此如果用CPE表示性能,那么普通版的CPE=b(请注意, x * result 和 result += 可以分别通过指令并行和循环展开解决)。而Horner法实现的,循环内部也有数据相关,而且无法通过循环展开解决。(a[i] + 依赖于 x * result 的结果,所以得先乘法做完,再做加法)因此Horner法的CPE=a+b;

因此我们可以解释Horner法在当前处理器,机器,编译条件下为什么比普通实现慢了。当然,我们稍微改动下编译选项 ,实验结果截然不同。下面是不同编译选项下的实验结果

  • 实验结果
实现方法 -Og - O1 -O2 -O3
普通版本 0.000002 0.000002 0.000002 0.000003
Horner法 0.000000 0.000002 0.000002 0.000001

总结

软件层面的算法相差常数数量级的时候,并不代表着常数低的算法性能好。利用不同编译优化规则,甚至在不同处理器上运行,性能结果都有不同。

参考

Computer Systems: A Programmer's Perspective (3rd Edition)

相关文章

  • 算法性能黑洞

    在软件层面分析一个program/function性能效率的时候,我们都会用算法分析中O(n), Θ(n),Ω(n...

  • 从 Auto Layout 的布局算法谈性能

    从 Auto Layout 的布局算法谈性能 从 Auto Layout 的布局算法谈性能

  • Day2 Chapter5.4

    5.4 估计、偏差、方差(衡量学习算法的性能,通过分析影响性能的因素从而提高学习算法的性能) 1. 点估计: 作用...

  • Python 代码优化常见技巧

    ​改进算法,选择合适的数据结构 一个良好的算法能够对性能起到关键作用,因此性能改进的首要点是对算法的改进。在算法的...

  • sammary

    vue-diff算法 react 性能优化 diff算法 ,局部更新DOMshouldComponentUpdat...

  • 算法导论公开课笔记(一)算法分析与设计

    算法分析 算法分析是关于计算机程序性能和资源利用的理论研究;性能研究主要是学习如何让算法或者应用程序 运行的更快;...

  • java性能优化

    压测工具 基准性能数据 方面 代码算法 JVM gc算法 gc收集器

  • 1.4.15 ThreeSum

    来见证不同算法的性能差距吧吧:

  • 4.分类算法(scikit-learn 的 perceptron

    应用机器学习分类算法的五个步骤 选择特征 选择一个性能指标 选择一个分类器和一个优化算法 评价模型的性能 优化算法...

  • 第一章 算法基础——算法性能分析

    1.2 算法性能分析 算法复杂度是算法性能最基本的评价标准,由时间复杂度和空间复杂度组成,属于计算复杂性理论中的内...

网友评论

      本文标题:算法性能黑洞

      本文链接:https://www.haomeiwen.com/subject/hyjqeftx.html