在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,但至少总体比率符合预期。
答案 0 :(得分:22)
我查看了您的代码,StringBuilder
它出现的最可能原因是您的基准测试没有正确考虑JVM预热的影响。在这种情况下:
其中任何一个或两个都可能会增加测试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所述的StringBuffer
和StringBuilder
之前所花费的时间,这可能会因多次运行而有所不同。
这是我做了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)
我建议
当您运行的代码少于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