I was checking out this similar post , the top answer of which says there's "no performance difference whatsoever" between + and StringBuilder.append() because the former will be turned into the latter by JVM.
However per my benchmark, the + way is always about ~20% faster than the StringBuilder way (I'm on Java17, running Intel core i7):
@State(Scope.Thread)
public static class BenchState {
private String a = "A";
private String b = "B";
private String c = "C";
}
@Benchmark
public void bmStringConcat(final BenchState state, final Blackhole blackhole) {
String a = state.a;
String b = state.b;
String c = state.c;
final String s = "{a:" + a + ", b:" + b + ", c: " + c + "}";
blackhole.consume(s);
}
@Benchmark
public void bmStringBuilder(final BenchState state, final Blackhole blackhole) {
String a = state.a;
String b = state.b;
String c = state.c;
StringBuilder sb = new StringBuilder();
final String s = sb.append("{a:").append(a)
.append(", b:").append(b)
.append(", c:").append(c)
.append("}")
.toString();
blackhole.consume(s);
}
Is it because the "+" version "is converted to invokedynamic call" as mentioned here .
Or there are more reasons?
Taking @Holger's advice, I checked the bytecode for following code:
public class StringBM {
public String toStringPlus(String a) {
return "{a:" + a + ", b:" + ", c: " + "}";
}
public String toStringBuilder(String a) {
StringBuilder sb = new StringBuilder(100);
return sb.append("{a:").append(a)
.append(", b:")
.append(", c:")
.append("}")
.toString();
}
}
For toStringPlus, I get
public java.lang.String toStringPlus(java.lang.String);
descriptor: (Ljava/lang/String;)Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: aload_1
1: invokedynamic #7, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
6: areturn
for toStringBuilder:
public java.lang.String toStringBuilder(java.lang.String);
descriptor: (Ljava/lang/String;)Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=3, locals=3, args_size=2
0: new #11 // class java/lang/StringBuilder
3: dup
4: bipush 100
6: invokespecial #13 // Method java/lang/StringBuilder."<init>":(I)V
9: astore_2
10: aload_2
11: ldc #16 // String {a:
13: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
16: aload_1
17: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: ldc #22 // String , b:
22: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: ldc #24 // String , c:
27: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: ldc #26 // String }
32: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: invokevirtual #28 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
38: areturn
The + version simply invokes dynamic function makeConcatWithConstants.
Whereas the StringBuilder version has to do it the 'honest' way.
I guess we can see why is + faster now.
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