title: Why is StringBuilder#append(int) faster in Java 7 than in Java 8?
body: <p>While investigating for a <a href="https://stackoverflow.com/q/23748186/507519">little debate</a> w.r.t. using <code>"" + n</code> and <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Integer.html#toString%28int%29" rel="nofollow noreferrer"><code>Integer.toString(int)</code></a> to convert an integer primitive to a string I wrote this <a href="http://openjdk.java.net/projects/code-tools/jmh/" rel="nofollow noreferrer">JMH</a> microbenchmark:</p>
  
  <pre><code>@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;
      }
  }
  </code></pre>
  
  <p>I ran it with the default JMH options with both Java VMs that exist on my Linux machine (up-to-date Mageia 4 64-bit, Intel i7-3770 CPU, 32GB RAM). The first JVM was the one supplied with Oracle JDK
  8u5 64-bit:</p>
  
  <pre><code>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)
  </code></pre>
  
  <p>With this JVM I got pretty much what I expected:</p>
  
  <pre><code>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
  </code></pre>
  
  <p>I.e. using the <code>StringBuilder</code> class is slower due to the additional overhead of creating the <code>StringBuilder</code> object and appending an empty string. Using <code>String.format(String, ...)</code> is even slower, by an order of magnitude or so.</p>
  
  <p>The distribution-provided compiler, on the other hand, is based on OpenJDK 1.7:</p>
  
  <pre><code>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)
  </code></pre>
  
  <p>The results here were <em>interesting</em>:</p>
  
  <pre><code>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
  </code></pre>
  
  <p>Why does <code>StringBuilder.append(int)</code> appear so much faster with this JVM? Looking at the <code>StringBuilder</code> class source code revealed nothing particularly interesting - the method in question is almost identical to <code>Integer#toString(int)</code>. Interestingly enough, appending the result of <code>Integer.toString(int)</code> (the <code>stringBuilder2</code> microbenchmark) does not appear to be faster.</p>
  
  <p>Is this performance discrepancy an issue with the testing harness? Or does my OpenJDK JVM contain optimizations that would affect this particular code (anti)-pattern?</p>
  
  <p><strong>EDIT:</strong></p>
  
  <p>For a more straight-forward comparison, I installed Oracle JDK 1.7u55:</p>
  
  <pre><code>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)
  </code></pre>
  
  <p>The results are similar to those of OpenJDK:</p>
  
  <pre><code>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
  </code></pre>
  
  <p>It seems that this is a more general Java 7 vs Java 8 issue. Perhaps Java 7 had more aggressive string optimizations?</p>
  
  <p><strong>EDIT 2</strong>:</p>
  
  <p>For completeness, here are the string-related VM options for both of these JVMs:</p>
  
  <p>For Oracle JDK 8u5:</p>
  
  <pre><code>$ /usr/java/default/bin/java -XX:+PrintFlagsFinal 2&gt;/dev/null | grep String
       bool OptimizeStringConcat                      = true            {C2 product}
       intx PerfMaxStringConstLength                  = 1024            {product}
       bool PrintStringTableStatistics                = false           {product}
      uintx StringTableSize                           = 60013           {product}
  </code></pre>
  
  <p>For OpenJDK 1.7:</p>
  
  <pre><code>$ java -XX:+PrintFlagsFinal 2&gt;/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}   
  </code></pre>
  
  <p>The <code>UseStringCache</code> option was removed in Java 8 with no replacement, so I doubt that makes any difference. The rest of the options appear to have the same settings.</p>
  
  <p><strong>EDIT 3:</strong></p>
  
  <p>A side-by-side comparison of the source code of the <code>AbstractStringBuilder</code>, <code>StringBuilder</code> and <code>Integer</code> classes from the <code>src.zip</code> file of reveals nothing noteworty. Apart from a whole lot of cosmetic and documentation changes, <code>Integer</code> now has some support for unsigned integers and <code>StringBuilder</code> has been slightly refactored to share more code with <code>StringBuffer</code>. None of these changes seem to affect the code paths used by <code>StringBuilder#append(int)</code>, although I may have missed something.</p>
  
  <p>A comparison of the assembly code generated for <code>IntStr#integerToString()</code> and <code>IntStr#stringBuilder0()</code> is far more interesting. The basic layout of the code generated for <code>IntStr#integerToString()</code> was similar for both JVMs, although Oracle JDK 8u5 seemed to be more aggressive w.r.t. inlining some calls within the <code>Integer#toString(int)</code> code. There was a clear correspondence with the Java source code, even for someone with minimal assembly experience.</p>
  
  <p>The assembly code for <code>IntStr#stringBuilder0()</code>, however, was radically different. The code generated by Oracle JDK 8u5 was once again directly related to the Java source code - I could easily recognise the same layout. On the contrary, the code generated by OpenJDK 7 was almost unrecognisable to the untrained eye (like mine). The <code>new StringBuilder()</code> call was seemingly removed, as was the creation of the array in the <code>StringBuilder</code> constructor. Additionaly, the disassembler plugin was not able to provide as many references to the source code as it did in JDK 8.</p>
  
  <p>I assume that this is either the result of a much more aggressive optimization pass in OpenJDK 7, or more probably the result of inserting hand-written low-level code for certain <code>StringBuilder</code> operations. I am unsure why this optimization does not happen in my JVM 8 implementation or why the same optimizations were not implemented for <code>Integer#toString(int)</code> in JVM 7. I guess someone familiar with the related parts of the JRE source code would have to answer these questions...</p>