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

时间:2012-08-22 20:45:52

标签: java string concatenation benchmarking pool

我正在尝试一些关于String Pool的性能基准测试。但是,结果不是预期的。

我做了3个静态方法

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

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

  1. 因字符串池而“测试”
  2. “Te”+“st”,因为字符串池,但因为+运算符
  3. 而慢于1
  4. new String(..)因为没有字符串池。
  5. 但基准测试显示“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
    ...
    

    以下是代码:

    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”的表现比“Test”快吗? JVM在这里做了一些优化吗?谢谢。

5 个答案:

答案 0 :(得分: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
...

方法的字节代码完全相同!这由Java Language Specification, 15.18.1

指定
  

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

您遇到的基准差异可能是由于典型的可变性或因为您的基准不完美。请参阅此问题:How do I write a correct micro-benchmark in Java?

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

  1. 您不会丢弃测试内核的“预热”迭代结果。
  2. 您没有启用GC日志记录(当perform1() 总是在创建一百万个对象的测试后立即运行时尤其相关。)

答案 1 :(得分:3)

也许JIT编译器开始了,第三个是执行本机代码。也许连接被移到了循环之外。也许连接从未完成,因为永远不会读取变量。也许差别就是噪音,你的三个样本巧合地指出了相同的方式。

Robust Java benchmarking, Part 1: Issues解释了许多基准化Java出错的方法。

  

基准测试非常困难。许多因素,无论是显而易见的还是微妙的,都会影响您的结果。要获得准确的结果,您需要全面掌握这些问题,可能需要使用基准测试框架来解决其中的一些问题。转到Part 2,了解这样一个强大的Java基准测试框架。

在了解JVM体系结构引入的特定缺陷之前,不要指望Java代码的微基准测试告诉您任何有用的东西,并且不要指望即使是最好的微基准测试也能预测实际应用程序的性能。

我不知道你的目标是什么,但学会使用一个好的剖析器并在实际应用中使用它通常会告诉你这条线路是否真的是低效率的来源,让你测量一下代码更改。学习分析器的时间可能比编写和调试微基准测试的时间更长。

答案 2 :(得分:0)

首先,我们很高兴知道:

  

如果你反复连接字符串,让我们说一个循环,那么你知道因为它们是不可变的,新的字符串会不断生成。 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

我认为这并不适用于您的案件。在第一次性能测试中,您始终创建一个新对象,以便创建1000000个String("Test")个对象。在第二和第三个例子中,只创建了一个被许多引用的对象。如前所述:"Te"+"st"被视为编译时间常量,差异太小,不足以说它比#34;测试&#34;。

答案 3 :(得分:0)

马克·彼得斯是对的,两个字符串常量将无条件地连接起来。

这是因为连接String对象所需的复制时间,遵循它们的大小。

现在编译器将这些对象编译成StringBuffer / StringBuilder对象,您可以通过反编译.class文件来查看它。

您应该看看这些类,但要注意,当将StringBuilder或StringBuffer呈现为String时,将创建一个新的String对象。

答案 4 :(得分:0)

很抱歉发表回答,但我不能在评论中说明这个基准是多么有缺陷。 在Linux上,我更改了订单并获得:

这个命令非常重要。

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