Explicit StringBuilder use is better than implicit one?
kedar mhaswade
kedar.mhaswade at gmail.com
Sun Sep 3 04:07:21 UTC 2017
All,
I have a rather basic question. I wrote a JMH microbenchmark
<https://github.com/kedarmhaswade/jmh-benchmarks/blob/master/src/main/java/org/sample/StringConcatenationBenchmark.java>
for string concatenation [1]. It just concatenates given strings to produce
a longer string.
When I run it on my Mac [2] using something like: java -jar
target/benchmarks.jar StringConcatenationBenchmark -wi 5 -i 20 -f 1
I get an output like:
Benchmark Mode Cnt Score
Error Units
StringConcatenationBenchmark.concatBuilder avgt 20 2768.090 ±
51.711 ns/op
StringConcatenationBenchmark.concatBuilderNaive avgt 20 4347.083 ±
31.835 ns/op
StringConcatenationBenchmark.concatDefault avgt 20 57356.593 ±
415.278 ns/op
My questions are:
1) Have I written the benchmark [1] reasonably correctly?
2) I can understand that the concatBuilder method is faster than
concatBuilderNaive (copying of the buffer is optimal while growing its
size). But looking superficially at the byte code (and JLS), although the
concatDefault method uses StringBuilder just like concatBuilderNaive, it is
*significantly* slower. Even with a smaller approxTotal (1024 bytes,
instead of 4096 bytes), this difference remains (although it is smaller).
How can we explain this anomaly? Have I written a string concatenation
routine that is better than what javac generates? (I must be missing
something).
Regards,
Kedar
[1]
package org.sample;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* <p> A string concatenation benchmark based on Effective Java 2e.
Item 51. </p>
* <p> To make it more appealing and useful, I have considered writing
a hypothetical routine that
* does string concatenation and applies the knowledge of performance
implications. </p>
*/
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class StringConcatenationBenchmark {
int approxTotal;
List<String> strings;
@Setup
public void initialize() {
approxTotal = 1 << 12; // 4096
// each UUID is 36 ASCII characters long, we have about 112
such strings to make it to 4096
int cap = 112;
strings = new ArrayList<>(cap);
for (int i = 0; i < cap; i++) {
boolean a = strings.add(UUID.randomUUID().toString());
assert a;
}
}
@Benchmark
public String concatBuilder() {
StringBuilder sb = new StringBuilder(approxTotal);
for (String s : strings)
sb = sb.append(s);
return sb.toString();
}
@Benchmark
public String concatBuilderNaive() {
StringBuilder sb = new StringBuilder(); // discard
approxTotal, initial cap is 16
for (String s : strings)
sb = sb.append(s);
return sb.toString();
}
@Benchmark
public String concatDefault() {
String str = "";
for (String s : strings)
str += s; // IDE warns!
return str;
}
public static void main(String[] args) {
StringConcatenationBenchmark bm = new StringConcatenationBenchmark();
bm.initialize();
String str1 = bm.concatBuilder();
String str2 = bm.concatDefault();
String str3 = bm.concatBuilderNaive();
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
}
}
[2]
$> uname -a
Darwin C02SG11NG8WL 15.6.0 Darwin Kernel Version 15.6.0: Mon Jan 9
23:07:29 PST 2017; root:xnu-3248.60.11.2.1~1/RELEASE_X86_64 x86_64
$> java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
More information about the jmh-dev
mailing list