Explicit StringBuilder use is better than implicit one?

kedar mhaswade kedar.mhaswade at gmail.com
Mon Sep 4 22:46:09 UTC 2017


Hi Claes,

Thank you. I should have looked at the bytecode more closely. Your
explanation makes sense.

I will report the numbers with Java 9 soon.

Regards,
Kedar

On Sun, Sep 3, 2017 at 5:31 AM, Claes Redestad <claes.redestad at oracle.com>
wrote:

> Hi Kedar,
>
> your benchmark seems sound.
>
> I think it's rather the case that the default String concatenation emitted
> by javac is limited to each call site, thus in a loop like this it is
> expanded not into one StringBuilder operation, but one for every step in
> the loop, i.e., the equivalent of:
>
> String str = "";
> for (...) {
> str = new StringBuilder(str).append(s).toString();
> }
> return str;
>
> Which means this routine allocates a StringBuilder and a String for each
> iteration (and likely expands the SB internal array too). So we're
> benchmarking something doing 112×2 or even 112×3 allocations and copies
> with something doing two or three. Seems 10x is within reason.
>
> Now, if you rebuild and run your benchmark with java 9, my guess is you
> will see the cost of this slow case looking a bit better. This is because
> optimizations to indify string concat which may help the JIT eliminate more
> of the costly intermediate steps.
>
> Still, might be worth avoiding naive concat loops even then, if
> performance is really critical.
>
> Thanks!
>
> /Claes
>
> kedar mhaswade <kedar.mhaswade at gmail.com> skrev: (3 september 2017
> 06:07:21 CEST)
> >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)
>
> --
> Sent from my Android device with K-9 Mail. Please excuse my brevity.
>


More information about the jmh-dev mailing list