2

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?

wayne
  • 598
  • 3
  • 15
  • 4
    Since you already found out that the answer from 2009 is outdated, yes, it’s very likely that the `invokedynamic` performs better, as that was one of the goals when introducing it. Make sure not to use the Eclipse compiler for comparing these variants, as it doesn’t support the `invokedynamic` yet. Or, verify the compiled code with `javap`. – Holger Apr 22 '22 at 14:48

1 Answers1

0

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.

wayne
  • 598
  • 3
  • 15