设为首页收藏本站language 语言切换
查看: 1468|回复: 0
收起左侧

谈谈Java 1.5和.NET 2.0中Generics(3)

[复制链接]
发表于 2010-2-25 10:36:13 | 显示全部楼层 |阅读模式
<p >接上一篇<p >    同样是Generics,为什么两者在性能上会有这么大的区别呢?现在让我们深入到它们的内部,看看他们内部工作的机理各是什么样的。<p >    首先,然我们用反编译工具看看两者生成的Code到底是什么样的。(本文使用了DJ Java Decompiler和Lutz Roeder's Reflector.exe,如图所示)<p ><img  src="http://www.hh010.com/upload_files/article/244/9_ximcyr352031.jpg"><p >    比如对于int和String段的测试,反编译Java class 的 bytecode我们得到这样的程序(double和int类似,为了节省篇幅,这里就不具体列出了)。<p ><ccid_nobr><table width="400" border="1" cellspacing="0" cellpadding="2"  bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code" ><pre><ccid_code>private void testIntA()  {    long l = System.currentTimeMillis();    long l1 = 0L;    for(int i = 0; i &lt; m_loopNum; i++)    {      LinkedList linkedlist = new LinkedList();      for(int j = 0; j &lt; m_dataSize; j++)        linkedlist.add(Integer.valueOf(m_testInt[j]));      for(Iterator iterator = linkedlist.iterator(); iterator.hasNext();)      {        Integer integer = (Integer)iterator.next();        l1 += integer.intValue();      }    }    long l2 = System.currentTimeMillis() - l;    System.out.println((new StringBuilder()).append(&quot;Using Generics LinkedList Takes Time: &quot;).append(l2).append(&quot; millseconds. Result: &quot;).append(l1).toString());  }  private void testIntB()  {    long l = System.currentTimeMillis();    long l1 = 0L;    for(int i = 0; i &lt; m_loopNum; i++)    {      ArrayList arraylist = new ArrayList();      for(int j = 0; j &lt; m_dataSize; j++)        arraylist.add(Integer.valueOf(m_testInt[j]));      for(Iterator iterator = arraylist.iterator(); iterator.hasNext();)      {        Integer integer = (Integer)iterator.next();        l1 += integer.intValue();      }    }    long l2 = System.currentTimeMillis() - l;    System.out.println((new StringBuilder()).append(&quot;Using Generics ArrayList  Takes Time: &quot;).append(l2).append(&quot; millseconds. Result: &quot;).append(l1).toString());  }  private void testIntC()  {    long l = System.currentTimeMillis();    long l1 = 0L;    for(int i = 0; i &lt; m_loopNum; i++)    {      ArrayList arraylist = new ArrayList();      for(int j = 0; j &lt; m_dataSize; j++)        arraylist.add(Integer.valueOf(m_testInt[j]));      for(Iterator iterator = arraylist.iterator(); iterator.hasNext();)      {        Object obj = iterator.next();        l1 += ((Integer)obj).intValue();      }    }    long l2 = System.currentTimeMillis() - l;    System.out.println((new StringBuilder()).append(&quot;Using Regular  ArrayList  Takes Time: &quot;).append(l2).append(&quot; millseconds. Result: &quot;).append(l1).toString());  }  private void testStringA()  {    long l = System.currentTimeMillis();    String s = null;    for(int i = 0; i &lt; m_loopNum; i++)    {      LinkedList linkedlist = new LinkedList();      for(int j = 0; j &lt; m_dataSize; j++)        linkedlist.add(m_testString[j]);      for(Iterator iterator = linkedlist.iterator(); iterator.hasNext();)      {        String s1 = (String)iterator.next();        s = s1.toUpperCase();      }    }    long l1 = System.currentTimeMillis() - l;    System.out.println((new StringBuilder()).append(&quot;Using Generics LinkedList Takes Time: &quot;).append(l1).append(&quot; millseconds. Result: &quot;).append(s).toString());  }  private void testStringB()  {    long l = System.currentTimeMillis();    String s = null;    for(int i = 0; i &lt; m_loopNum; i++)    {      ArrayList arraylist = new ArrayList();      for(int j = 0; j &lt; m_dataSize; j++)        arraylist.add(m_testString[j]);      for(Iterator iterator = arraylist.iterator(); iterator.hasNext();)      {        String s1 = (String)iterator.next();        s = s1.toUpperCase();      }    }    long l1 = System.currentTimeMillis() - l;    System.out.println((new StringBuilder()).append(&quot;Using Generics ArrayList  Takes Time: &quot;).append(l1).append(&quot; millseconds. Result: &quot;).append(s).toString());  }  private void testStringC()  {    long l = System.currentTimeMillis();    String s = null;    for(int i = 0; i &lt; m_loopNum; i++)    {      ArrayList arraylist = new ArrayList();      for(int j = 0; j &lt; m_dataSize; j++)        arraylist.add(m_testString[j]);      for(Iterator iterator = arraylist.iterator(); iterator.hasNext();)      {        Object obj = iterator.next();        s = ((String)obj).toUpperCase();      }    }        long l1 = System.currentTimeMillis() - l;        System.out.println((new StringBuilder()).append(&quot;Using Regular  ArrayList  Takes Time: &quot;).append(l1).append(&quot; millseconds. Result: &quot;).append(s).toString());    }</ccid_code></pre></td></tr></table></ccid_nobr><p ><img  src="http://www.hh010.com/upload_files/article/244/9_32vsll352033.jpg"><p >    反编译.NET可执行文件,我们得到这样的程序。<p ><ccid_nobr><table width="400" border="1" cellspacing="0" cellpadding="2"  bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code" ><pre><ccid_code>private void testIntA(){      long num1 = Environment.TickCount;      long num2 = 0;      int num3 = 0;      while ((num3 &lt; this.m_loopNum))      {            LinkedList&lt;int&gt; list1 = new LinkedList&lt;int&gt;();            int num4 = 0;            while ((num4 &lt; this.m_dataSize))            {                  list1.AddTail(((int) this.m_testInt[num4]));                  ++num4;            }            LinkedList.Enumerator&lt;int&gt; enumerator1 = list1.GetEnumerator();            try            {                  while (enumerator1.MoveNext())                  {                        int num5 = enumerator1.Current;                        num2 += num5;                  }            }            finally            {                  enumerator1.Dispose();            }            ++num3;      }      long num6 = (Environment.TickCount - num1);      object[] objArray1 = new object[4];      objArray1[0] = &quot;Using Generics LinkedList Takes Time: &quot;;      objArray1[1] = num6;      objArray1[2] = &quot; millseconds. Result:&quot;;      objArray1[3] = num2;      Console.WriteLine(string.Concat(objArray1));}private void testIntB(){      long num1 = Environment.TickCount;      long num2 = 0;      int num3 = 0;      while ((num3 &lt; this.m_loopNum))      {            List&lt;int&gt; list1 = new List&lt;int&gt;();            int num4 = 0;            while ((num4 &lt; this.m_dataSize))            {                  list1.Add(((int) this.m_testInt[num4]));                  ++num4;            }            List.Enumerator&lt;int&gt; enumerator1 = list1.GetEnumerator();            try            {                  while (enumerator1.MoveNext())                  {                        int num5 = enumerator1.Current;                        num2 += num5;                  }            }            finally            {                  enumerator1.Dispose();            }            ++num3;      }      long num6 = (Environment.TickCount - num1);      object[] objArray1 = new object[4];      objArray1[0] = &quot;Using Generics List       Takes Time: &quot;;      objArray1[1] = num6;      objArray1[2] = &quot; millseconds. Result:&quot;;      objArray1[3] = num2;      Console.WriteLine(string.Concat(objArray1));}private void testIntC(){      long num1 = Environment.TickCount;      long num2 = 0;      int num3 = 0;      while ((num3 &lt; this.m_loopNum))      {            ArrayList list1 = new ArrayList();            int num4 = 0;            while ((num4 &lt; this.m_dataSize))            {                  list1.Add(this.m_testInt[num4]);                  ++num4;            }            foreach (object obj1 in list1)            {                  num2 += Convert.ToInt32(obj1);            }            ++num3;      }      long num5 = (Environment.TickCount - num1);      object[] objArray1 = new object[4];      objArray1[0] = &quot;Using Regular  ArrayList  Takes Time: &quot;;      objArray1[1] = num5;      objArray1[2] = &quot; millseconds. Result:&quot;;      objArray1[3] = num2;      Console.WriteLine(string.Concat(objArray1));}private void testStringA(){      long num1 = Environment.TickCount;      string text1 = null;      int num2 = 0;      while ((num2 &lt; this.m_loopNum))      {            LinkedList&lt;string&gt; list1 = new LinkedList&lt;string&gt;();            int num3 = 0;            while ((num3 &lt; this.m_dataSize))            {                  list1.AddTail(((string) this.m_testString[num3]));                  ++num3;            }            LinkedList.Enumerator&lt;string&gt; enumerator1 = list1.GetEnumerator();            try            {                  while (enumerator1.MoveNext())                  {                        string text2 = enumerator1.Current;                        text1 = text2.ToUpper();                  }            }            finally            {                  enumerator1.Dispose();            }            ++num2;      }      long num4 = (Environment.TickCount - num1);      object[] objArray1 = new object[4];      objArray1[0] = &quot;Using Generics LinkedList Takes Time: &quot;;      objArray1[1] = num4;      objArray1[2] = &quot; millseconds. Result:&quot;;      objArray1[3] = text1;      Console.WriteLine(string.Concat(objArray1));}private void testStringB(){      long num1 = Environment.TickCount;      string text1 = null;      int num2 = 0;      while ((num2 &lt; this.m_loopNum))      {            List&lt;string&gt; list1 = new List&lt;string&gt;();            int num3 = 0;            while ((num3 &lt; this.m_dataSize))            {                  list1.Add(((string) this.m_testString[num3]));                  ++num3;            }            List.Enumerator&lt;string&gt; enumerator1 = list1.GetEnumerator();            try            {                  while (enumerator1.MoveNext())                  {                        string text2 = enumerator1.Current;                        text1 = text2.ToUpper();                  }            }            finally            {                  enumerator1.Dispose();            }            ++num2;      }      long num4 = (Environment.TickCount - num1);      object[] objArray1 = new object[4];      objArray1[0] = &quot;Using Generics List       Takes Time: &quot;;      objArray1[1] = num4;      objArray1[2] = &quot; millseconds. Result:&quot;;      objArray1[3] = text1;      Console.WriteLine(string.Concat(objArray1));}private void testStringC(){      long num1 = Environment.TickCount;      string text1 = null;      int num2 = 0;      while ((num2 &lt; this.m_loopNum))      {            ArrayList list1 = new ArrayList();            int num3 = 0;            while ((num3 &lt; this.m_dataSize))            {                  list1.Add(this.m_testString[num3]);                  ++num3;            }            foreach (string text2 in list1)            {                  text1 = text2.ToUpper();            }            ++num2;      }      long num4 = (Environment.TickCount - num1);      object[] objArray1 = new object[4];      objArray1[0] = &quot;Using Regular ArrayList   Takes Time: &quot;;      objArray1[1] = num4;      objArray1[2] = &quot; millseconds. Result:&quot;;      objArray1[3] = text1;      Console.WriteLine(string.Concat(objArray1));}</ccid_code></pre></td></tr></table></ccid_nobr><p >    这些程序反映出什么问题呢?<p >    Java 1.5虽然支持Generics,对于开发人员来说,我们确实感受到了Generics带来的诸如类型安全等等好处。但是对于Java虚拟机来说,bytecode还是以前的bytecode,没有任何的变化。如果是整数,浮点数这些基本数据类型(primary data type),Java还是先将它们转化成了相应的类的实例,即Integer,Double类的对象。在使用的时候,还必须从Object类型转换成相应的类型。也就是说,Java的Generics只是一种"障眼法",在编译的时候,编译器把我们以前手工写的包装/解包装和类型转换代码自动地加进去了。所以对于具体运行的程序而言,和以前没有使用Generics的程序没有任何的变化。我们没有看到任何性能上的提升,原因就在于此。<p >    .NET 2.0是一种全新的Generics设计。我们得到了Generics承诺的所有好处。按照微软的说法,对于整数,浮点数这些数值类型(Value Type),由于Generics避免了包装/解包装操作这个不小的开销,所以性能会有2~3倍的提高;而对于String以及其它的参考类型(Reference Type),由于避免的类型转换,性能会有20%左右的提高。我们今天的测试基本证明了这一说法。<p >    看到这里,你可能不禁会问,同样是Generics,为什么Sun和Microsoft会有截然不同的做法呢?<p >    Sun的Generics是起源于一个叫做"比萨饼(Pizza)"的项目。这个项目的设计原则就是使加了Generics后的Java程序可以在以前的Java虚拟机(Java Virtual Machine)上运行,而无需对Java虚拟机进行任何改动。在这种指导思想下,Generics的工作实际上就落在了编译器的身上。由编译器在编译的时候进行类型安全检查。对于通过语法检查的程序,自动加入包装/解包装操作以及类型转换操作这样的程序代码。这样编译生成的bytecode和以前没有任何不同,Java虚拟机在执行这些bytecode的时候,根本不知道还有Generics曾经发生过。这种做法的好处是显而易见的,那就是简单,易于实现,对以前的Java虚拟机有很好的兼容性。但是其缺点也是非常突兀的,那就是从核心上牺牲了Generics的精髓,我们没能感受到Generics应该带来的性能上的提高。<p >    .NET的Generics是全新设计的,从根本上体现了Generics的精髓。语法上的保障是在编译器层次实现的。但是编译器除了应有的静态检查外和加入一些Meta信息外,并不做更多的工作(从我们反编译的程序中可以看出这一点)。具体的工作是由.NET的公共语言运行时(CLR,Common Language Runtime)来完成的。在你在第一次使用它的时候,比如说是List<int>,CLR会让JIT(Just-in-time-compiler)动态的生成这样的一个专门的int类List的本地机器代码。这个代码会被保存起来,以后类似的请求(List<int>)就可以重复使用这个已经生成的类代码。出于性能上的考虑,所有程序中申明并且使用的Value Type都会有其相应的动态类生成。比如CLR会为我们的例程生成double和int两个专用List类。为了减小程序在运行时的膨胀(Code expansion)和增加代码重用性,所有的参考类(Reference type,比如我们使用的String)共享一个专门的类,也就是说所有的参考类只有一套本地机器代码。因为参考类的本质就是一个指针,这个共性使它们可以共享同样的代码。但是这些参考类有分开的VTable,这样的做法避免了类型转换的需要。<p >    .NET这种设计是全新的,我们感受到了它的威力。但是这个新增的Generics将不兼容以前的CLR。以前的CLR(即.NET 1.0版和1.1版)无法处理这种Generics,这是一个需要指出的问题。但就我个人感觉而言,牺牲这个兼容性是值得的。<p >    最后要说明的一点就是.NET 2.0在总体上的性能比Java 1.5要高出很多,这点非常出乎我的意料。包括程序中用到的那个非常简单的函数prepareData() 两者都有巨大的性能差距。记得在.NET 1.0Beta的时候,我对比过.NET和当时Java的性能。在那个时候,他们还是非常相近的。当然了,要全面评价.NET 2.0和 Java 1.5性能上的优劣需要更全面,更系统的测试,本文目的不在于此,所以就不多加评判了。(T111)                                 <p align="center"></p></p>
您需要登录后才可以回帖 登录 | 论坛注册

本版积分规则

QQ|Archiver|手机版|小黑屋|sitemap|鸿鹄论坛 ( 京ICP备14027439号 )  

GMT+8, 2025-4-17 03:35 , Processed in 0.067232 second(s), 24 queries , Redis On.  

  Powered by Discuz!

  © 2001-2025 HH010.COM

快速回复 返回顶部 返回列表