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()