In this example, StringBuffer is actually faster than StringBuilder, whereas I would have expected opposite results.
Is this something to do with optimizations being made by the JIT ? Does anyone know why StringBuffer would be faster than StringBuilder, even though it's methods are synchronized ?
Here's the code and the benchmark results:
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));
}
}
Benchmark results:
Using + operator : 17199609ns
Using StringBuffer : 244054ns
Using StringBuilder : 4351242ns
Diff '+'/Buff = 70.47460398108615
Diff Buff/Builder = 0.056088353624091696
UPDATE:
Thanks to everyone. Warmup was indeed the problem. Once some warmup code was added, the benchmarks changed to:
Using + operator : 8782460ns
Using StringBuffer : 343375ns
Using StringBuilder : 211171ns
Diff '+'/Buff = 25.576876592646524
Diff Buff/Builder = 1.6260518726529685
YMMV, but at least the overall ratios agree with what would be expected.
I had a look at your code, and the most likely reason that StringBuilder
it appears to be slower is that your benchmark is not properly taking account of the effects of JVM warmup. In this case:
Either or both of these could add to the time measured for the StringBuilder
part of your test.
Please read the answers to this Question for more details: How do I write a correct micro-benchmark in Java?
The exact same code, from java.lang.AbstractStringBuilder
, is used in both cases, and both instances are created with the same capacity (16).
The only difference is the use of synchronized
at the initial call.
I conclude this is a measurement artifact.
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 isn't overrided)
This blog post says more about :
Note that the "slowness" of synchronized in recent JDK can be considered a myth. All tests I made or read conclude there is generally no reason to lose much time avoiding the synchronizations.
When you run that code on yourself, you would see a varying result. Sometimes StringBuffer is faster and sometimes StringBuilder is faster.
The likely reason for this may be the time taken for JVM warmup
before using StringBuffer
and StringBuilder
as stated by @Stephen, which can vary on multiple runs.
This is the result of 4 runs I made: -
Using StringBuffer : 398445ns
Using StringBuilder : 272800ns
Using StringBuffer : 411155ns
Using StringBuilder : 281600ns
Using StringBuffer : 386711ns
Using StringBuilder : 662933ns
Using StringBuffer : 413600ns
Using StringBuilder : 270356ns
Of course the exact figures cannot be predicted based on just 4 execution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With