终于谈到这个话题了,首先声明我不是汇编优化的高手,甚至于我知道的所有关于汇编优化的内容,仅仅来自于学校的课程、书本及当年做过的一些简单练习。换句话说,我了解的东西只能算是一些原则,甚至也有一些“陈旧”了——不过我想既然是一些原则性的东西,还是能够用它来做一定程度的判断。至少我认为,我在博客园里看到的许多关于“汇编优化”也好,“内嵌汇编”也罢的说法,经常是有些问题的。

说到汇编优化,自然被人想到“高性能”。似乎用.NET或Java平台上的程序性能一定不佳,性能好的程序一定要用C++——不,至少一定要用C来写。为什么呢?因为一个“常识”:便是“封装”会损失性能。性能最高的是“机器码”,因为CPU直接执行机器吗;“汇编”作为机器码的直接对应产物性能自然是一致的;C语言对于汇编/机器码几乎没有任何封装,因此性能也很好;而到了C++语言时,性能就要比C慢一些了——不过,这个看法正确吗?

其实我最近这几篇文章谈的都是与程序性能,尤其是代码执行效率有关的话题。在上一篇文章里,我们可以知道即便是在使用汇编编写代码,同样绕不开“CPU缓存”这部分与计算机体系结构相关的内容,而它对程序性能的影响甚至远远超过几句指令本身。事实上这也只是一小个方面而已,我们平时在谈性能相关问题时,总是在做很多假设,例如我们会假设不同指令的执行速度是一样的,各级别存储的读取性能也是相同的,但这都只是一个“理想环境”,和“事实”有很大差距。而进行汇编级别的优化,往往也是在利用“事实”进行细枝末节的调整。

例如,假设编译器只是对代码做“直接翻译”的话,您认为以下两种做法性能哪个比较好?

int sum = 0;
for (int i = 0; i < 100; i++)
{sum += array[i];
}
int sum1 = 0, sum2 = 0;
for (int i = 0; i < 100; i += 2)
{sum1 += array[i];sum2 += array[i + 1];
}int sum = sum1 + sum2;

从算法上看,两者完全相同,但是对于CPU来说,后一种做法比前一种做法性能要高。首先,第二段代码与前者相比,一个循环内部有两个完全不相关的加法运算,这样CPU便有机会将他们并行地执行,于是性能便会更好一些。其次,第二种做法的条件跳转次数少,一般来说性能就会更好一些。因为条件跳转直到最后一刻才知道要跳向何方,因此CPU流水线就很难对代码的走向进行预测了。当然,现在CPU设计已经引入了分支预测技术,如果预测成功,效率自然较高,但如果预测失败,那么便会有比较严重的损失了。因此,有时候“我们”会尽可能想办法去减少条件跳转的次数。

例如,求一个有符号32位整数的绝对值,按照我们普通的逻辑,它应该是这样的:

if (eax < 0) eax = -eax

这显然是一个条件跳转,但是它的汇编实现也完全可以是:

cdq           // 扩展eax的符号位到edx中,如果eax是正数则edx为0否则edx为0xffffffff
xor eax, edx  // 如果eax为负数,就把所有的位取反,否则不变
sub eax, edx  // 如果最开始eax为负数,则把这个数字取反加一

这样,原本的条件跳转消失了,但是我们使用顺序的汇编指令得到了正确的结果。

这样看来,内嵌汇编对于性能多么关键啊。但是,我们真需要亲自动手实现这些吗?无论是前面的“循环展开”还是后面的“取绝对值”都是机械的汇编级别的优化,这些正是编译器最(包括运行时里的JIT)擅长的优化手段了。如果我们想要代替编译器去做这些事情,基本上唯一的结果只是“丑陋的代码”而难以有性能的提高。

编译器其实是提高代码执行效率的重要工具,例如之前在谈这个话题的时候,有人谈到OCaml的性能比C/C++要高,这便是因为它的编译器并不需要像C/C++编译器那样作出最坏的打算——例如C/C++很多时候无法检测出两个变量之间的关系,因此只能按部就班地执行。同样,我们为什么说C语言中strlen()不应该放在循环内部,因为它会造成重复计算?因为C语言编译器不能假设在循环过程中strlen的返回值永远不变,因此它无法自动将其提取到循环外部,只能一遍遍地执行。

因此很多时候,我们在这方面必须为编译器做点什么。例如,一个关于处理器的“常识”便是,不管是整数还是浮点数,除法操作都比乘法要慢上许多,因此我们需要尽可能消除一些除法,例如在进行图片缩放的时候,我们需要确定缩放的依据是“宽”还是“高”,因此我们可能就会写这样的代码:

if (desiredWidth / originalWidth < desiredHeight / originalHeight)

事实上,如果您要在性能上作精细地追求,则这样是更好的做法:

if (desiredWidth * originalHeight < desiredHeight * originalWidth)

可惜的是,编译器可能无法为我们自动作这样的优化:我们的这些变量都是32为“有符号”整数,因此originalWidth可能会是负数。虽然我们知道图片的尺寸一定大于零,但是我们却没有办法把这些信息告诉编译器,因此编译器只能做最保守的计算了。

看到这里您可能会说,这些是在谈汇编优化吗?好像还是一直再说高级代码啊。没错,因为正向刚才所说那样,我不其实并不了解多少汇编优化的内容,我也只能说一些“大道理”。如果您对这方面有些“兴趣”的话,云风的《游戏之旅——我的编程感悟》一书似乎值得您一看(其实这篇文章的许多说法,都和这本书有密切关系)。在这本书里,云风总结他在多年游戏开发中总结到的经验,其中有相当部分便是汇编优化方面的内容。其中也讨论了许多其他方面的问题,如文章开始我提到的C++和C语言的性能高低,他认为C++的性能其实与C语言相比有过之而无不及,如果您在C语言里实现C++的特性(如多态)则几乎无法作的如C++一样好,而反过来,如果在C++中做C语言写过程式的代码,其性能往往会比C语言来的好。为什么?语言特性与编译器的威力呗。

如今的处理器,它的的优化手段已经非常高级,远不是在加快时钟频率上那么简单。这给了程序员手动进行汇编优化的动力,因为此时可能只要交换两条指令的顺序便可以有很明显的性能提高,而编译器的力量已经不足以作更细致的优化了。同时,CPU设计上的进步也在敦促程序员要不断更新自己的知识,因为可能在旧CPU上常用的优化方式,到了新的CPU上就不是那么明显了。例如《游戏之旅》就用了“不小”的篇幅“简单”描述了从Pentium到Pentium IV上渐进的优化方式。

当然,我并不赞同以性能为尊的程序编写方式,事实上汇编优化远比编写高级代码更可能遇到麻烦。云风在书上也强调,不要过于信任自己的汇编书写能力,即便像他这样有丰富经验的高手也遇到过不少令人大跌眼镜的事情。

更新:希望您在看了这篇文章以后,也可以关注一下下面的评论,不乏精妙说法。

相关文章

  • 浅谈代码的执行效率(1):算法是关键
  • 浅谈代码的执行效率(2):编译器的威力
  • 浅谈代码的执行效率(3):缓存与局部性
  • 浅谈代码的执行效率(4):汇编优化

浅谈代码的执行效率(4):汇编优化相关推荐

  1. 浅谈代码的执行效率(3):缓存与局部性

    在前两篇文章里,我们讨论了程序性能的两个方面,一是算法(广义的算法,即解决问题的方法),二是编译器.通过这两个方面,我想表达的意思是,一段程序的执行效率,是很难从表面现象得出结论的,至少从一些简单的层 ...

  2. 浅谈代码的执行效率(2):编译器的威力

    在上一篇文章中,我主要表达了这样一个观点:影响程序效率的关键之一是算法,而算法的选择与优化,和是否多一个赋值少一个判断的关系不大.关于算法的选择,我谈到其理论上的复杂度,并不直接反映出效率.因为在实际 ...

  3. 浅谈代码的执行效率(2):编译器的威力 [摘自赵劼老师的博客]

    在上一篇文章中,我主要表达了这样一个观点:影响程序效率的关键之一是算法,而算法的选择与优化,和是否多一个赋值少一个判断的关系不大.关于算法的选择,我谈到其理论上的复杂度,并不直接反映出效率.因为在实际 ...

  4. 浅谈代码的执行效率(1):算法是关键

    前一段时间在博客园里看到这样一篇文章,那位兄弟谈到程序效率的关键是"简短".他说,"程序越简短,其可执行代码就越少,就越有效率",而在编写程序的时候," ...

  5. android onclick执行顺序,浅谈onTouch先执行,还是onClick执行(详解)

    有一个Button 按钮,要想为该按钮设置onClick事件和OnTouch事件 mTestButton.setOnClickListener(new View.OnClickListener() { ...

  6. 【黑帽大牛】浅谈SEO快排系统对网站排名优化真的有帮助吗?【精品】

    大家好,今天我们要跟大家探讨的问题是"[黑帽大牛]浅谈SEO快排系统对网站排名优化真的有帮助吗?".以下案例参考战神快排系统. 之前有太多的人私聊我:汉文黑帽大牛,我想问一下现在百 ...

  7. 运用计算机优化教学的方法,浅谈计算机基础课程教学模式的优化对策论文

    计算机的应用在中国越来越普遍,改革开放以后,中国计算机用户的数量不断攀升,应用水平不断提高,特别是互联网.通信.多媒体等领域的应用取得了不错的成绩.1996年至2009 年,计算机用户数量从原来的63 ...

  8. 浅谈提升C#正则表达式效率

     摘要:说到C#的Regex,谈到最多的应该就是RegexOptions.Compiled这个东西,传说中在匹配速度方面,RegexOptions.Compiled是可以提升匹配速度的,但在启动速度上 ...

  9. 掌握这35 个小细节,助你有效提升 Java 代码的执行效率!

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 作者:萌小Q 来源:https://www.cnblogs.com/Qian123/p/60 ...

最新文章

  1. 春节充电 | 送你10本机器学习和数据科学必读书(附PDF下载)
  2. Web应用程序信息收集工具wig
  3. 给WIN2003 IIS SQL服务器安全加固
  4. SVM推导过程及SMO详细求解过程(转载+自己笔记)
  5. 使用ODBC连接SQL Anywhere 5.0(asp)
  6. kafka maven没有下载_构建工具的进化:ant, maven, gradle
  7. 通过Okta的单点登录保护Spring Boot Web App的安全
  8. 技能的反面 - 魔方和模仿
  9. [Firefox] 方便实用的firefox 插件
  10. 基于 cm-11 源码编译模拟器
  11. STlink下载程序步骤
  12. windows定时截屏小工具
  13. 【语音处理】开始学习语音,从基本概念和应用讲起
  14. 微信开门,给你简单极致的开门体验!
  15. 传统广域网有什么特点?传统广域网面临哪些挑战?
  16. 如何去除实验数据中的毛刺
  17. Finger.01 - ESP8266模块STA模式调试
  18. 2016年开源巨献:百度71款开源项目
  19. 怎么改变ADS1.2的字体大小
  20. Python Requests:两个例子说明get和post方法+用谷歌浏览器查看网络请求

热门文章

  1. uni-app在iOS移动端页面上下滑动关闭(页面回弹问题,非刷新)
  2. 罗斯蒙特1056ph电极_ROSEMOUNT 罗斯蒙特 PH电极 0396R-10-2(0396R-10-2)
  3. dev gridcontrol summaryitem如何加条件_如何一次清洗1000根核磁管
  4. ​网页图表Highcharts实践教程之图表代码构成
  5. ubuntu下nextcloud性能和安全设置优化_Ubuntu linux 18.04安装图解及IPV6协议处理和优化...
  6. php 怎么写个定时自理器,教你编写更加稳定、可读性强的JavaScript代码的示例
  7. bat 取得服务列表_基于IDEA热部署更新服务器Tomcat类,服务器Tomcat热更新
  8. 汇编SF、CF、 OF 、ZF、 PF
  9. 存储服务器配置型号,存储服务器配置要求指什么
  10. MIT工程师利用人工大脑突触(忆阻器)设计了“脑芯片“,未来可用于便携式设备