2014-11-28

String Concatenation vs StringBuilder Performance Benchmark

This single Java class benchmark, shows impressively that the penalty for using StringBuilder instead of regular String concatenation can be around 5x! Many static code analyzers like PMD / SonarQube deceptively claim the opposite. This assumption might be a vestige from the pre Java 5 era. Indeed some developers even concatenate compile time constants (static final) using StringBuilder.

Although be warned, when it comes to using effectively non-final variables inside the concatenation, StringBuilder will drastically outperform the regular String concatenation by several orders of magnitude. Effectively non-final variables are references that are not fixed. On the other hand effectively final values do not necessarily need be prefixed with the "final" keyword but they could be, which differentiates them from effectively non-final variables, that cannot be prefixed with the "final" keyword without causing a compilation error.

To state it clear & simple: whenever you absolutely have to use the construct += which is the equivalent for 
someString = someString + someOtherString
...and cannot paraphrase it with simple standalone + (pluses), please, for the love of God, use StringBuilder!

As the rule of thumb you should remember: If you can make a string "final", do not use StringBuilder or pay the performance penalty. If you cannot prefix a string with the "final" keyword, seriously consider to use StringBuilder.








/**
 * A simple benchmark to compare regular String concatenation
 * with the StringBuilder implementation.
 */
public class ConcatenationVsStringBuilderPerformance
{
   private static final int NUMBER_OF_ITERATIONS = 1234567;
   private static final int START_ITERATION_FROM = 0;
   private static final int LOWER_NUMBER_OF_ITERATIONS = 12345;
   private String a = "AAAA";
   private String b = "BBBB";
   private String c = "CCCC";
   private String d = "DDDD";
   private String e = "EEEE";
   private String f = "FFFF";

   private String useStringBuilder()
   {
      StringBuilder sb = new StringBuilder(100);
      return sb.append("{a:").append(a)
            .append(", b:").append(b)
            .append(", c:").append(c)
            .append(", d:").append(d)
            .append(", e:").append(e)
            .append(", f:").append(f)
            .append("}")
            .toString();
   }

   private String useStringBuilder(int number)
   {
      StringBuilder sb = new StringBuilder(100);
      return sb.append("{a:").append(a)
            .append(", b:").append(b)
            .append(", c:").append(c)
            .append(", d:").append(d)
            .append(", e:").append(e)
            .append(", f:").append(f)
            .append(", number:").append(number)
            .append("}")
            .toString();
   }

   private String fastStringBuilder(int number)
   {
      StringBuilder sb = new StringBuilder();
      sb.append("{a:").append(a)
            .append(", b:").append(b)
            .append(", c:").append(c)
            .append(", d:").append(d)
            .append(", e:").append(e)
            .append(", f:").append(f);

      for (int idx = START_ITERATION_FROM; idx < number; idx++)
      {
         sb.append(", number:").append(number);
      }
      sb.append("}");

      return sb.toString();
   }

   private String useStringBuilderWithoutInitCapacity()
   {
      StringBuilder sb = new StringBuilder();
      return sb.append("{a:").append(a)
            .append(", b:").append(b)
            .append(", c:").append(c)
            .append(", d:").append(d)
            .append(", e:").append(e)
            .append(", f:").append(f)
            .append("}")
            .toString();
   }

   private String concatenate()
   {
      // string can be final      final String s = "{a:" + a + ", b:" + b + ", c:" + c + ", d:" + d + ", e:" + e + ", f:" + f + "}";

      return s;
   }

   private String concatenate(int number)
   {
      // string can be final      final String s = "{a:" + a + ", b:" + b + ", c:" + c + ", d:" + d + ", e:" + e + ", f:" + f + ", number:" + number + "}";

      return s;
   }

   private String slowRegularConcatenation(int number)
   {
      // string cannot be final      String s = "{a:" + a + ", b:" + b + ", c:" + c + ", d:" + d + ", e:" + e + ", f:" + f;
      for (int idx = START_ITERATION_FROM; idx < number; idx++)
      {
         s += ", number:" + number;
      }
      s += "}";

      return s;
   }
 /**
 * Run benchmark
 *
 * @param args can be empty
 */
   public static void main(String[] args)
   {
      // Do not use StringBuilder in this case      useSbWithVariable();
      useSbWithoutVariable();
      useSbWithoutInitCapacity();
      useConcatenation();
      useConcatenationWithVariable();

      // This is a completely different use case,      // use StringBuilder in this case      fastSbWithVariableInfix();
      slowConcatenationWithNonFinalString();
   }

   private static void useSbWithVariable()
   {
      long start = System.currentTimeMillis();
      for (int idx = START_ITERATION_FROM; idx < NUMBER_OF_ITERATIONS; idx++)
      {
         ConcatenationVsStringBuilderPerformance test = new ConcatenationVsStringBuilderPerformance();
         test.useStringBuilder(idx);
      }
      long end = System.currentTimeMillis();
      System.out.println("Use StringBuilder with variable: "            + String.valueOf(end - start) + " ms");
   }

   private static void useSbWithoutVariable()
   {
      long start = System.currentTimeMillis();
      for (int idx = START_ITERATION_FROM; idx < NUMBER_OF_ITERATIONS; idx++)
      {
         ConcatenationVsStringBuilderPerformance test = new ConcatenationVsStringBuilderPerformance();
         test.useStringBuilder();
      }
      long end = System.currentTimeMillis();
      System.out.println("Use StringBuilder without variable: "            + String.valueOf(end - start) + " ms");
   }

   private static void useSbWithoutInitCapacity()
   {
      long start = System.currentTimeMillis();
      for (int idx = START_ITERATION_FROM; idx < NUMBER_OF_ITERATIONS; idx++)
      {
         ConcatenationVsStringBuilderPerformance test = new ConcatenationVsStringBuilderPerformance();
         test.useStringBuilderWithoutInitCapacity();
      }
      long end = System.currentTimeMillis();
      System.out.println("Use StringBuilder without variable but also without initial capacity (worst performance): "            + String.valueOf(end - start) + " ms");
   }

   private static void useConcatenation()
   {
      long start = System.currentTimeMillis();
      for (int idx = START_ITERATION_FROM; idx < NUMBER_OF_ITERATIONS; idx++)
      {
         ConcatenationVsStringBuilderPerformance test = new ConcatenationVsStringBuilderPerformance();
         test.concatenate();
      }
      long end = System.currentTimeMillis();
      System.out.println("Use regular concatenation without variable (best performance): "            + String.valueOf(end - start) + " ms");
   }

   private static void useConcatenationWithVariable()
   {
      long start = System.currentTimeMillis();
      for (int idx = START_ITERATION_FROM; idx < NUMBER_OF_ITERATIONS; idx++)
      {
         ConcatenationVsStringBuilderPerformance test = new ConcatenationVsStringBuilderPerformance();
         test.concatenate(idx);
      }
      long end = System.currentTimeMillis();
      System.out.println("Use regular concatenation with variable (performs bettter compared to StringBuilder): "            + String.valueOf(end - start) + " ms");
   }

   private static void fastSbWithVariableInfix()
   {
      long start = System.currentTimeMillis();

      ConcatenationVsStringBuilderPerformance test = new ConcatenationVsStringBuilderPerformance();
      System.out.println("Character length = "            + test.fastStringBuilder(LOWER_NUMBER_OF_ITERATIONS).length());

      long end = System.currentTimeMillis();
      System.out.println("Use StringBuilder in this case: "            + String.valueOf(end - start) + " ms");
   }

   private static void slowConcatenationWithNonFinalString()
   {
      long start = System.currentTimeMillis();

      ConcatenationVsStringBuilderPerformance test = new ConcatenationVsStringBuilderPerformance();
      System.out.println("Character length = "            + test.slowRegularConcatenation(LOWER_NUMBER_OF_ITERATIONS).length());

      long end = System.currentTimeMillis();
      System.out.println("Do not use regular string concatenation in this case: "            + String.valueOf(end - start) + " ms");
   }
}
These are the results running this performance benchmark on Mac OS X Yosemite 10.10.1 with 2.3 GHz Intel Core i7 with JVM 8u25:
/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/... ConcatenationVsStringBuilderPerformance
Connected to the target VM, address: '127.0.0.1:63973', transport: 'socket' 
Use StringBuilder with variable: 298 ms
Use StringBuilder without variable: 244 ms
Use StringBuilder without variable but also without initial capacity (worst performance): 349 ms
Use regular concatenation without variable (best performance): 56 ms
Use regular concatenation with variable (performs bettter compared to StringBuilder): 107 ms
Character length to assure the same work amount = 172878
Use StringBuilder in this case: 1 ms  // This is not comparable to the above results
Character length to assure the same work amount = 172878
Do not use regular string concatenation in this case: 1255 ms // but this is comparable to the "1 ms" result, ...wow!