
Java StringBuffer and StringBuilder performance - ingve
http://alblue.bandlem.com/2016/04/jmh-stringbuffer-stringbuilder.html
======
vardump
How about just using StringBuilder unless you _really_ (that 0.01% case) need
thread synchronization of StringBuffer?

I'm a C++ programmer mainly, surely all Java programmers know why StringBuffer
is bad by now?

Article also has a pretty pointless microbenchmark.

 _Most_ synthetic microbenchmarks are questionable by default. Even worse when
there's any kind of limited resource utilization such as thread
synchronization involved.

Synthetic microbenchmarks assume all the resources are just for them. Whole
CPU instruction and data cache, all synchronization bandwidth, all memory
bandwidth, etc.

Use profiling instead of microbenchmarks.

~~~
lmm
> How about just using StringBuilder unless you really (that 0.01% case) need
> thread synchronization of StringBuffer?

You wouldn't necessarily know, or notice - your users would just experience
occasional random crashes. How about just using StringBuffer unless you really
(that 0.01% case) need the performance of StringBuilder? As you say, profiling
is the right way to approach these things.

~~~
kodablah
> How about just using StringBuffer unless you really (that 0.01% case) need
> the performance of StringBuilder?

Because when writing new code you should opt-in to thread safety, not out.
Collections are not concurrent by default, there is no `unsynchronized` or
`non-volatile` keywords, etc.
StringBuilder:StringBuffer::HashMap:ConcurrentHashMap. Also, if the
performance benefit requires negligible upfront cost (e.g. providing length to
ArrayList if known) you should do it and build it in your muscle memory as the
default approach.

The problem is StringBuffer (unlike many other parts of the stdlib) was the
only approach available for a long time so people need to change their default
way of thinking. Luckily it's been a long time since 1.5 and basically
everyone has changed.

~~~
kbenson
As a non-java programmer, I'm wondering why you would want to opt-in to a
safety system instead of out of it? My instinct would be to say that you
should be safe be default, and if you find you need performance, then you
verify it won't cause problems in your case and opt out of the default safety
mechanism. Why is defaulting to non-safe ever the right choice?

~~~
jdmichal
Thread-safety is implicitly different than other types of safety: There are
multiple answers to the same problem, and the default one present in
StringBuffer is not necessarily the one you want. For instance, StringBuffer's
thread-safety only guarantees that every write will be executed without
corruption. What are the possible outcomes of this executing in two threads:

    
    
        stringBuffer.append("a").append("b"); // Thread 1
        stringBuffer.append("c").append("d"); // Thread 2
    

The answer is, any of the 6 permutations of "abcd" such that "a" appears
before "b" and "c" appears before "d". However, maybe what you really wanted
was just the two permutations of "abcd" or "cdab".

Just saying, "thread-safe!" doesn't answer the question of _how_ it's thread-
safe and what its behavior will be if multiple threads are contending for it.
All it means is that the program won't crash or corrupt just because multiple
threads are in the same place at once, which is not really all that useful a
guarantee when you're looking at very expensive locking mechanisms to get
there.

~~~
kbenson
I don't consider that a matter of _safety_ , as much as _data integrity_.
Safety, as I've defined it elsewhere within this thread, being "does not cause
memory corruption or crash". If neither technique cause that, then I don't
really have an opinion. If one can cause a crash in certain circumstances, and
the other cannot, I don't think it's responsible to endorse the one that can
cause a crash.

Does that mean you may need to figure out why your data looks weird at some
later date? Possibly. But to me that much preferred to entire program failure
in hard to reason about circumstances, or in memory corruption, which can lead
to the same.

~~~
jdmichal
Actually, I'd rather my program just crash at the point of use than silently
corrupt my data. First, my existent data will be preserved, and after fixing
the issue I can rerun the operation. Second, I will get a clear crash that
immediately tells me where the problem is, instead of getting garbage output
that I have no idea where it sourced from. Oh, and since this is a threading
problem, it's probably even _transient_ data corruption.

~~~
kbenson
That's not how threads work. Without thread safety mechanisms, you don't get
occasional data corruption _or_ the occasional crash, you get _both_ , but
often not at the same time. Occasionally data will be written out of order.
Occasionally there will be enough weirdness you can have a crash. How often
you get one or the other depends on the algorithms in use. Lock safe
methodologies can prevent the crash, and if designed specifically for the data
in question and the data structure supports it, can _possibly_ ensure no
corruption.

Given with strings you probably have data corruption no matter what, do you
want crashes on top of that or not?

~~~
jdmichal
> Lock safe methodologies can prevent the crash, and if designed specifically
> for the data in question and the data structure supports it, can possibly
> ensure no corruption.

Which was the entire point of my post. "Thread-safe" does not guarantee
"usage-safe", and using them interchangeably is a fallacy. If the synchronized
StringBuffer does not provide the guarantees you need anyway, there's no point
in paying the cost of using it.

> Given with strings you probably have data corruption no matter what, do you
> want crashes on top of that or not?

I just said that, yes, I would rather the program crash than silently corrupt
data. I would rather it crash 100% of the time, but I'll take intermittent
crashes instead. A crashed process can be restarted; it's very much harder to
fix data once corruption hits. And if data corruption hits anyway, at least if
I also have a crash I have an idea of where to look for the issue.

------
js2
I was surprised by the difference between the Simple and Chain results.
Naively, I had thought they'd generate the same byte code.

~~~
kodablah
The main deal is that an instance method (e.g. append) uses the latest stack
value as the instance and has its return value put on the stack. So when
chaining, it doesn't need to re-put the variable on the stack to call the next
method. When not chaining, it has to pop (i.e. discard) that previous return
value and then re-put the builder/buffer variable on the stack each time.

But this is seriously micro-opimtization only beneficial in the most strict of
situations. Only in this crazy situations would I ever use this as
justification to choose one approach over the other.

------
leothekim
Wondering if the similarity in performance of StringBuffer and StringBuilder
has to do with lock elision. Can see if the compiler is doing this by
examining the generated bytecode.

~~~
logn
It might. There are other optimizations for synchronization introduced around
Java 1.6 after wisdom around StringBuilder was already common. I think it's
still safe to say StringBuilder at least can't hurt performance. More info:
[https://www.infoq.com/articles/java-threading-
optimizations-...](https://www.infoq.com/articles/java-threading-
optimizations-p1)

Separately, it's interesting to me chained calls can be more efficient.
Personally I prefer these longer, multi-line type of statements from a
readability perspective. So I'm happy it happens to be efficient.

------
briane80
I wonder what effect +OptimiseStringConcat has on those benchmarks. It's
supposed to collapse a lost of .append calls and tries to use the minimum
amount of StringBuilder objects

~~~
jcdavis
Huge, like several times faster (3x when I benchmarked IIRC). I'm a little
surprised the author didn't mention it anywhere - it might account for some of
the differences he saw.

However with a normal JVM the only way to know if its happening (AFAIK) is to
look at the compilation logs or JIT'd assembly, bytecode isn't enough

~~~
alblue
FWIW I didn't mention/test the -XX:+OptimizeStringConcat, because the place
where the changes were being made didn't have that flag enabled and as such
wouldn't have been representative of the use case behind the blog post.

In any case, I re-ran the benchmarks with -appendJvmArgs
-XX:+OptimizeStringConcat and saw no significant difference in the timing on
OSX. You can of course run the gist linked at the bottom and see for yourself
whether there is an impact for your OS but there is JEP280 which is looking at
generating invoke dynamic for Java 9 which will change the measurements again
and make the -XX:+OptimizeStringConcat flag obsolete.

~~~
jcdavis
-XX:+OptimizeStringConcat is enabled by default everywhere I can remember, AFAIK

