1

I ran the following JUnit test case and was able to continuously get good performance results for Stringbuffer than StringBuilder. I'm sure that I'm missing something here but I could not find the reason behind why I get better speed for StringBuffer than StringBuilder.

My test case is,

    @Test
    public void stringTest(){

        String s1 = "s1";
        String s2 = "s2";

        for(int i=0;i<=100000;i++){
            s1 = s1+s2;
        }
        System.out.println(s1);
    }

    @Test
    public void stringBuilderTest(){

        StringBuilder s1 = new StringBuilder("s1");
        String s2 = "s2";

        for(int i=0;i<=100000;i++){
            s1.append(s2);
        }
        System.out.println(s1);
    }


    @Test
    public void stringBufferTest(){

        StringBuffer s1 = new StringBuffer("s1");
        String s2 = "s2";

        for(int i=0;i<=100000;i++){
            s1.append(s2);
        }
        System.out.println(s1);
    }

Please find the JUnit test results,

JUnit Test 1 JUnit Test 2

as you can see in the above results stringBufferTest case has executed sooner than stringBuilderTest case. My question is why is this? I know this is impossible theoretically but how I'm getting this result?


Update

As per the @Henry's comment I removed the SysOuts and results got changed dramatically.

JUnit Test 3 - removing the SysOut

Then I increase the loop count by 100000 -> 1000000 and was able to get some realistic results which I expected all the time,

JUnit Test 3 - removing the SysOut for 1000000 iterations

Well my new questions are,

  1. Why I get a significant performance improvement when I remove my SysOut?
  2. When the load is increased low to high in 1000000 StringBuffer gives the best results over StringBuilder, why is that?
Community
  • 1
  • 1
tk_
  • 16,415
  • 8
  • 80
  • 90
  • Did you try putting them in a different order, i.e. buffer before builder? This could be caused by your buffer using the same string iun memory. – Turtle May 22 '17 at 08:25
  • 2
    I would take out the println from the measurement (takes probably the most time). Also have a look at http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java – Henry May 22 '17 at 08:26
  • @Nathan yes I tried doing the StringBuffer first and still got the same results. – tk_ May 22 '17 at 08:29
  • @Henry thanks for the input I will test once again. – tk_ May 22 '17 at 08:29
  • @thusharaK After your update: you could try to benchmark the `toString()` method of `buffer` and `builder`, maybe that's the root of your strange results. – Turtle May 22 '17 at 09:44
  • Sorry, but looking at eclipse console numbers is really not a solid base for a question. Feel free to drop me a comment when you have data that is solid enough to base a question on ... but for now, I think your question is a perfectly duplicated against that other question. Although that other question doesnt say anything about stringbuilder vs stringbuffer. – GhostCat May 22 '17 at 10:17
  • @GhostCat I have updated the question and have asked some solid question now. Please reopen the question and open the discussion. – tk_ May 22 '17 at 12:57
  • 1
    @GhostCat The post you linked may contain an answer, but these two questions aren't duplicates at all? If I have the same problem, I won't at all end up on the post you marked as duplicate. The question's answer could be "Benchmark invalid", and link to the post you linked. For now, this is clearly a different question, shows researched efforts (he did the tests), and do not deserve a duplicate flag. – Turtle May 22 '17 at 13:03
  • @GhostCat The provided question link directs to a somewhat broad scope which is not what I'm specifically asking here. Therefore, Please be kind enough to reopen the question or vote for reopening of the issue. – tk_ May 23 '17 at 01:23
  • A) printing to System.out is slow, and if you would do some research you would find plenty of explanations B) I don't see how you improved the quality of your benchmarking in any way. There is no point in discussing such kind of measurements. – GhostCat May 23 '17 at 02:43

1 Answers1

2

I'm afraid your benchmark is basically invalid.

Microbenchmarks (little code, completing relatively quickly) are notoriously hard to get right in Java (and in many other languages, too). Some of the problems that make these benchmarks hard:

  • Depending on how the test is written, the compiler might optimize some code away, causing the benchmark to not measure what you wanted to measure.
  • Optimizations that occur at runtime (instead of compile time) often happen incrementally. Initially, the code is interpreted. Only when some parts of it are executed often enough will the Just in Time compiler generate optimized machine code. Microbenchmarks often do not trigger this optimization. http://www.oracle.com/technetwork/java/whitepaper-135217.html has more infos about this topic.
  • Even if the code is eligible for optimization, depending on how it's written, the Just in Time compiler might not be able to patch it out completely, so it has to fall back to suboptimal optimizations. Search for "On stack replacement" for more details.

This article from Oracle goes into more details on these problems: http://www.oracle.com/technetwork/articles/java/architect-benchmarking-2266277.html

In the end, it comes down to this: unless you're very experienced in this, don't write microbenchmarks from scratch. The results you get have nothing to do with how that code would perform in a real application.

Use a framework like JMH (Java Microbenchmark Harness) instead. The article I've linked above contains an intro to JMH, but there are other tutorials about it as well (e.g. http://java-performance.info/jmh/).

Invest some time and learn to use JMH. It's worth it. When you run your benchmarks with JMH, you will see in its output how drastically the benchmark times change over time due to JVM optimizations taking place (JMH calls your tests multiple times).

If you run your StringBuilder vs. StringBuffer tests in JMH, you should see that both classes perform just about the same on modern CPUs. StringBuilder is slightly faster, but not by that much. Still, I'd use StringBuilder, as it's slightly faster.

Rolf Schäuble
  • 690
  • 4
  • 15
  • This StackOverflow article might also contain useful information about microbenchmarks: http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java?noredirect=1&lq=1 – Rolf Schäuble May 22 '17 at 10:00
  • thanks for the detailed answer. your last line kinda contradicts with the earlier line. Why would you use Stringbuffer? – tk_ May 22 '17 at 12:50
  • 1
    @thusharaK He probably meant that the difference is not great enough to give up on the thread-safe property. Even if you don't need it, he uses it for consistency? – Turtle May 22 '17 at 13:12
  • @Nathan I think so. – tk_ May 23 '17 at 03:04
  • 1
    @thusharaK That was a mistake in the last line, confusing the two classes. I have corrected it. – Rolf Schäuble May 23 '17 at 08:45
  • @Nathan nope, was just a mistake of mine when I wrote that line. – Rolf Schäuble May 23 '17 at 08:46