Java基准测试-为什么第二个循环更快?

Java基准测试-为什么第二个循环更快?,java,performance,benchmarking,Java,Performance,Benchmarking,我对此很好奇 我想检查哪个函数更快,所以我创建了一些代码,执行了很多次 public static void main(String[] args) { long ts; String c = "sgfrt34tdfg34"; ts = System.currentTimeMillis(); for (int k = 0; k < 10000000; k++) { c.getBytes();

我对此很好奇

我想检查哪个函数更快,所以我创建了一些代码,执行了很多次

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

    }
publicstaticvoidmain(字符串[]args){
长ts;
字符串c=“sgfrt34tdfg34”;
ts=系统.currentTimeMillis();
对于(int k=0;k<10000000;k++){
c、 getBytes();
}
System.out.println(“t1->”+(System.currentTimeMillis()-ts));
ts=系统.currentTimeMillis();
对于(int i=0;i<10000000;i++){
字节。toBytes(c);
}
System.out.println(“t2->”+(System.currentTimeMillis()-ts));
}

“第二个”循环更快,所以,我认为hadoop中的Bytes类比String类中的函数更快。然后,我改变了循环的顺序,然后c.getBytes()变得更快。我执行了很多次,我的结论是,我不知道为什么,但是在第一个代码执行之后,我的VM中发生了一些事情,所以第二个循环的结果会更快

这是一个典型的java基准问题。Hotspot/JIT/etc将在您使用代码时编译代码,因此在运行过程中会更快


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

最有可能的情况是,在第一个循环运行时代码仍在编译或尚未编译

将整个方法包装在一个外部循环中,这样您就可以运行几次基准测试,您应该会看到更稳定的结果


阅读:。

可能是这样的情况:您通过调用getBytes()为对象分配了太多空间,JVM垃圾收集器启动并清理未使用的引用(清除垃圾)。

您知道有问题,因为
Bytes.toBytes
在内部调用
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;
    }
}
来源于。这告诉您,调用不可能比直接调用快-最好(即,如果它内联),它将具有相同的时间。但是,通常情况下,由于调用函数的开销很小,您会期望它会稍微慢一点

这是在具有任意时间运行的组件(如垃圾收集器)的已解释、垃圾收集环境中进行微基准测试的典型问题。除此之外,还有一些硬件优化,比如缓存,这会使情况变得更糟。因此,了解正在发生的事情的最佳方法通常是查看源代码

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

当您执行一个方法至少10000次时,它将触发编译整个方法。这意味着您的第二个循环可以是

  • 速度更快,因为第一次运行时它已经编译好了
  • 速度较慢,因为优化后,它没有关于代码执行方式的良好信息/计数器
最好的解决方案是将每个循环放在一个单独的方法中,这样一个循环就不会优化另一个循环,并将其运行几次,忽略第一次运行

e、 g

for(int i=0;i<3;i++){
long-time1=doTest1();//使用System.nanoTime()计时;
longtime2=doTest2();
System.out.printf(“Test1平均占%,Test2平均占%,d平均占%n”,
时间1/次,时间2/次);
}
再观察几次

  • 正如上面@dasblinkenlight所指出的,Hadoop的
    Bytes.toBytes(c)
    在内部调用
    字符串.getBytes(“UTF-8”)

  • 采用字符集作为输入的variant方法
    String.getBytes()
    比不采用任何字符集的方法快。因此,对于给定的字符串,
    getBytes(“UTF-8”)
    将比
    getBytes()
    快。我已经在我的机器(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));
    
    即使更改循环的执行顺序,结果也是相同的。为了不考虑任何JIT优化,我建议用不同的方法运行测试以确认这一点(正如上面@Peter Lawrey所建议的)

    • 因此,
      Bytes.toBytes(c)
      应该总是比
      String.getBytes()快。

    您能提供JDK版本、操作系统和样本基准编号等详细信息吗?在您的标题和说明之间存在的主要问题是哪个循环更快?请使用
    System.nanoTime()
    。@guille。我只是遇到了同样的行为,但当我在调试模式下而不是在发行版中启动它时,我注意到了它。您是否在调试中进行了测试?请参阅Aleksey Shipilev的重复问题,以深入分析此类代码的性能。
    Bytes.toBytes()
    在内部调用
    c.getBytes(“UFT-8”)
    ,但输入为编码的那一个。
    String
    s
    getByte()
    getByte()多一点开销(“UTF-8”)
    因此速度较慢。简而言之
    Bytes.toBytes()
    更快。谢谢您的评论,我已经知道了一点。@Santosh您正在寻找的选项是-XX:CompileThreshold从未将其设置为0或1。这是一个方法符合JIT条件后的方法调用数。默认情况下,它是3000,-server选项从Oracle文档中将其覆盖为10000,看起来像是default client值是1500,可能3000在某些新版本中被修改:)同样,当您使用64位JRE时,服务器选项是隐式默认值。这并不能解决第二个循环总是更快的事实,不管其中包含哪些代码。@OrangeDog-Hmmm,这正是我要解决的问题以及如何修复它。我不知道如何使它成为任何c学习者。
        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