本文转载自http://sd.csdn.net/a/20100921/279732.html
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
分享到:
相关推荐
在实际的代码审查过程中,这两种方式可以结合使用,以便从宏观和微观两个层面全面把握代码逻辑。 二、代码优化的目的 代码优化的目的是为了让阅读者花费更少的时间理解更多的功能。这通常意味着需要减少代码的冗余...
在Java编程中,找出两个数组中的重复元素是一个常见的问题,特别是在数据处理和算法设计中...以上就是关于“java求两个数组中重复元素源代码”的详细解析,涵盖了数组操作、重复元素查找、代码实现及优化等方面的知识。
环外,或者使用静态类和单例模式。1.1.1.2 不要使用空析构函数...以上是.NET代码优化的一些关键点,涵盖语言特性、多线程、数据库交互、Web开发等多个方面。理解和应用这些原则有助于提升.NET应用程序的性能和稳定性。
忽略生成代码的格式化可以防止这些代码被意外改变,确保构建过程的稳定性和一致性。 在`描述`中提到的“开发案列优质学习资料资源工具与案例应用场景开发文档教程资料”,这表明提供的资源可能包含关于如何使用...
在这个场景中,我们将探讨如何利用VSCode、Python和批处理命令来批量比较两个工程的代码差异。 首先,让我们深入理解"VSCode批量代码比较"这个主题。VSCode内置了源代码管理工具,可以方便地查看单个文件的差异。...
当两个代码版本之间存在差异时,diffcount会通过比较它们的源代码文件,找出新增、删除或修改的代码行。它不仅计算总的代码行数变化,更重要的是,它能区分出那些实际包含逻辑改动而非仅仅格式调整或注释修改的行。...
在Java编程环境中,实现两个Word文档的比较是一项常见的任务,特别是在文档处理或自动化测试的场景中。本篇文章将深入探讨如何使用Java技术有效地完成这个任务,重点在于理解文档的结构、选择合适的库以及如何标记...
标题"PSO优化LSTM.zip"提到了两个主要概念:PSO(粒子群优化)和LSTM(长短期记忆网络)。这是一个关于如何使用粒子群优化算法来改进LSTM模型的资源包。粒子群优化是一种基于群体智能的全局优化算法,而LSTM是深度...
AST可以忽略代码的格式差异,关注于语义结构。通过比较两个AST的节点结构和连接关系,可以更准确地评估代码的逻辑相似性。 在实现C++代码相似度计算时,我们可能需要以下步骤: 1. **预处理**:去除注释、空格和换...
- 如果这条边连接的两个顶点不在同一个连通分量中,就将这条边加入集合。 - 否则,这条边会形成环路,所以忽略它。 4. 当集合中的边数等于图中顶点数减一时,最小生成树构建完成。 在实际应用中,Kruskal算法通常...
本话题主要探讨如何计算两个简单多边形(可以是凸多边形或凹多边形)的并集、交集和差集。 首先,我们要明确几个基本概念: 1. **简单多边形**:一个多边形不自相交,即它的边不交叉。简单多边形可以是凸的或凹的。...
该工具不仅支持多种编程语法,而且能够忽略注释,这一特性使得开发者在对比时更专注于代码逻辑本身,而非被注释内容所干扰。 首先,我们来详细了解一下"keil工程对比"。Keil是ARM公司的一款知名嵌入式系统开发工具...
同时,准备一个数据结构,如并查集(Disjoint Set),用于判断两个顶点是否属于同一棵树,以避免形成环路。 3. **遍历边**:从最小的边开始,按照排序顺序依次考虑每条边。对于每条边(e),检查其连接的两个顶点是否...
首先,文本对比的基本目标是识别两个文本之间的异同,这在版本控制、文档编辑、代码审查等场景中非常有用。在C#中,我们可以使用内置的`string`类的一些方法,如`Equals()`、`Compare()`或`IndexOf()`,但这些方法...
例如,宏展开可能导致单个源代码行在编译后变成多行,而条件编译可能导致某些代码块在特定条件下被忽略。 5. **源代码结构**:C++源代码通常包含头文件(.h)和源文件(.cpp)。头文件通常包含函数声明和类定义,而...
这类软件主要用于比较两个代码文件或代码库之间的差异,帮助用户找出它们之间的异同,从而进行有效的代码审查、合并冲突或者查找错误。在编程工作中,尤其是在团队协作中,代码对比软件扮演着至关重要的角色。 首先...
代码对比工具是软件开发过程中不可或缺的辅助工具,主要用于查看和分析两个或多个代码文件或代码库之间的差异。这些工具能够帮助开发者找出代码间的细微差别,从而进行版本控制、合并冲突、修复bug或优化代码。本篇...
这个教程将详细讲解如何在Java中测试两个字符串的特定区域是否相等,并提供相关的源代码实例。 首先,让我们了解Java中字符串的基本比较方法。`equals()`和`compareTo()`是最常见的两种方式。`equals()`方法用于...
1. **大小写敏感**:JavaScript是一种大小写敏感的语言,这意味着"variable"和"Variable"会被视为两个不同的变量,所以在命名变量、函数或类时,要保持一致的大小写风格。 2. **格式和缩进**:虽然JavaScript代码对...
代码比对工具是软件开发过程中不可或缺的辅助工具,主要用于比较两个代码文件或代码库之间的差异,帮助开发者识别出代码的变化、冲突以及不一致之处。在Java开发领域,有一款名为"WinMerge"的代码比对工具,它以其...