When exploring the little wrt debate using "" + n and Integer.toString(int) to convert an integer primitive to a string, I wrote this JMH microbenchmark:
@Fork(1) @OutputTimeUnit(TimeUnit.MILLISECONDS) @State(Scope.Benchmark) public class IntStr { protected int counter; @GenerateMicroBenchmark public String integerToString() { return Integer.toString(this.counter++); } @GenerateMicroBenchmark public String stringBuilder0() { return new StringBuilder().append(this.counter++).toString(); } @GenerateMicroBenchmark public String stringBuilder1() { return new StringBuilder().append("").append(this.counter++).toString(); } @GenerateMicroBenchmark public String stringBuilder2() { return new StringBuilder().append("").append(Integer.toString(this.counter++)).toString(); } @GenerateMicroBenchmark public String stringFormat() { return String.format("%d", this.counter++); } @Setup(Level.Iteration) public void prepareIteration() { this.counter = 0; } }
I ran it with default JMH settings with Java virtual machines that exist on my Linux machine (modern Mageia 4 64-bit Intel i7-3770 processor, 32 GB of RAM). The first JVM was the one that came with the Oracle JDK 8u5 64-bit:
java version "1.8.0_05" Java(TM) SE Runtime Environment (build 1.8.0_05-b13) Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)
With this JVM, I got almost what I expected:
Benchmark Mode Samples Mean Mean error Units b.IntStr.integerToString thrpt 20 32317.048 698.703 ops/ms b.IntStr.stringBuilder0 thrpt 20 28129.499 421.520 ops/ms b.IntStr.stringBuilder1 thrpt 20 28106.692 1117.958 ops/ms b.IntStr.stringBuilder2 thrpt 20 20066.939 1052.937 ops/ms b.IntStr.stringFormat thrpt 20 2346.452 37.422 ops/ms
those. using the StringBuilder class is slower due to the additional overhead of creating a StringBuilder and adding an empty string. Using String.format(String, ...) even slower, an order or so.
The compiler provided by the distribution, on the other hand, is based on OpenJDK 1.7:
java version "1.7.0_55" OpenJDK Runtime Environment (mageia-2.4.7.1.mga4-x86_64 u55-b13) OpenJDK 64-Bit Server VM (build 24.51-b03, mixed mode)
The results here were interesting:
Benchmark Mode Samples Mean Mean error Units b.IntStr.integerToString thrpt 20 31249.306 881.125 ops/ms b.IntStr.stringBuilder0 thrpt 20 39486.857 663.766 ops/ms b.IntStr.stringBuilder1 thrpt 20 41072.058 484.353 ops/ms b.IntStr.stringBuilder2 thrpt 20 20513.913 466.130 ops/ms b.IntStr.stringFormat thrpt 20 2068.471 44.964 ops/ms
Why StringBuilder.append(int) appear much faster with this JVM? Looking at the source code of the StringBuilder class, I did not find anything particularly interesting - this method is almost identical to Integer#toString(int) . It is interesting to note that adding the result Integer.toString(int) (microobject stringBuilder2 ) does not seem to be faster.
Is this performance mismatch a problem with the test harness? Or does my OpenJDK JVM contain optimizations that will affect this particular (anti) pattern code?
EDIT:
For a more direct comparison, I installed Oracle JDK 1.7u55:
java version "1.7.0_55" Java(TM) SE Runtime Environment (build 1.7.0_55-b13) Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)
The results are similar to OpenJDK:
Benchmark Mode Samples Mean Mean error Units b.IntStr.integerToString thrpt 20 32502.493 501.928 ops/ms b.IntStr.stringBuilder0 thrpt 20 39592.174 428.967 ops/ms b.IntStr.stringBuilder1 thrpt 20 40978.633 544.236 ops/ms
This seems to be a more general Java 7 vs Java 8. issue. Perhaps Java 7 had more aggressive string optimization?
EDIT 2 :
For completeness, here are the string-related VM parameters for both of these JVMs:
For Oracle JDK 8u5:
$ /usr/java/default/bin/java -XX:+PrintFlagsFinal 2>/dev/null | grep String bool OptimizeStringConcat = true {C2 product} intx PerfMaxStringConstLength = 1024 {product} bool PrintStringTableStatistics = false {product} uintx StringTableSize = 60013 {product}
For OpenJDK 1.7:
$ java -XX:+PrintFlagsFinal 2>/dev/null | grep String bool OptimizeStringConcat = true {C2 product} intx PerfMaxStringConstLength = 1024 {product} bool PrintStringTableStatistics = false {product} uintx StringTableSize = 60013 {product} bool UseStringCache = false {product}
The UseStringCache option UseStringCache been removed in Java 8 without replacement, so I doubt it matters. Other parameters have the same settings.
EDIT 3:
A comparative comparison of the source code for the AbstractStringBuilder , StringBuilder and Integer classes from the src.zip file shows nothing remarkable. Besides a lot of changes in cosmetics and documentation, Integer now has some support for unsigned integers, and StringBuilder been slightly reorganized to share more code with StringBuffer . None of these changes affect the code paths used by StringBuilder#append(int) , although I may have missed something.
Comparing the assembly code generated for IntStr#integerToString() and IntStr#stringBuilder0() is much more interesting. The basic layout of the code generated for IntStr#integerToString() was the same for both JVMs, although the Oracle JDK 8u5 seemed more aggressive wrt by inserting some calls into Integer#toString(int) code. There was a clear correspondence with the Java source code, even for those with minimal build experience.
The build code for IntStr#stringBuilder0() , however, was radically different. The code generated by Oracle JDK 8u5 was again directly linked to the Java source code - I could easily recognize the same layout. On the contrary, the code created by OpenJDK 7 was almost unrecognizable for the unprepared eye (for example, for me). The call to new StringBuilder() would seem to be removed, as was the creation of the array in the StringBuilder constructor. In addition, the disassembler plugin was unable to provide as many references to the source code as in JDK 8.
I assume that this is either the result of a more aggressive optimization transition in OpenJDK 7, or rather, the result of entering handwritten low-level code for certain StringBuilder operations. I am not sure why this optimization is not performed in my JVM 8 implementation or why the same optimizations for Integer#toString(int) were not implemented in JVM 7. Probably someone who is familiar with the relevant parts of the JRE source code will have to answer these questions ...