Java基准 – 为什么第二个循环更快?

我对此很好奇。

我想检查哪个函数更快,所以我创build了一个小代码,并执行了很多次。

public static void main(String[] args) { long ts; String c = "sgfrt34tdfg34"; ts = System.currentTimeMillis(); for (int k = 0; k < 10000000; k++) { c.getBytes(); } System.out.println("t1->" + (System.currentTimeMillis() - ts)); ts = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { Bytes.toBytes(c); } System.out.println("t2->" + (System.currentTimeMillis() - ts)); } 

“第二”循环更快,所以,我认为从hadoop的字节类比从string类的function更快。 然后,我改变了循环的顺序,然后c.getBytes()变得更快。 我执行了很多次,我的结论是,我不知道为什么,但是在执行第一个代码后,在我的虚拟机中发生了一些事情,以便第二个循环的结果变得更快。

这是一个经典的Java基准testing问题。 Hotspot / JIT / etc会在你使用的时候编译你的代码,所以在运行的时候会变得更快。

首先运行循环至less3000次(在服务器上或64位上),然后进行测量。

你知道有什么问题,因为Bytes.toBytes c.getBytes内部调用c.getBytes

 public static byte[] toBytes(String s) { try { return s.getBytes(HConstants.UTF8_ENCODING); } catch (UnsupportedEncodingException e) { LOG.error("UTF-8 not supported?", e); return null; } } 

来源是从这里拿走的。 这告诉你,这个调用不可能比直接调用更快 – 在最好的情况下(即,如果它被内联),它将具有相同的时间。 但是,一般来说,由于调用函数的开销很小,所以期望它会稍微慢一些。

这是在垃圾回收环境中使用微型基准testing的经典问题,垃圾收集环境中的组件可以在任意时间运行,比如垃圾收集器。 最重要的是硬件优化,比如高速caching,会使图片歪斜。 结果,看到发生的最好的方法往往是看源头。

“第二”循环更快,所以,

当你执行一个方法至less10000次时,它会触发整个方法被编译。 这意味着你的第二个循环可以

  • 因为它是第一次运行它已经编译更快。
  • 速度较慢,因为优化后,代码的执行方式没有很好的信息/计数器。

最好的解决scheme是把每个循环放在一个单独的方法中,这样一个循环不会优化另一个循环,并且运行几次,忽略第一次运行。

例如

 for(int i = 0; i < 3; i++) { long time1 = doTest1(); // timed using System.nanoTime(); long time2 = doTest2(); System.out.printf("Test1 took %,d on average, Test2 took %,d on average%n", time1/RUNS, time2/RUNS); } 

在第一次循环运行的时候,代码很可能还在编译或者还没有编译。

将整个方法封装在一个外部循环中,这样可以多次运行基准testing,并且应该看到更稳定的结果。

阅读: dynamic编译和性能测量 。

简单的情况是,您可以通过调用getBytes()来为对象分配太多空间,以使JVM垃圾收集器启动并清理未使用的引用(引发垃圾)。

几乎没有观察

  • 正如上面的Bytes.toBytes(c);指出的,Hadoop的Bytes.toBytes(c); 内部调用String.getBytes("UTF-8")

  • 字符集作为input的变体方法String.getBytes() 不采取任何字符集的方法更快 。 所以对于给定的string, getBytes("UTF-8")将比getBytes()更快。 我已经在我的机器上testing了这个(Windows8,JDK 7)。 用getBytes("UTF-8")getBytes()以循环顺序运行两个循环。

      long ts; String c = "sgfrt34tdfg34"; ts = System.currentTimeMillis(); for (int k = 0; k < 10000000; k++) { c.getBytes("UTF-8"); } System.out.println("t1->" + (System.currentTimeMillis() - ts)); ts = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { c.getBytes(); } System.out.println("t2->" + (System.currentTimeMillis() - ts)); 

这给了:

 t1->1970 t2->2541 

即使改变循环的执行顺序,结果也一样。 要打折任何JIT优化,我会build议在单独的方法中运行testing来确认(如上面@Peter Lawrey所build议的那样)

  • 所以, Bytes.toBytes(c)应该总是比String.getBytes()