为什么StringBuilder比StringBuffer慢?

时间:2012-10-26 07:38:26

标签: java stringbuilder stringbuffer

this example中,StringBuffer实际上比StringBuilder更快,而我本来期望相反的结果。

这与JIT的优化有关吗?有谁知道为什么StringBuffer比StringBuilder更快,即使它的方法是同步的?

以下是代码和基准测试结果:

public class StringOps {

    public static void main(String args[]) {

        long sConcatStart = System.nanoTime();
        String s = "";
        for(int i=0; i<1000; i++) {
            s += String.valueOf(i);
        }
        long sConcatEnd = System.nanoTime();

        long sBuffStart = System.nanoTime();
        StringBuffer buff = new StringBuffer();
        for(int i=0; i<1000; i++) {
            buff.append(i);
        }
        long sBuffEnd = System.nanoTime();

        long sBuilderStart = System.nanoTime();
        StringBuilder builder = new StringBuilder();
        for(int i=0; i<1000; i++) {
            builder.append(i);
        }
        long sBuilderEnd = System.nanoTime();

        System.out.println("Using + operator : " + (sConcatEnd-sConcatStart) + "ns");
        System.out.println("Using StringBuffer : " + (sBuffEnd-sBuffStart) + "ns");
        System.out.println("Using StringBuilder : " + (sBuilderEnd-sBuilderStart) + "ns");

        System.out.println("Diff '+'/Buff = " + (double)(sConcatEnd-sConcatStart)/(sBuffEnd-sBuffStart));
        System.out.println("Diff Buff/Builder = " + (double)(sBuffEnd-sBuffStart)/(sBuilderEnd-sBuilderStart));
    }
}


基准测试结果:

Using + operator : 17199609ns
Using StringBuffer : 244054ns
Using StringBuilder : 4351242ns
Diff '+'/Buff = 70.47460398108615
Diff Buff/Builder = 0.056088353624091696


更新:

感谢大家。热身确实是问题所在。一旦添加了一些预热代码,基准就变为:

Using + operator : 8782460ns
Using StringBuffer : 343375ns
Using StringBuilder : 211171ns
Diff '+'/Buff = 25.576876592646524
Diff Buff/Builder = 1.6260518726529685


YMMV,但至少总体比率符合预期。

5 个答案:

答案 0 :(得分:22)

我查看了您的代码,StringBuilder出现的最可能原因是您的基准测试没有正确考虑JVM预热的影响。在这种情况下:

  • JVM启动会产生大量需要处理的垃圾,
  • JIT编译可能会在运行中途启动。

其中任何一个或两个都可能会增加测试StringBuilder部分的时间。

请阅读本课题的答案以获取更多详细信息:How do I write a correct micro-benchmark in Java?

答案 1 :(得分:5)

在两种情况下使用来自java.lang.AbstractStringBuilder的完全相同的代码,并且两个实例都以相同的容量创建(16)。

唯一的区别是在初次通话时使用synchronized

我得出结论,这是一个测量工件。

StringBuilder:

228    public StringBuilder append(int i) {
229        super.append(i);
230        return this;
231    }

StringBuffer:

345    public synchronized StringBuffer append(int i) {
346        super.append(i);
347        return this;
348    }

AbstractStringBuilder:

605     public AbstractStringBuilder append(int i) {
606         if (i == Integer.MIN_VALUE) {
607             append("-2147483648");
608             return this;
609         }
610         int appendedLength = (i < 0) ? Integer.stringSize(-i) + 1
611                                      : Integer.stringSize(i);
612         int spaceNeeded = count + appendedLength;
613         if (spaceNeeded > value.length)
614             expandCapacity(spaceNeeded);
615         Integer.getChars(i, spaceNeeded, value);
616         count = spaceNeeded;
617         return this;
618     }


110     void expandCapacity(int minimumCapacity) {
111         int newCapacity = (value.length + 1) * 2;
112         if (newCapacity < 0) {
113             newCapacity = Integer.MAX_VALUE;
114         } else if (minimumCapacity > newCapacity) {
115             newCapacity = minimumCapacity;
116         }
117         value = Arrays.copyOf(value, newCapacity);
118     }

(expandCapacity未被覆盖)

这篇博文更多地讲述了:

  • 微基准测试的难点
  • 事实上,如果不仔细看看你测量的内容(这里是常见的超类),你就不会发布基准的“结果”

请注意,最近JDK中同步的“慢度”可以被视为一个神话。我所做的或所读的所有测试都得出结论,通常没有理由避免花费太多时间来避免同步。

答案 2 :(得分:2)

当您自己运行该代码时,您会看到不同的结果。有时StringBuffer更快,有时StringBuilder更快。 可能的原因可能是JVM warmup在使用@Stephen所述的StringBufferStringBuilder之前所花费的时间,这可能会因多次运行而有所不同。

这是我做了4次跑步的结果: -

Using StringBuffer : 398445ns
Using StringBuilder : 272800ns

Using StringBuffer : 411155ns
Using StringBuilder : 281600ns

Using StringBuffer : 386711ns
Using StringBuilder : 662933ns

Using StringBuffer : 413600ns
Using StringBuilder : 270356ns

当然,基于4次执行无法预测确切数字。

答案 3 :(得分:2)

我建议

  • 将每个循环分成单独的方法,因此优化一个不会影响另一个。
  • 忽略前10K迭代
  • 运行测试至少2秒钟。
  • 多次运行测试以确保其可重现性。

当您运行的代码少于10000次时,它可能不会触发将代码编译为默认值-XX:CompileThreshold=10000。这样做的部分原因是收集有关如何最佳优化代码的统计信息。但是,当一个循环触发编译时,它会触发整个方法,这可以使后来的循环看起来更好,因为它们在开始之前被编译b)更糟糕,因为编译时没有收集任何统计信息


考虑以下代码

public static void main(String... args) {
    int runs = 1000;
    for (int i = 0; i < runs; i++)
        String.valueOf(i);

    System.out.printf("%-10s%-10s%-10s%-9s%-9s%n", "+ oper", "SBuffer", "SBuilder", "+/Buff", "Buff/Builder");
    for (int t = 0; t < 5; t++) {
        long sConcatTime = timeStringConcat(runs);
        long sBuffTime = timeStringBuffer(runs);
        long sBuilderTime = timeStringBuilder(runs);

        System.out.printf("%,7dns %,7dns %,7dns ",
                sConcatTime / runs, sBuffTime / runs, sBuilderTime / runs);
        System.out.printf("%8.2f %8.2f%n",
                (double) sConcatTime / sBuffTime, (double) sBuffTime / sBuilderTime);
    }
}

public static double dontOptimiseAway = 0;

private static long timeStringConcat(int runs) {
    long sConcatStart = System.nanoTime();
    for (int j = 0; j < 100; j++) {
        String s = "";
        for (int i = 0; i < runs; i += 100) {
            s += String.valueOf(i);
        }
        dontOptimiseAway = Double.parseDouble(s);
    }
    return System.nanoTime() - sConcatStart;
}

private static long timeStringBuffer(int runs) {
    long sBuffStart = System.nanoTime();
    for (int j = 0; j < 100; j++) {
        StringBuffer buff = new StringBuffer();
        for (int i = 0; i < runs; i += 100)
            buff.append(i);
        dontOptimiseAway = Double.parseDouble(buff.toString());
    }
    return System.nanoTime() - sBuffStart;
}

private static long timeStringBuilder(int runs) {
    long sBuilderStart = System.nanoTime();
    for (int j = 0; j < 100; j++) {
        StringBuilder buff = new StringBuilder();
        for (int i = 0; i < runs; i += 100)
            buff.append(i);
        dontOptimiseAway = Double.parseDouble(buff.toString());
    }
    return System.nanoTime() - sBuilderStart;
}

使用runs = 1000打印

+ oper    SBuffer   SBuilder  +/Buff   Buff/Builder
  6,848ns   3,169ns   3,287ns     2.16     0.96
  6,039ns   2,937ns   3,311ns     2.06     0.89
  6,025ns   3,315ns   2,276ns     1.82     1.46
  4,718ns   2,254ns   2,180ns     2.09     1.03
  5,183ns   2,319ns   2,186ns     2.23     1.06

但是如果你增加次数= 10,000

+ oper    SBuffer   SBuilder  +/Buff   Buff/Builder
  3,791ns     400ns     357ns     9.46     1.12
  1,426ns     139ns     113ns    10.23     1.23
    323ns     141ns     117ns     2.29     1.20
    317ns     115ns      78ns     2.76     1.47
    317ns     127ns     103ns     2.49     1.23

如果我们将运行量增加到100,000,我就得到了

+ oper    SBuffer   SBuilder  +/Buff   Buff/Builder
  3,946ns     195ns     128ns    20.23     1.52
  2,364ns     113ns      86ns    20.80     1.32
  2,189ns     142ns      95ns    15.34     1.49
  2,036ns     142ns      96ns    14.31     1.48
  2,566ns     114ns      88ns    22.46     1.29

注意:+操作已经放慢,因为循环的时间复杂度为O(N ^ 2)

答案 4 :(得分:1)

我稍微修改了你的代码并添加了预热循环。 我的观察结果大部分时间都是StringBuilder在大多数时候都更快。

我在Ubuntu12.04上运行,它在Windows 7上虚拟运行,并为VM分配了2 GB RAM。

public class StringOps {

public static void main(String args[]) {

    for(int j=0;j<10;j++){
        StringBuffer buff = new StringBuffer();
        for(int i=0; i<1000; i++) {
                buff.append(i);
        }
    buff = new StringBuffer();
    long sBuffStart = System.nanoTime();
    for(int i=0; i<10000; i++) {
                buff.append(i);
        }
    long sBuffEnd = System.nanoTime();


        StringBuilder builder = new StringBuilder();
        for(int i=0; i<1000; i++) {
                builder.append(i);
        }
    builder = new StringBuilder();
    long sBuilderStart = System.nanoTime();
    for(int i=0; i<10000; i++) {
                builder.append(i);
        }   
        long sBuilderEnd = System.nanoTime();

        if((sBuffEnd-sBuffStart)>(sBuilderEnd-sBuilderStart)) {
        System.out.println("String Builder is faster") ; 
    }
    else {
        System.out.println("String Buffer is faster") ;
    }
    }
}

}

结果是:

String Builder is faster
String Builder is faster
String Builder is faster
String Builder is faster
String Buffer is faster
String Builder is faster
String Builder is faster
String Builder is faster
String Builder is faster
String Builder is faster