字符串池:“TE” +“ST”快于“测试”?(String Pool: “Te”+“st” fast

2019-09-21 12:18发布

我想关于字符串池一些业绩比较基准。 然而,没有预料的结果。

我做了3种静态方法

  • perform0()方法...创建一个新的对象,每次
  • perform1()方法...字符串文字“测试”
  • perform2()方法...字符串常量表达式 “TE” + “ST”

我的期望是(1.最快的 - > 3.最慢)

  1. 因为串池的“测试”
  2. 因为+操作“TE” +,因为串池,但“ST”位低于1
  3. 新的String(..),因为没有字符串池的。

但是基准表明,“TE” +“ST”是略低的速度比“测试”。

new String(): 141677000 ns 
"Test"      : 1148000 ns 
"Te"+"st"   : 1059000 ns

new String(): 141253000 ns
"Test"      : 1177000 ns
"Te"+"st"   : 1089000 ns

new String(): 142307000 ns
"Test"      : 1878000 ns
"Te"+"st"   : 1082000 ns

new String(): 142127000 ns
"Test"      : 1155000 ns
"Te"+"st"   : 1078000 ns
...

下面的代码:

import java.util.concurrent.TimeUnit;


public class StringPoolPerformance {

    public static long perform0() {
        long start = System.nanoTime();
        for (int i=0; i<1000000; i++) {
            String str = new String("Test");
        }
        return System.nanoTime()-start;
    }

    public static long perform1() {
        long start = System.nanoTime();
        for (int i=0; i<1000000; i++) {
            String str = "Test";
        }
        return System.nanoTime()-start;
    }

    public static long perform2() {
        long start = System.nanoTime();
        for (int i=0; i<1000000; i++) {
            String str = "Te"+"st";
        }
        return System.nanoTime()-start;
    }

    public static void main(String[] args) {
        long time0=0, time1=0, time2=0;
        for (int i=0; i<100; i++) {
            // result
            time0 += perform0();
            time1 += perform1();
            time2 += perform2();
        }

        System.out.println("new String(): " +  time0 + " ns");
        System.out.println("\"Test\"      : " + time1 + " ns");
        System.out.println("\"Te\"+\"st\"   : " + time2 + " ns");
    }
}

有人可以解释为什么“TE” +“ST”的性能比“测试”更快? 是JVM做了一些优化,在这里? 谢谢。

Answer 1:

"Te" + "st"是一个编译时间常数表达式,所以在运行时的行为无异于简单的"Test" 。 尝试编译它的时候,试图运行它不会当任何性能损失就越大。

这是很容易拆卸使用编译Benchmark类证明javap -c StringPoolPerformance

public static long perform1();
  Code:
...
   7:   ldc #3; //int 1000000
   9:   if_icmpge   21
   12:  ldc #5; //String Test
   14:  astore_3
   15:  iinc    2, 1
...

public static long perform2();
  Code:
...
   7:   ldc #3; //int 1000000
   9:   if_icmpge   21
   12:  ldc #5; //String Test
   14:  astore_3
   15:  iinc    2, 1
...

方法字节码是完全相同的! 这是由指定的Java语言规范,15.18.1 :

字符串对象是新创建(§12.5),除非表达是一个编译时间常量表达式(§15.28)。

您遇到基准差异可能是由于典型的变化,或者因为你的基准是不完美的。 看到这个问题: 我如何用Java写一个正确的微标杆?

一些著名的规则你打破:

  1. 你不放弃你的测试内核的“热身”迭代的结果。
  2. 你不必启用GC日志(尤其是相关的,当perform1() 总是被正确的它创建了一个亿的对象测试后运行)。


Answer 2:

也许JIT编译器踢,三是执行本机代码。 也许串联移到外循环。 也许级联是永远做不完的,因为变量从未被读取。 也许不同的是噪声和你的三个样品不约而同地指向了同样的方式。

健壮的Java基准测试,第1部分:问题解释了很多的办法是基准的Java可能出错。

标杆管理是非常困难的。 许多因素,既明显又微妙,会影响你的成绩。 为了获得准确的结果,你需要对这些问题进行彻底的命令,可能通过使用一个基准框架,解决其中的一些。 进入第2部分 ,以了解这样一个健壮的Java基准测试框架。

不要指望Java代码的微基准测试来告诉你什么有用的东西,直到你了解具体的缺陷是,JVM架构介绍,不要期望,即使是最好的微基准预测一个真正的应用程序的性能。

我不知道你的目标是什么,但学习使用一个优秀的分析器,并使用它在实际的应用程序通常会告诉你,如果这行真的是无效率的源泉,让你衡量一个代码变化的影响。 花费的时间学习探查可能是比时间编写和调试微基准测试中度过。



Answer 3:

首先,它会很高兴地知道:

如果你是遍地连接字符串,让我们在一个循环中说,那么你知道,因为他们是不可改变的,不断得到新生成的字符串。 javac编译器内部使用一个StringBuffer因此,例如,你所要做这个 -

String itemList = "";
 itemList=itemList + items[i].description;

在一个循环。

什么情况是,内环路,产生两个对象。 一个是StringBuffer-

itemList=new StringBuffer().append(itemList).
      append(items[i].description).toString();

另一个是被通过toString()分配给itemList中的String。`

来源: http://thought-bytes.blogspot.com/2007/03/java-string-performance.html

我认为这并不适用于你的情况。 在第一个性能测试,你总是创建一个新的对象,因此有产生百万String("Test")的对象。 在第二和第三例子有只创建一个对象到由许多参考指出。 正如之前所说: "Te"+"st"被视为编译时间常数和差异太小,说,这是比“测试”更快。



Answer 4:

对不起,张贴一个答案,但我不能把这个注释,以显示这个标杆是如何有缺陷的。 在Linux上,我改变了顺序,并得到:

订单挑衅事项。

new String()   : 123328907 ns
"Test"         : 1153035 ns
"Te"+"st"      : 5389377 ns
"a"+"b"+"c"+"d": 1256918 ns


Answer 5:

马克·彼得斯是正确的,两个String常量将毫不手软地连接在一起。

这是因为来连接String对象,跟随他们的大小所需的复制时间。

现在,这些被编译成由编译器的StringBuffer / StringBuilder的对象,可以通过反编译.class文件看到它。

你应该看看这些类,但要注意的是呈现一个StringBuilder或StringBuffer的一个字符串时,一个新的String对象将被创建。



文章来源: String Pool: “Te”+“st” faster than “Test”?