`
RednaxelaFX
  • 浏览: 3015531 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

一个简单循环的优化问题 (2007-09-20)

阅读更多
Alright,终于开始用JavaEye的空间,顺便把之前写的别的东西转过来吧~~

==============转载开始==============
(2007-09-20)

昨晚回到宿舍之后,有同学问了我一个简单循环的优化问题.问题是,为何编译器要进行下述优化:

将优化前的代码:
for (int i = 0; i < 100; i++)
    for (int j = 0; j < 20; j++)
        a[j] = a[j] + 1;


优化为:
for (int j = 0; j < 20; j++)
    for (int i = 0; i < 100; i++)
        a[j] = a[j] + 1;


这让我一下疑惑了.我平时留意的循环相关优化主要是运算强度减弱(如乘法转换为加减法),不变量外提,短循环展开等.但这次的问题却跟前面三种情况都没什么关系.想了好一会才突然醒悟过来,原来是内存访问的问题.
可以观察到,优化前的代码里,外层循环里的所有语句都在内层循环里,而且外层循环控制变量i没有参与最内层循环体的运算,这为优化提供了可能性,意味着可以调换内外循环的顺序而不影响执行结果.

让我们来看看优化前的代码大概是被如何执行的(伪代码):
  lea reg0, a ; reg0 ← a
  and reg1, 0 ; reg1 ← i

outer_loop:
  cmp reg1, 100.
  jge end_outer_loop
  and reg2, 0 ; reg2 ← j
inner_loop:
  cmp reg2, 20.
  jge end_inner_loop
  mov reg3, reg0[reg2] ; //关键语句1_1
  incr reg3
  mov reg0[reg2], reg3 ; //关键语句1_2
  incr reg2
  jmp inner_loop
end_inner_loop:
  incr reg1
  jmp outer_loop

end_outer_loop:
  ...


而优化后的代码大概是这样(伪代码,实际上可以优化得更彻底):
  lea reg0, a ; reg0 ← a
  and reg2, 0 ; reg2 ← j

outer_loop:
  cmp reg2, 20.
  jge end_outer_loop
  and reg1, 0 ; reg1 ← i
  mov reg3, reg0[reg2] ; //关键语句2_1
inner_loop:
  cmp reg1, 100.
  jge end_inner_loop
  incr reg3
  incr reg1
  jmp inner_loop
end_inner_loop:
  mov reg0[reg2], reg3 ; //关键语句2_2
  incr reg2
  jmp outer_loop

end_outer_loop:
  ...


可以看到优化前后的代码长度几乎是一样的.也就是说这个优化跟减小目标代码的大小没有关系.最大的区别在于内存访问的语句的位置与内/外层循环体的位置关系.优化前,内层循环体每次执行都要进行两次内存访问,循环中需要进行的内存访问次数约为100 * 20 * 2 = 4000次.而优化后,内存访问被外提到了外层循环中,内层循环里不再需要访问内存,所以循环中需要进行的内存访问次数约为20 * 2 = 40次.
由于内存访问远远慢于寄存器访问,即使根据局部性(locality)原则a数组的内容都预先读到了高速cache中还是要比直接只使用寄存器要慢.所以综合各种条件后尽量减少内存的访问次数是优化的一个方向.而这个题目就展示了这一点.

为什么没一眼就看出来呢...我真是的.明明在debug的时候见得那么多的东西了...

=============续篇=============
(2007-09-24)

让我们来看看what's in the real world.下面两幅图分别是一个C++代码片段和对应的编译后结果的对照图.可以看到,前文中"优化后"代码对应的汇编在功能和顺序上跟我预期的没多少差别,只是具体指令用得不一样...嘛,寄存器清零确实xor用得最多.另外,两个版本在实际循环前都有一大段mov指令,这些就是a[20]的初始化语句所展开得到的.真正需要关心的地方从地址0x00401068开始.这循环次数实在太少,即使高精度记时器也未必能满足比较这两份代码消耗时间的需求,所以我们还是用观察法来分析两段代码的区别.





前一版本的代码("未优化版")的反汇编结果中我们可以看到,内层循环体里第一句是一个add指令.这对应于a[j] += 1;,这句指令实际上做了从内存取值,运算,将结果保存回内存这几步,也就是说内存访问次数大致可以认为有两次.综合来说,这个双层循环中内存的访问次数大约是100 * 20 * 2 = 4000次,与前面分析的一样.
后一版本的代码("优化版")的反汇编结果与前文的分析基本吻合,就不重复了.双层循环中内存的访问次树大致是20 * 2 = 40次.

很好,既然两次的编译结果都跟前面分析得差不多,那还有什么问题呢? 问题就是这优化是"我"而不是"编译器"做的.也就是说出这道问题的人光顾着理论研究而没管现实世界中的状况;也可能是没把想表达的意思说清楚;也有可能是转述的人记错了题目.Anyway,清华研究生入学考试考这种题也有够无聊的.这题实际想考的或许是与内存相关,不过也很可能只是下面提到的优化建议.

---------------------------------------------------------------------

Code Complete, 2nd Edition里,Chapter 26, 26.2 Loops一节提到了手工优化循环代码的一些建议,其中与本题相关的是:
Putting the Busiest Loop on the Inside

When you have nested loops, think about which loop you want on the outside and which you want on the inside. Following is an example of a nested loop that can be improved:

Java Example of a Nested Loop That Can Be Improved

for ( column = 0; column < 100; column++ ) {
    for ( row = 0; row < 5; row++ ) {
        sum = sum + table[ row ][ column ];
    }
}


The key to improving the loop is that the outer loop executes much more often than the inner loop. Each time the loop executes, it has to initialize the loop index, increment it on each pass through the loop, and check it after each pass. The total number of loop executions is 100 for the outer loop and 100 * 5 = 500 for the inner loop, for a total of 600 iterations. By merely switching the inner and outer loops, you can change the total number of iterations to 5 for the outer loop and 5 * 100 = 500 for the inner loop, for a total of 505 iterations. Analytically, you'd expect to save about (600 - 505) / 600 = 16 percent by switching the loops. Here's the measured difference in performance:

Language / Straight Time / Code-Tuned Time / Time Savings
C++ 4.75 / 3.19 / 33%
Java 5.39 / 3.56 / 34%
PHP 4.16 / 3.65 / 12%
Python 3.48 / 3.33 / 4%

The results vary significantly, which shows once again that you have to measure the effect in your particular environment before you can be sure your optimization will help.


---------------------------------------------------------------------

所以问题的关键是什么? 我觉得是"See it for yourself."
分享到:
评论

相关推荐

    利用cvx 解决凸优化问题实例代码.rar_matlab 凸优化_凸优化_凸优化程序_凸优化问题_利用cvx 解决凸优化问题实例

    本人最近利用MATLAB在做仿真,其中涉及到求解凸优化问题,现发出来与大家共享代码程序,一起进步。

    python for循环优化

      最近工作中遇到一个难题,优化一个项目的计算时间。最初,建立项目时用户少,中间使用了for循环,还是嵌套的,共两层,项目整体运行一次耗时1-2个小时。最近,随着用户量增长,项目耗时达到6-7个小时。显然是不...

    IOI国家集训队论文集1999-2019

    骆 骥 -《由"汽车问题"浅谈深度搜索的一个方面——搜索对象与策略的重要性》 毛子青 -《动态规划算法的优化技巧》 俞 玮 -《基本动态规划问题的扩展》 张一飞 -《求N!的高精度算法》 ## 2002 戴德承 -《退...

    Advanced Bash-Scripting Guide <>

    循环的一个简单例子 10-2. 每个[list]元素带两个参数的for 循环 10-3. 文件信息:对包含在变量中的文件列表进行操作 10-4. 在for 循环中操作文件 10-5. 在for 循环中省略[list] 10-6. 使用命令替换来产生for 循环的...

    贺兰_电子钢琴 2.0.5(源代码)

    她非常小巧,只有一个可执行文件,不需要安装,功能非常强大,界面简洁、美观大方,完全免费,开放源代码。 『主要功能』 1、用电脑的声卡、键盘、鼠标来模拟电子钢琴支持鼠标、键盘操作,操作与声音同步,没有...

    js 优化次数过多的循环 考虑到性能问题

    但明显地,循环体很简单,没什么优化的余地。即使把循环体清空,提示仍然存在。于是,我得出了一个结论:在IE下,一旦循环次数超过了某个特定值,就会弹出停止脚本的提示。 原因找到了,该如何解决呢?我首先想到的...

    Android ViewPager实现无限循环(2.加入小圆点,优化自动和手动滑动冲突)

    Android ViewPager实现无限循环(2.加入小圆点,优化自动和手动滑动冲突) 上一篇内容只是简单的实现了viewpager的页面自动轮播,但有以下两个缺点: 1.还没有小圆点,用户看不出...此次将对以上两个问题进行解决。

    grub4dos-V0.4.6a-2017-02-04更新

    执行时可以不用输入扩展名,比如输入test如果当前路径下有一个test.g4b就会自动使用。 2013-10-17 1.修改代码支持新版HOTKEY。 2013-07-10 1.insmod现在支持长文件名(以前最多11个字符,现在没有限制). 2....

    Linux高级bash编程

    循环的一个简单例子 10-2. 每个[list]元素带两个参数的for循环 10-3. 文件信息:对包含在变量中的文件列表进行操作 10-4. 在for循环中操作文件 10-5. 在for循环中省略[list] 10-6. 使用命令替换来产生for循环的[list...

    贺兰_电子钢琴 2.0.4(简单发布)

    她非常小巧,只有一个可执行文件,不需要安装,功能非常强大,界面简洁、美观大方,完全免费,开放源代码。 『主要功能』 1、用电脑的声卡、键盘、鼠标来模拟电子钢琴支持鼠标、键盘操作,操作与声音同步,没有...

    贺兰_电子钢琴 2.0.3(简单发布)

    她非常小巧,只有一个可执行文件,不需要安装,功能非常强大,界面简洁、美观大方,完全免费,开放源代码。 『主要功能』 1、用电脑的声卡、键盘、鼠标来模拟电子钢琴支持鼠标、键盘操作,操作与声音同步,没有...

    优化无限循环滚动的ScrollView

    简单几句代码就可以集成无限滚动的ScrollView。 通过新的方式去实现无限滚动功能,模式采用类似... 摒弃了普通思路在didScroll这个代理里面去翻页,优化了翻页的算法,手动滑动过程中自动滑动和手动滑动不相互影响。

    贺兰_电子钢琴 2.0.2(简单发布)

    她非常小巧,只有一个可执行文件,不需要安装,功能非常强大,界面简洁、美观大方,完全免费,开放源代码。 『主要功能』 1、用电脑的声卡、键盘、鼠标来模拟电子钢琴支持鼠标、键盘操作,操作与声音同步,没有...

    英伟达CUDA C/C++加速和优化N体模拟器认证通过代码01-nbody.cu

    01-nbody.cu 包含一个简单而有效的 n-body 模拟器,适合用于在三维空间移动的物体。我们可通过向该应用程序传递一个命令行参数以影响系统中的物体数量。 以目前的仅用CPU的情况下,此应用程序大约需要5秒钟才能运行...

    1000个【易语言模块大全汇总批量下载】

    易语言~模块~批量~下载 2008-11-08 14:41 文件夹 文件夹 易语言模块大全 2005-10-21 15:30 14489 3100 易语言模块大全\24位转单色位图模块.ec 2007-01-18 07:00 7110 2339 易语言模块大全\69msn.ec ...2005-09-18 20:...

    易语言模块大全汇总批量下载

    易语言~模块~批量~下载 2008-11-08 14:41 文件夹 文件夹 易语言模块大全 2005-10-21 15:30 14489 3100 易语言模块大全\24位转单色位图模块.ec 2007-01-18 07:00 7110 2339 易语言模块大全\69msn.ec ...2005-09-18 20:...

    通过循环优化 JavaScript 程序

    对于提高 JavaScript 程序的性能这个问题,最简单同时也是很容易被忽视的方法就是学习如何正确编写高性能循环语句。本文将会帮你解决这个问题。 我们将看到 JavaScript 中主要的循环类型,以及如何针对它们进行高效...

    E语言1000模块

    2004-09-02 16:58 3875 1504 易语言模块大全\BoyChong-神2多方式取IP模块.ec 2005-10-21 23:30 85873 8419 易语言模块大全\BoyChong专用常用模块2.ec 2004-02-07 10:17 4396930 326963 易语言模块大全\Cool皮肤...

    ExtAspNet v2.2.1 (2009-4-1) 值得一看

    -修正了使用IFrameUrl的Tab在切换过程中会重复加载的问题,这是一个在v2.1.6引入的问题(feedback:eroach)。 -修正了启用AutoPostBack的Grid,其RowClick会覆盖LinkButtonField, HyperLinkField, CheckBoxField的...

    中高温烟气余热动力回收的复叠跨临界CO2动力循环热力学分析-论文

    重点分析了工质分流质量比、吸热压力对回热过程换热匹配特性以及循环性能的影响,并对简单循环、回热循环和复叠循环进行了优化对比。结果表明,复叠循环中,上下级循环的回热匹配是影响循环性能的重要因素,通过调节...

Global site tag (gtag.js) - Google Analytics