7

我正在尝试一些关于字符串池的性能基准。然而,结果并不出人意料。

我做了3个静态方法

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

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

  1. “测试”因为字符串池
  2. "Te"+"st" 因为字符串池,但由于 + 运算符而比 1 慢一点
  3. new 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
...

这是代码:

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在这里做了一些优化吗?谢谢你。

4

5 回答 5

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 语言规范 15.18.1指定的:

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

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

您违反的一些值得注意的规则:

  1. 您不会丢弃测试内核的“热身”迭代的结果。
  2. 您没有启用 GC 日志记录(尤其perform1()是在创建一百万个对象的测试之后始终运行时)。
于 2012-08-22T20:50:08.230 回答
3

也许 JIT 编译器启动,第三个是执行本机代码。也许串联被移到了循环之外。也许连接永远不会完成,因为变量永远不会被读取。也许区别在于噪音,您的三个样本巧合地指向相同的方向。

强大的 Java 基准测试,第 1 部分:问题解释了许多 Java 基准测试可能出错的方式。

基准测试非常困难。许多因素,无论是明显的还是微妙的,都会影响您的结果。要获得准确的结果,您需要彻底掌握这些问题,可能通过使用解决其中一些问题的基准测试框架。转到第 2 部分,了解这样一个健壮的 Java 基准测试框架。

在您了解 JVM 架构引入的特定缺陷之前,不要期望 Java 代码的微基准能够告诉您任何有用的信息,也不要期望即使是最好的微基准也能预测实际应用程序的性能。

我不知道你的目标是什么,但是学习使用一个好的分析器并在你的实际应用程序中使用它通常会告诉你有问题的行是否真的是低效率的根源,并让你衡量代码更改的效果。花时间学习分析器可能比花时间编写和调试微基准更好。

于 2012-08-22T20:51:41.947 回答
0

首先,很高兴知道:

如果您一遍又一遍地连接字符串,比如说在一个循环中,那么您就会知道,因为它们是不可变的,所以会不断生成新的字符串。javac 编译器在内部使用 StringBuffer 来执行此操作 - 例如,您有

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

在一个循环中。

发生的事情是,在循环中,生成了两个对象。一个是StringBuffer-

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

另一个是通过 toString() 分配给 itemList 的字符串。

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

我认为这不适用于您的情况。在第一次性能测试中,您总是创建一个新对象,因此创建了 1000000 个String("Test")对象。在第二个和第三个示例中,只创建了一个被许多引用指向的对象。如前所述:"Te"+"st"被视为编译器时间常数,差异太小,不能说它比“测试”快。

于 2012-08-22T21:00:29.173 回答
0

Mark Peters 是对的,两个 String 常量将毫不留情地连接起来。

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

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

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

于 2012-08-22T21:02:44.467 回答
0

很抱歉发布答案,但我无法将其放在评论中以显示该基准有多么缺陷。在 Linux 上,我更改了顺序并得到:

顺序很重要。

new String()   : 123328907 ns
"Test"         : 1153035 ns
"Te"+"st"      : 5389377 ns
"a"+"b"+"c"+"d": 1256918 ns
于 2012-08-22T21:11:42.183 回答