`
RednaxelaFX
  • 浏览: 3052519 次
  • 性别: 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还在读书?不然哪来这么多时间分析并把分析结果写下来?
如果已经工作了,那我只有佩服的份了

相关推荐

    议论文写作事实论据的分析方法.docx

    针对这些问题,有效的解决方法是在提出事实论据之后,立即进行深入分析,即遵循“材料+分析+观点”的模式。 #### 四、具体分析方法 1. **归纳分析法**(揭示实质法): - 在列举了多个相似的事例之后,对其进行...

    一种基于对话的论据可接受性计算方法研究

    本文献提出了一种新的基于对话的论据可接受性计算方法,旨在通过模拟真实的对话过程来评估论据的有效性和可信度。这种方法的核心在于构建了一个能够动态调整论据权重的计算模型,以反映对话过程中论据的相互作用和...

    2022年高考写作议论文事实论据的分析技巧.docx

    ### 2022年高考写作议论文...在写作实践中,学生应注重论据与论点之间的内在联系,通过深入分析,使论据更加有力地支持论点,从而写出高质量的议论文。希望以上的分析方法和实例能够帮助大家更好地掌握议论文写作技巧。

    2攻坚-判断推理3赵睿(讲义+笔记).pdf

    如果题目中只给出论点或论据,考生需要根据所提供的信息推导出未给出的部分。比如,如果给出了论据,就需要考虑支持这一论据的最佳论点是什么;如果给出了论点,则需要想出与之相反的论点,再在选项中寻找对应的表述...

    论证有效性分析整理.doc

    论证有效性分析是一种批判性思维技能,它要求对给出的论证进行深入分析,判断其结论是否合理,论据是否充分。这种分析通常应用于论文、报告、计划书等文档中,以评估作者的推理过程是否可靠。以下是对论证有效性分析...

    江苏省大丰市2020届高三语文一轮复习 主体论证段的常式与变式学案(无答案).doc

    9. 文段类型判断:根据习作三的结构,其属于主体论证段的常式,因为它是先提出分论点,然后对“尊重生命”的含义进行解释,再给出论据(对生命的尊重的底线是不残害生命),最后进行议论,强调观点。 综上所述,...

    怎样写好议论文.doc

    议论文是一种以说服读者为目的的文体,旨在通过提出见解、主张并给出理由,让读者接受作者的观点。它的核心特点是其说服性。在构建一篇成功的议论文时,我们需要关注以下几个关键方面: 1. 论题:论题是讨论的核心...

    八年级语文下册第二单元复习教(学)案1.doc

    5. 议论文的结构:议论文通常遵循“提出问题(引论)—分析问题(本论)—解决问题(结论)”的三段式结构。 6. 议论文的语言特点:议论文的语言要求概括性强、情感色彩鲜明,用词准确、生动且严密。 7. 议论文与...

    一、英语议论文写作要点.doc

    结论段应给读者留下深刻的印象,有时还可以提出进一步的思考或建议。 以提供的例子分析,文章讨论了“Tutorial center is helpful”的主题。第一段明确提出论点,即家教中心是有帮助的。主体段分别列举了费用合理、...

    江苏省大丰市高三语文一轮复习主体论证段的常式与变式学案无答案 学案.doc

    这种变式先提出观点,然后进行分析,接着引入论据进一步论证,再进行深入的讨论,最后得出结论,使得论证更具有说服力。 3. 分论点的设置: 分论点是论证段的基石,它应该简洁明了,准确反映段落论述的主要内容。...

    中考议论文复习要点归纳.doc

    总的来说,中考议论文复习应注重理解议论文的基本结构,掌握提出论点、组织论据和进行论证的方法,同时关注语言的准确性和表达的严密性。通过反复练习和深入理解,学生可以在考试中更好地展示自己的分析能力和表达...

    四级英语作文模板.doc

    - 其次,描述另一部分人的观点二,同样给出原因一和原因二。 - 最后,作者应表达自己的立场,选择支持的观点一或二,并给出自己的理由。 2. **比照选择型** 在这种类型的作文中,作者需要比较两个不同的观点或...

    2攻坚-判断推理3谢笑(讲义笔记)(2021文职招考系统班:2期).pdf

    1. 削弱之否定论点:此类题目只给出论点,要求考生找出与其相反的观点。解题步骤包括找出论点,想出与之相反的意思表述,然后在选项中找到对应的选项。例如,若论点是“不应该支持民间反扒”,则能反驳该结论的选项...

    议论文导学案[教师版].doc

    中心论点可能由文章标题、开头、结尾或中间部分直接给出,有时需要读者根据文章内容提炼。 2. 论据:是支持论点的证据,分为事实论据和理论论据。事实论据包括具体事例、概括事实、统计数据、亲身经历等,具有直接...

    观点论述型作文.doc

    例如,第二段可能呈现与第一段相反的观点,同样给出两个支持的理由。第三段则可能引入自己的立场,或是对前两个观点的反驳。 3. **结论段落**:最后一段总结全文,作者在这里明确表达自己的立场,并给出支持自己...

    初中真题+考点中考英语作文模板.doc

    通常包括三个部分:第一部分介绍一个话题,第二部分分别列出两种对立观点及其理由,第三部分阐述个人立场和原因。 - 强调对比分析能力:在论述过程中,学生需要清晰地表达两种不同观点,并给出各自的支持理由。 -...

    常见高考英语作文模板.doc

    首先提出人们对某个话题的不同看法,接着明确自己的立场,然后给出支持理由。例如,"关于X,人们有不同的看法。一些人认为……(观点1),而另一些人指出……(观点2)。在我看来,前者/后者的观点更有道理。一方面...

    2013年英语四级作文模板背诵30天

    在正文段落中,考生需要给出两个或更多支持自己观点的论据,并对反对方的观点给出反驳,通常会用一个或多个例子来支持反对方的观点。结尾段落则需要总结全文,表达个人对这一辩论问题的最终立场。 整体来说,2013年...

    二年级——如何给文章分段PPT学习教案.pptx

    议论文通常包含论点的提出、论证和结论。分段策略可以是: - **提出问题**:在文章开头明确提出要讨论的问题或观点。 - **分析问题**:接下来详细论述论点,提供论据和证据支持。 - **解决问题**:最后总结分析...

    高三英语二轮复习主题36“记”旅游交通.pdf

    2. 议论文结构:文章通常包含引言、主体段落和结论,每个部分都有明确的任务,如引言提出论点,主体段落提供论据,结论总结观点。 3. 词汇和句型的运用:文章中运用了如"I can't agree more with this view" 和 "In...

Global site tag (gtag.js) - Google Analytics