`
brucejiang
  • 浏览: 31989 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

两个最容易被人忽略的基本代码优化技术

阅读更多

Dr. Dobb’s Blogger 的Walter Bright曾写了一篇博文《 Overlooked Essentials For Optimizing Code 》, 为我们总结了两个最容易被人忽略的基本代码优化技术。酷壳 个人网站版主陈皓 对本文进行了翻译 ,现转载于此,供大家学习。 全 文如下:

我编写程序至今有35年了,我做了很多关于程序执行速度方面优化的工(一个示例 ),我也看过其它人做的优化。我发现有两个最基本的优化技术总是被人所忽略。

注意,这两个技术并不是避免时机不成熟的优化。并不是把冒泡排序变成快速排序(算法优化)。也不是语言或是编译器的优化。也不是把 i*4写成i<<2 的优化。

这两个技术是:

使用 一个profiler。

查看程序执行时的汇编码。

使用这两个技术的人将会成功地写出运行快的代码,不会使用这两个技术的人则不行。下面让我为你细细道来。

使用一个 Profiler

我们知道,程序运行时的90%的时间是用在了10%的代码上。我发现这并不准确。一次又一次地,我发现,几乎所有的程序会在1%的代码上花了99% 的运行时间。但是,是哪个1%?一个好的Profiler可以告诉你这个答案。就算我们需要使用100个小时在这1%的代码上进行优化,也比使用100个 小时在其它99%的代码上优化产生的效益要高得多得多。

问题是什么?人们不用profiler?不是。我工作过的一个地方使用了一个华丽而奢侈的Profiler,但是自从购买这个Profiler后, 它的包装3年来还是那么的暂新。为什么人们不用?我真的不知道。有一次,我和我的同事去了一个负载过大的交易所,我同事坚持说他知道哪里是瓶颈,毕竟,他 是一个很有经验的专家。最终,我把我的Profiler在他的项目上运行了一下,我们发现那个瓶颈完全在一个意想不到的地方。

就像是赛车一样。团队是赢在传感器和日志上,这些东西提供了所有的一切。你可以调整一下赛车手的裤子以让其在比赛过程中更舒服,但是这不会让你赢得 比赛,也不会让你更有竞争力。如果你不知道你的速度上不去是因为引擎、排气装置、空体动力学、轮胎气压,或是赛车手,那么你将无法获胜。编程为什么会不同 呢?只要没有测量,你就永远无法进步。

这个世界上有太多可以使用的Profiler了。随便找一个你就可以看到你的函数的调用层次,调用的次数,以前每条代码的时间分解表(甚至可以到汇 编级)。我看过太多的程序员回避使用Profiler,而是把时间花在那些无用的,错误的方向上的“优化”,而被其竞争对手所羞辱。(译 者陈皓注 :使用Profiler时,重点需要关注:1)花时间多的函数以优化其算法,2)调用次数巨多的函数——如果一个函数每秒被调 用300K次,你只需要优化出0.001毫秒,那也是相当大的优化。这就是作者所谓的1%的代码占用了99%的CPU时间)

查看汇编代码

几年前,我有一个同事,Mary Bailey,她在华盛顿大学教矫正代数(remedial algebra),有一次,她在黑板上写下:

x + 3 = 5

然后问他的学生“求解x”,然后学生们不知道答案。于是她写下:

__ + 3 = 5

然后,再问学生“填空”,所有的学生都可以回答了。未知数x就像是一个有魔法的字母让大家都在想“x意味着代数,而我没有学过代数,所以我就不知道 这个怎么做”。

汇编程序就是编程世界的代数。如果某人问我“inline函数是否被编译器展开了?”或是问我“如果我写下i*4,编译器会把其优化为左移位操作 吗?”。这个时候,我都会建议他们看看编译器的汇编码。这样的回答是不是很粗暴和无用?通常,在我这样回答了提问者后,提问都通常都会说,对不起,我不知 道什么是汇编!甚至C++的专家都会这么回答。

汇编语言是最简单的编程语言了(就算是和C++相比也是这样的),如:

ADD ESI,x

就是(C风格的代码)

ESI += x;

而:

CALL foo

则是:

foo();

细节因为CPU的种类而不同,但这就是其如何工作的。有时候,我们甚至都不需要细节,只需要看看汇编码的长啥样,然后和源代码比一比,你就可以知道 汇编代码很多很多了。

那么,这又如何帮助代码优化?举个例子,我几年前认识一个程序员认为他应该去发现一个新的更快的算法。他有一个benchmark来证明这个算法, 并且其写了一篇非常漂亮的文章关于他的这个算法。但是,有人看了一下其原来算法以及新算法的汇编,发现了他的改进版本的算法允许其编译器把两个除法操作变 成了一个。这和算法真的没有什么关系。我们知道除法操作是一个很昂贵的操作,并且在其算法中,这俩个除法操作还在一个内嵌循环中,所以,他的改进版的算法 当然要快一些。但,只需要在原来的算法上做一点点小的改动——使用一个除法操作,那么其原来的算法将会和新的一样快。而他的新发现什么也不是。

下一个例子,一个D用户张贴了一个 benchmark 来显示 dmd (Digital Mars D 编译器)在整型算法上的很糟糕,而ldc (LLVM D 编译器) 就好很多了。对于这样的结果,其相当的有意见。我迅速地看了一下汇编,发现两个编译器编译出来相当的一致,并没有什么明显的东西要对2:1这么大的不同而 负责。但是我们看到有一个对long型整数的除法,这个除法调用了运行库。而这个库成为消耗时间的杀手,其它所有的加减法都没有速度上的影响。出乎意料 地,benchmark 和算法代码生成一点关系也没有,完全就是long型整数的除法的问题。这暴露了在dmd的运行库中的long型除法的实现很差。修正后就可以提高速度。所 以,这和编译器没有什么关系,但是如果不看汇编,你将无法发现这一切。

查看汇编代码经常会给你一些意想不到的东西让你知道为什么程序的性能是那样。一些意想不到的函数调用,预料不到的自傲,以及不应该存在的东西,等等 其实所有的一切。但也不需要成为一个汇编代码的黑客才能干的事。

结论

如果你觉得需要程序有更好的执行速度,那么,最基本的方法就是使用一个profiler和愿意去查看一下其汇编代码以找到程序的瓶颈。只有找到了程 序的瓶颈,此时才是真正在思考如何去改进的时候,比如思考一个更好的算法,使用更快的语言优化,等等。

常规的做法是制胜法宝是挑选一个最佳的算法而不是进行微优化。虽然这种做法是无可异议的,但是有两件事情是学校没有教给你而需要你重点注意的。第一 个也是最重要的,如果你优化的算法没没有参与到你程序性能中的算法,那么你优化他只是在浪费时间和精力,并且还转移了你的注意力让你错过了应该要去优化的 部分。第二点,算法的性能总和处理的数据密切相关的,就算是冒泡排序有那么多的笑柄,但是如果其处理的数据基本是排好序的,只有其中几个数据是未排序的, 那么冒泡排序也是所有排序算法里性能最好的。所以,担心没有使用好的算法而不去测量,只会浪费时间,无论是你的还是计算机的。

就好像赛车零件的订购速底是不会让你更靠进冠军(就算是你正确安装零件也不会),没有Profiler,你不会知道问题在哪里,不去看汇编,你可能 知道问题所在,但你往往不知道为什么。

原文链接:Overlooked Essentials For Optimizing Code

译文链接:http://coolshell.cn/articles/2967.html

分享到:
评论

相关推荐

    Oracle SQL高级编程(资深Oracle专家力作,OakTable团队推荐)--随书源代码

    他认为对于SQL的学习是永无止境的,相信每一个查询Oracle数据库的人都需要精通SQL语言,才能写出高效的查询。他参与本书的编写就是为了帮助别人实现这一目标。 目录 封面 -11 封底 -10 扉页 -9 版权 -8 版权声明 -7...

    基于改进EGO算法的黑箱函数全局最优化 (2015年)

    针对该算法的不足之处,提出了兼顾Kriging模型精度与模型寻优的迭代函数,并将改进后的EGO算法应用于五个检验函数及一个存货模型,从Kriging模型精度及优化结果两方面对改进前后的算法进行比较。结果表明,改进后的...

    煤矿隐患管理系统优化

    采用持续改进与优化的方法,研究了现有系统存在的几点不足:缺乏对隐患的标准化处理、忽略人的不安全行为以及缺少对隐患信息的统计分析.通过实现对隐患的标准化录入、对员工的不安全行为的管理以及对隐患数据库的动态...

    smo算法优化matlab代码-Spam-Classifier:垃圾邮件分类器

    smo算法优化matlab代码垃圾邮件分类器 该项目包括使用 MATLAB 机器学习工具箱开发的垃圾邮件分类应用程序。 它有助于在收件箱中的各种电子邮件中对垃圾邮件进行分类。 现在,大多数电子邮件服务都提供垃圾邮件过滤器...

    基于图神经网络强化学习解决车辆路径规划问题(完整代码+报告).zip

    目前的研究工作分为两个流派:一种是通过运筹学,另一种是深度学习。运筹学的方法是把 VRP 定义为数学优化问题,通过精确或启发式算法达到最优或者近似最优解,但是真实场景的数据量下需要花费的时间很多。而对于...

    混合粒子群算法+粒子群参数设置改进策略(matlab代码实现)

    1:在基本的粒子群中加入了遗传算法中交叉变异操作,通过交叉因子更新当前粒子群的空间位置,这个过程产生新的粒子群更加符合目标优化函数,得到了更好的适应度,进而提高了经典的粒子群算法的局部搜索能力;...

    eWOW64Ext v1.2 - 加载任意 32/64 模块|动态调用|64 位汇编|64 位进程读写

    模块原理:。wow64 是在 64 位操作系统上允许 32 位程序(比如易编译的程序)执行的模拟器子系统;...更新:极大优化了 X64Call 的代码,现在的通用调用性能损失几乎可忽略不计,实际上本模块的所有代码都是一句句汇

    picmi:单元格内粒子代码的标准输入格式

    但是,可以预期,就像两个人将为最常见的主题使用并理解相同的单词和语法一样,代码将为相同的定义和任务共享相同的语言。 例如,预期大多数PIC代码可以共享相同的语法来定义网格,常用的场求解器,一组粒子等。另...

    jquery插件使用方法大全

    看起来比其他两个框架的要多了一个#,好,看看下面的用法: 代码 $("div p"); // (1) $("div.container"); // (2) $("div #msg"); // (3) $("table a",context); // (4) 在prototype里看过这样的写法吗?第一行代码...

    KODExplorer 芒果云-资源管理器

    ie8以下基本上不做兼容处理。chrome支持文件夹拖拽上传。 [文件打开] office文件在线预览功能,服务器必须在公网(外部能访问该服务器) [忘记密码] 修改data/system/member.php 密码为明文的md5值 例如将admin密码重...

    C#微软培训资料

    18.2 在 C #代码中调用 C++和 VB 编写的组件 .240 18.3 版 本 控 制 .249 18.4 代 码 优 化 .252 18.5 小 结 .254 第五部分 附 录 .255 附录 A 关 键 字.255 附录 B 错 误 码.256 附录 C .Net 名字空间...

    你必须知道的495个C语言问题

    3.15 我要检查一个数是不是在另外两个数之间,为什么if(abc)不行? 3.16 为什么如下的代码不对?inta=1000,b=1000;longintc=a*b; 3.17 为什么下面的代码总是给出0?doubledegC,degF;degC=5.0/9*(degF-32); ...

    超级有影响力霸气的Java面试题大全文档

    抽象包括两个方面,一是过程抽象,二是数据抽象。 2.继承:  继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承...

    FairDARTS:公平的DARTS

    但是,仍有两个基本弱点尚未解决。 首先,我们观察到优化过程中众所周知的跳过连接聚合是由排他性竞争中的不公平优势引起的。 其次,将连续的建筑权重离散为一个热点表示时,存在不可忽略的不一致。 由于这两个原因...

    《你必须知道的495个C语言问题》

    3.15 我要检查一个数是不是在另外两个数之间,为什么if(a b c)不行? 40 3.16 为什么如下的代码不对?int a=1000, b=1000; long int c=a * b; 40 3.17 为什么下面的代码总是给出0?double degC, degF; degC= ...

    java 面试题 总结

    28、设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。 以下程序使用内部类实现线程,对j增减的时候没有考虑顺序问题。 public class ThreadTest1{ private int j; public static ...

Global site tag (gtag.js) - Google Analytics