⬆️ ⬇️

The append (x) .append (y) call chain in StringBuilder is faster than typical sb.append (x); sb.append (y)

Hello everyone, to the last article about the legacy of StringBuffer in the comments left an interesting link . This article has an interesting benchmark, which I changed to make it more dramatic:



@BenchmarkMode(Mode.Throughput) @Fork(1) @State(Scope.Thread) @Warmup(iterations = 10, time = 1, batchSize = 1000) @Measurement(iterations = 40, time = 1, batchSize = 1000) public class Chaining { private String a1 = "111111111111111111111111"; private String a2 = "222222222222222222222222"; private String a3 = "333333333333333333333333"; @Benchmark public String typicalChaining() { return new StringBuilder().append(a1).append(a2).append(a3).toString(); } @Benchmark public String noChaining() { StringBuilder sb = new StringBuilder(); sb.append(a1); sb.append(a2); sb.append(a3); return sb.toString(); } } 


Result:



 Benchmark Mode Cnt Score Error Units Chaining.noChaining thrpt 40 8408.703 ± 214.582 ops/s Chaining.typicalChaining thrpt 40 35830.907 ± 1277.455 ops/s 


So, concatenating through the sb.append().append() call chain is 4 times faster ... The author from the article above states that the difference is due to the fact that in the case of the call chain less bytecode is generated and, accordingly, it runs faster.

')

Well, let's check.



Difference in bytecode?



The hypothesis can be easily tested without going into the byte code - create a typical UriBuilder :



 public class UriBuilder { private String schema; private String host; private String path; public UriBuilder setSchema(String schema) { this.schema = schema; return this; } ... @Override public String toString() { return schema + "://" + host + path; } } 


And repeat the benchmark:



 @BenchmarkMode(Mode.Throughput) @Fork(1) @State(Scope.Thread) @Warmup(iterations = 10, time = 1, batchSize = 1000) @Measurement(iterations = 40, time = 1, batchSize = 1000) public class UriBuilderChaining { private String host = "host"; private String schema = "http"; private String path = "/123/123/123"; @Benchmark public String chaining() { return new UriBuilder().setSchema(schema).setHost(host).setPath(path).toString(); } @Benchmark public String noChaining() { UriBuilder uriBuilder = new UriBuilder(); uriBuilder.setSchema(schema); uriBuilder.setHost(host); uriBuilder.setPath(path); return uriBuilder.toString(); } } 


If the reason is really in the amount of bytecode, then even on such a heavy operation as string concatenation, we should see the difference.



Result:



 Benchmark Mode Cnt Score Error Units UriBuilderChaining.chaining thrpt 40 35797.519 ± 2051.165 ops/s UriBuilderChaining.noChaining thrpt 40 36080.534 ± 1962.470 ops/s 


Hmm ... The difference is at the level of error. So the bytecode amount has nothing to do with it. Since the anomaly is manifested with StringBuilder and append() , then this is probably due to the well-known JVM option +XX:OptimizeStringConcat . Let's check. Repeat the very first test, but with the option disabled.



In JMH through annotations you can do it like this:



 @Fork(value = 1, jvmArgsAppend = "-XX:-OptimizeStringConcat") 


Repeat the first test:



 Benchmark Mode Cnt Score Error Units Chaining.noChaining thrpt 40 7598.743 ± 554.192 ops/s Chaining.typicalChaining thrpt 40 7946.422 ± 313.967 ops/s 


Bingo!



Since joining strings with x + y quite a frequent operation in any application - the Hotspot JVM finds new StringBuilder().append(x).append(y).toString() patterns in bytecode and replaces them with optimized machine code , without creating intermediate objects.



Unfortunately, this optimization does not apply to sb.append(x); sb.append(y); sb.append(x); sb.append(y); . The difference on large lines can be on the order.



findings



Use the “chain of call” pattern where possible. First, in the case of StringBuilder this will help the JIT to optimize string concatenation. Secondly, less bytes of code are generated this way and it can really help to inline your method in some cases.



Question on SO

Related report with dirty details from Shipilev.

Source: https://habr.com/ru/post/330220/



All Articles