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

提出结论,给出论据(二)

阅读更多
相关链接:
提出结论,给出论据(一)

前一篇提到Main()方法里的变量j整个消失了。我是如何确定这一点的?

============================================================================

观察变量j的存在与否

回忆起原测试代码中变量i与j的关系:它们在每轮for循环中都保持一样的值。在合适的优化下,它们可以看成是同一个变量,因而就不用重复计算i与j的值,只要算其中一个就行。在前一篇中,我们看到for循环里只对一个变量做了累加,那么它到底只是i,还是同时表示了i与j?

为了确认这个问题,我们可以把变量j的初始值变得与i的不一样,那么它们的值就不一样,而无法用同一个变量表示。例如改成0xCAFEBABE:
using System;

namespace ConsoleApplication1 {
    class Program {
        static void Main( string[ ] args ) {
            long j = 0xCAFEBABE;
            Console.WriteLine( DateTime.Now.ToString( ) );
            for ( long i = 1; i < 10000000000; i++ ) {
                j = j + 1;
            }
            Console.WriteLine( DateTime.Now.ToString( ) );
        }
    }
}

由JIT生成的目标代码是:
00E70070 push        ebp
00E70071 mov         ebp,esp
00E70073 push        edi
00E70074 push        esi
00E70075 sub         esp,20h
00E70078 mov         esi,ecx
00E7007A lea         edi,[ebp-28h]
00E7007D mov         ecx,8
00E70082 xor         eax,eax
00E70084 rep stos    dword ptr es:[edi]
00E70086 mov         ecx,esi
00E70088 lea         edi,[ebp-20h]
00E7008B pxor        xmm0,xmm0
00E7008F movq        mmword ptr [edi],xmm0
00E70093 lea         ecx,[ebp-20h]
00E70096 call        792896D0
00E7009B call        792897B0
00E700A0 mov         ecx,eax
00E700A2 lea         eax,[ebp-20h]
00E700A5 sub         esp,8
00E700A8 movq        xmm0,mmword ptr [eax]
00E700AC movq        mmword ptr [esp],xmm0
00E700B1 lea         edx,[ebp-10h]
00E700B4 mov         eax,dword ptr [ecx]
00E700B6 call        dword ptr [eax+48h]
00E700B9 lea         eax,[ebp-10h]
00E700BC sub         esp,8
00E700BF movq        xmm0,mmword ptr [eax]
00E700C3 movq        mmword ptr [esp],xmm0
00E700C8 call        792DDBC0
00E700CD mov         edx,eax
00E700CF xor         ecx,ecx
00E700D1 call        792DDC30
00E700D6 mov         esi,eax
00E700D8 call        792ED2F0
00E700DD mov         ecx,eax
00E700DF mov         edx,esi
00E700E1 mov         eax,dword ptr [ecx]
00E700E3 call        dword ptr [eax+000000D8h]
00E700E9 mov         esi,1
00E700EE xor         edi,edi
00E700F0 add         esi,1
00E700F3 adc         edi,0
00E700F6 cmp         edi,2
00E700F9 jg          00E70105
00E700FB jl          00E700F0
00E700FD cmp         esi,540BE400h
00E70103 jb          00E700F0
00E70105 lea         edi,[ebp-28h]
00E70108 pxor        xmm0,xmm0
00E7010C movq        mmword ptr [edi],xmm0
00E70110 lea         ecx,[ebp-28h]
00E70113 call        792896D0
00E70118 call        792897B0
00E7011D mov         ecx,eax
00E7011F lea         eax,[ebp-28h]
00E70122 sub         esp,8
00E70125 movq        xmm0,mmword ptr [eax]
00E70129 movq        mmword ptr [esp],xmm0
00E7012E lea         edx,[ebp-18h]
00E70131 mov         eax,dword ptr [ecx]
00E70133 call        dword ptr [eax+48h]
00E70136 lea         eax,[ebp-18h]
00E70139 sub         esp,8
00E7013C movq        xmm0,mmword ptr [eax]
00E70140 movq        mmword ptr [esp],xmm0
00E70145 call        792DDBC0
00E7014A mov         edx,eax
00E7014C xor         ecx,ecx
00E7014E call        792DDC30
00E70153 mov         esi,eax
00E70155 call        792ED2F0
00E7015A mov         ecx,eax
00E7015C mov         edx,esi
00E7015E mov         eax,dword ptr [ecx]
00E70160 call        dword ptr [eax+000000D8h]
00E70166 lea         esp,[ebp-8]
00E70169 pop         esi
00E7016A pop         edi
00E7016B pop         ebp
00E7016C ret

与前一篇原测试代码生成出来的目标代码对比——两者一模一样。给变量j赋的初始值0xCAFEBABE并没有出现在目标代码中,很好的说明了变量j确实消失了。
变量j在赋值后并没有被用于其它运算(唯一的运算就是用于累加自身),它的值既然不会对程序的其它部分造成任何影响,就可以安全的被优化掉。简单的数据流分析就能发现这点。

============================================================================

观察变量j的存在的情况

既然我们知道了原测试代码在实际执行时,Main()中的变量j消失了,那有什么办法能把它留住呢?最简单的办法就是把这个变量输出出来,使变量j的值在运算后用于可见的副作用当中。在原测试代码的最后加一句Console.WriteLine(j);,如下:
using System;

namespace ConsoleApplication1 {
    class Program {
        static void Main( string[ ] args ) {
            long j = 1;
            Console.WriteLine( DateTime.Now.ToString( ) );
            for ( long i = 1; i < 10000000000; i++ ) {
                j = j + 1;
            }
            Console.WriteLine( DateTime.Now.ToString( ) );
            Console.WriteLine( j );
        }
    }
}

则由JIT生成的目标代码为:
//// 代码块1:方法头
00E70070 push        ebp     // 保存帧指针
00E70071 mov         ebp,esp // 设置新的帧指针
00E70073 push        edi     // 这两句保护EDI和ESI寄存器
00E70074 push        esi
00E70075 sub         esp,30h // 分配局部变量空间
00E70078 mov         esi,ecx
00E7007A lea         edi,[ebp-38h]
00E7007D mov         ecx,8
00E70082 xor         eax,eax
00E70084 rep stos    dword ptr es:[edi]
00E70086 mov         ecx,esi
//// 代码块1结束

//// 代码块2:Program.Main()的方法体

// 为变量j赋初始值
00E70088 mov         dword ptr [ebp-10h],1
00E7008F mov         dword ptr [ebp-0Ch],0

// 内联开始,System.DateTime.get_Now()
00E70096 lea         edi,[ebp-30h]
00E70099 pxor        xmm0,xmm0
00E7009D movq        mmword ptr [edi],xmm0
00E700A1 lea         ecx,[ebp-30h]
00E700A4 call        792896D0 (System.DateTime.get_UtcNow(), mdToken: 060002d2)
00E700A9 call        792897B0 (System.TimeZone.get_CurrentTimeZone(), mdToken: 06000942)
00E700AE mov         ecx,eax
00E700B0 lea         eax,[ebp-30h]
00E700B3 sub         esp,8
00E700B6 movq        xmm0,mmword ptr [eax]
00E700BA movq        mmword ptr [esp],xmm0
00E700BF lea         edx,[ebp-20h]
00E700C2 mov         eax,dword ptr [ecx]
00E700C4 call        dword ptr [eax+48h] (System.CurrentSystemTimeZone.ToLocalTime(System.DateTime), mdToken: 06000951)
// 内联结束,System.DateTime.get_Now()

// 内联开始,System.DateTime.ToString()
00E700C7 lea         eax,[ebp-20h]
00E700CA sub         esp,8
00E700CD movq        xmm0,mmword ptr [eax]
00E700D1 movq        mmword ptr [esp],xmm0
00E700D6 call        792DDBC0 (System.Globalization.DateTimeFormatInfo.get_CurrentInfo(), mdToken: 06002493)
00E700DB mov         edx,eax
00E700DD xor         ecx,ecx
00E700DF call        792DDC30 (System.DateTimeFormat.Format(System.DateTime, System.String, System.Globalization.DateTimeFormatInfo), mdToken: 06002408)
// 内联结束,System.DateTime.ToString()

// 内联开始,System.Console.WriteLine(System.String)
00E700E4 mov         esi,eax
00E700E6 call        792ED2F0 (System.Console.get_Out(), mdToken: 06000772)
00E700EB mov         ecx,eax
00E700ED mov         edx,esi
00E700EF mov         eax,dword ptr [ecx]
00E700F1 call        dword ptr [eax+000000D8h] (System.IO.TextWriter+SyncTextWriter.WriteLine(System.String), mdToken: 060036c5)
// 内联结束,System.Console.WriteLine(System.String)

//>> for循环初始段:对变量i赋初始值
00E700F7 mov         dword ptr [ebp-18h],1
00E700FE mov         dword ptr [ebp-14h],0
//>> for循环体:对变量j累加
00E70105 mov         eax,dword ptr [ebp-10h]
00E70108 mov         edx,dword ptr [ebp-0Ch]
00E7010B add         eax,1
00E7010E adc         edx,0
00E70111 mov         dword ptr [ebp-10h],eax
00E70114 mov         dword ptr [ebp-0Ch],edx
//>> for循环增量段:对变量i累加
00E70117 mov         eax,dword ptr [ebp-18h]
00E7011A mov         edx,dword ptr [ebp-14h]
00E7011D add         eax,1
00E70120 adc         edx,0
00E70123 mov         dword ptr [ebp-18h],eax
00E70126 mov         dword ptr [ebp-14h],edx
//>> for循环条件ver1:
00E70129 cmp         dword ptr [ebp-14h],2
00E7012D jg          00E7013A
00E7012F jl          00E70105
//>> for循环条件ver2:
00E70131 cmp         dword ptr [ebp-18h],540BE400h
00E70138 jb          00E70105
//>> for循环结束

// 内联开始,System.DateTime.get_Now()
00E7013A lea         edi,[ebp-38h]
00E7013D pxor        xmm0,xmm0
00E70141 movq        mmword ptr [edi],xmm0
00E70145 lea         ecx,[ebp-38h]
00E70148 call        792896D0 (System.DateTime.get_UtcNow(), mdToken: 060002d2)
00E7014D call        792897B0 (System.TimeZone.get_CurrentTimeZone(), mdToken: 06000942)
00E70152 mov         ecx,eax
00E70154 lea         eax,[ebp-38h]
00E70157 sub         esp,8
00E7015A movq        xmm0,mmword ptr [eax]
00E7015E movq        mmword ptr [esp],xmm0
00E70163 lea         edx,[ebp-28h]
00E70166 mov         eax,dword ptr [ecx]
00E70168 call        dword ptr [eax+48h] (System.CurrentSystemTimeZone.ToLocalTime(System.DateTime), mdToken: 06000951)
// 内联结束,System.DateTime.get_Now()

// 内联开始,System.DateTime.ToString()
00E7016B lea         eax,[ebp-28h]
00E7016E sub         esp,8
00E70171 movq        xmm0,mmword ptr [eax]
00E70175 movq        mmword ptr [esp],xmm0
00E7017A call        792DDBC0 (System.Globalization.DateTimeFormatInfo.get_CurrentInfo(), mdToken: 06002493)
00E7017F mov         edx,eax
00E70181 xor         ecx,ecx
00E70183 call        792DDC30 (System.DateTimeFormat.Format(System.DateTime, System.String, System.Globalization.DateTimeFormatInfo), mdToken: 06002408)
// 内联结束,System.DateTime.ToString()

// 内联开始,System.Console.WriteLine(System.String)
00E70188 mov         esi,eax
00E7018A call        792ED2F0 (System.Console.get_Out(), mdToken: 06000772)
00E7018F mov         ecx,eax
00E70191 mov         edx,esi
00E70193 mov         eax,dword ptr [ecx]
00E70195 call        dword ptr [eax+000000D8h] (System.IO.TextWriter+SyncTextWriter.WriteLine(System.String), mdToken: 060036c5)
// 内联结束,System.Console.WriteLine(System.String)

// 内联开始,System.Console.WriteLine(System.Int64)
00E7019B call        792ED2F0 (System.Console.get_Out(), mdToken: 06000772)
00E701A0 push        dword ptr [ebp-0Ch]
00E701A3 push        dword ptr [ebp-10h]
00E701A6 mov         ecx,eax
00E701A8 mov         eax,dword ptr [ecx]
00E701AA call        dword ptr [eax+000000C4h] (System.IO.TextWriter+SyncTextWriter.WriteLine(Int64), mdToken: 060036c1)
// 内联结束,System.Console.WriteLine(System.Int64)

//// 代码块2结束

//// 代码块3:方法尾
00E701B0 lea         esp,[ebp-8]
00E701B3 pop         esi
00E701B4 pop         edi
00E701B5 pop         ebp
00E701B6 ret
//// 代码块3结束

//// Program.Main()方法结束


这次我们可以清楚的看到变量j的存在。不仅变量j确实存在于栈上了,受迫于寄存器分配的压力,变量i也从原先直接分配在寄存器ESI和EDI中变为现在也分配在栈上。访问主内存比访问寄存器要慢很多。看看测试时间,会发现加了这么一行就使速度慢了很多,在我的机器上需要2分半钟左右。
就加了一行看似很无辜的代码而已,我们见证了micro-benchmark是如何容易受到各种因素的影响而导致测试结果发生巨大的差异,进而带来误导性的结论。

从这段代码我们可以看出,CLR 2.0对循环中的归纳变量相关的冗余删除做得并不彻底。本来变量i与j还是可以合为一体来计算的,但这里却对它们做了重复计算。这可能是CLR 2.0实现的不足,但更有可能的是采取更激进的优化需要更长的编译时间和更多的空间,而JIT的一个重要需求就是要“快”,不能为了产生高效的代码而占用太多时间,否则程序反而会很卡

============================================================================

观察涉及long的方法调用

顺带提个小细节。CLR 2.0中,大多数方法都是用类似__fastcall的calling convention来调用。这种calling convention规定头两个参数分别放在ECX与EDX中,其余参数与__stdcall一样通过栈来传递;CLR的JIT calling convention跟__fastcall不一样的地方在,前者是把剩余的参数从左向右压栈的,而后者是从右向左

但是留意到上面代码中System.Console.WriteLine(System.Int64)内联进来的代码。首先,这个方法的源码是类似这样的:
public static class Console {
    // ...
    public static TextWriter Out {
        get {
            // ...
        }
    }
    
    public static void WriteLine(long i) {
        Console.Out.WriteLine(i);
    }
    // ...
}

在Console.WriteLine(long)中调用了TextWriter.WriteLine(long)。后者是一个虚方法,意味着它实际的参数列表中第一个参数是一个隐藏的this。根据__fastcall的规定,this应该通过ECX传递,那么要输出的long就应该通过EDX传递了,是这样的吗?仔细看看JIT生成的目标代码:
// 内联开始,System.Console.WriteLine(System.Int64)
00E7019B call        792ED2F0 (System.Console.get_Out(), mdToken: 06000772)
00E701A0 push        dword ptr [ebp-0Ch]
00E701A3 push        dword ptr [ebp-10h]
00E701A6 mov         ecx,eax
00E701A8 mov         eax,dword ptr [ecx]
00E701AA call        dword ptr [eax+000000C4h] (System.IO.TextWriter+SyncTextWriter.WriteLine(Int64), mdToken: 060036c1)
// 内联结束,System.Console.WriteLine(System.Int64)

可以看到,this确实是通过ECX传递的,但要输出的long型数据却是分两次压到栈上传递,而不是通过EDX传递的。原因很简单:long超过了机器的字长,在EDX里放不下,自然只能从栈上走。__fastcall实际的规定是:
MSDN 写道
The first two DWORD or smaller arguments are passed in ECX and EDX registers; all other arguments are passed right to left.

变量j是long型的,是个QWORD,比DWORD大,所以属于“其余参数”,就从栈上传递了。

============================================================================

嗯,关于CLR 2.0与原测试代码的一些“facts”就先写到这里吧。以后要是有机会也可以补充上在64位平台上的相关facts。
前面都只是在关注CLR,下一篇将转到JVM的一边,看看Sun HotSpot VM的一些facts ^ ^
3
0
分享到:
评论
2 楼 RednaxelaFX 2009-07-23  
seen 写道
lz还在读书?不然哪来这么多时间分析并把分析结果写下来?
如果已经工作了,那我只有佩服的份了

刚毕业不过还没开始工作。之前还有点不太想马上找工作,不过……现实所迫,还是去找吧
是啊,如果是已经工作我还能这这些的话,我都要佩服我自己了 OTL
得到结果其实用不了多少时间,分析结果会消耗一些时间,写出来要耗的时间会是前面两段加起来的两倍。不然我不会有那么多东西还堆在草稿箱里 T T
1 楼 seen 2009-07-21  
lz还在读书?不然哪来这么多时间分析并把分析结果写下来?
如果已经工作了,那我只有佩服的份了

相关推荐

    2023年广工虚拟化与云计算课程报告

    一、报告题目 课程报告为开放式选题,个人可以参考...4、总结:简要地归纳课程报告的题目要求,主要论点和简明论据,并给出主要结论。 5、参考文献:至少要有6-8篇参考文献。在报告中,要在引用文献的地方进行标注。

    调研报告的写作要求及格式规范.doc

    在第一手材料中,筛选出最典型、最 能说明问题的材料,对其进行分析,从中揭示出事物的本质或找出事物的内在规律,得 出正确的结论,总结出有价值的东西,这是写调研报告时应特别注意的。 第三,用词力求准确,文风...

    数据分析报告的-7-个模块.docx

    所以,结论建议一定要简明扼要、抓住重点,得出的结论一定要严谨慎重、有理有据,给出的建议一定要合情合理、能落地执行,应该注意 3 个要点: (1)搞清楚要建议的对象; (2)符合业务的实际情况; (3)不要回避...

    144份毕业选题参考(包含音乐,生物,哲学,计算机,法律,社会学等)

     会计毕业论文要求有自己的见解,可以是在会计理论研究及会计实践等方面解决了前人长期没有发现的一些问题,提出了解决问题的新方法以及提出了具有科学依据的新观点或新的理论模型。  (三) 会计毕业论文——应用性...

    论文研究 - 考虑相对论动能的量子力学不完全性

    在提到过去发表的论文的同时,提出了更清晰的论据。 本文预测的氢原子能级几乎与玻尔理论值匹配。 很难通过实验区分两者。 但是,本文预测存在n = 0的能级,即使使用狄拉克的相对论量子力学也无法预测。 本文处理的...

    论文研究 - 从邪恶到论辩的角色发展辩护逻辑上不一致

    邪恶论证通常被认为是反对犹太-基督教完美神观念的最有力论据。 它指出,一个完全善良,全知全能的上帝不会容许世界上存在的邪恶程度。 本文描述了为什么有神论者对此论证最有力的回应,即众所周知的“灵魂创造神论...

    统一封面.doc

     〈2)论文正文:正文是论文的主体,正文应包括论点、论据、 论证过程和结论。主体部分包括以下内容:  a.提出-论点;  b.分析问题-论据和论证;  c.解决问题-论证与步骤;  d.结论。  6、一篇论文的参考文献...

    第11章-数据分析报告简介.pptx

    11.2.4 正文 正文是主体,是分析报告最主要的部分,正文部分必须准确阐明全部有关论据,包括问题的提出到最后的结论。 11.2.5 结论与建议 报告的结尾是对整个数据分析报告的综合与总结、深化与提高,是得出结论、...

    平台数据分析.doc

    能用数据表明的结论,就用数据来表明结论,数据是最可靠的论据 分析数据时,要表明数据的来源。好让读者知道,那里来的数据,可行度高嘛? 数据收集 APP相关数据 用户留存率 用户平均访问时长 订单数据 商家下单...

    闯入我自己的电脑:版权隐喻的较量-研究论文

    反对版权最大化观点的学者对隐喻问题进行了大量思考,特别是寻找可以挑战财产隐喻的隐喻等论据。 本文的结论是,在这方面还有更多工作要做。 作为对这次对话的渐进贡献,本文提出了其他论点,包括其他隐喻,以寻找...

    小组探讨关于 EMF 是否会导致儿童脑癌的正反争论

    提出的论点旨在帮助卫生服务部准备其最终风险评估,通过尊重和启发性审查得出结论磁场是或不可能是脑癌的原因的可能原因。 Bioelectromagnetics Supplement 5:S144±S149, 2001. ß 2001 Wiley-Liss, Inc. 关键词:...

    反托拉斯与规范大数据-研究论文

    许多人关注大数据是否甚至会提出反托拉斯问题,以及是否应根据反托拉斯法对大数据所造成的损害以及是否造成危害进行分析和补救。 然而,学术文献在政策辩论方面有些滞后,对现有学术著作的仔细检查显示,缺乏对该...

    通货膨胀、相对价格和名义刚性-研究论文

    第三,关于最优通胀率,数据中发现的长期右偏并没有反对价格稳定的论据,因为它似乎是优化价格制定者的内生React,当目标是零通胀率时就会消失。 这一结论与托宾 (1972) 的外生假设下行刚性的含义形成鲜明对比,后...

Global site tag (gtag.js) - Google Analytics