字符串池:"Te"+"st"比"Test"快?

pit*_*chr 7 java string benchmarking pool concatenation

我正在尝试一些关于String Pool的性能基准测试.但是,预计结果不会出现.

我做了3个静态方法

  • perform0()方法...每次都创建一个新对象
  • perform1()方法...字符串文字"测试"
  • perform2()方法...字符串常量表达式"Te"+"st"

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

  1. 由于字符串池化而"测试"
  2. "Te"+"st",因为字符串池,但因为+运算符而比1慢
  3. 新的String(..)因为没有字符串池.

但基准测试显示"Te"+"st"比"Test"快一点.

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
...
Run Code Online (Sandbox Code Playgroud)

这是代码:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

有人可以解释为什么"Te"+"st"的表现比"Test"快吗?JVM在这里做了一些优化吗?谢谢.

Mar*_*ers 10

"Te" + "st"是一个编译器时间常量表达式,因此在运行时的行为与简单的行为没有区别"Test".任何性能损失将在尝试编译时,而不是在尝试运行时.

通过使用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
...
Run Code Online (Sandbox Code Playgroud)

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

除非表达式是编译时常量表达式(第15.28节),否则将新创建String对象(第12.5节).

您遇到的基准差异可能是由于典型的可变性或因为您的基准不完美.看到这个问题:如何在Java中编写正确的微基准测试?

你打破一些值得注意的规则:

  1. 您不会丢弃测试内核的"预热"迭代结果.
  2. 您没有启用GC日志记录(特别perform1()是在创建一百万个对象的测试后始终正在运行时).