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