String Concatenation Options
There’s a new inspection in IDEA 8 (might just be in the EAP at this point) that will convert string concatentation to a variety of different approaches. One of these options is to use String.format(). I started applying this option to some code I’m working because it’s certainly more readable than some of the concatenation stuff I’d been doing. But I started thinking that I should probably profile this before I get too crazy with it just to make sure I’m not hamstringing myself with this. So I wrote a simple test to see what the fastest option was and I was a little surprised by the results.
First, let’s see the code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | import java.text.*; public class test { private static long concat(int count) { long start = System.currentTimeMillis(); for(int x = 0; x < count; x++) { String s = "Loop " + x + " of " + count + " iterations."; } return System.currentTimeMillis() - start; } private static long format(int count) { long start = System.currentTimeMillis(); for(int x = 0; x < count; x++) { String s = String.format("Loop %s of %s iterations." , x, count); } return System.currentTimeMillis() - start; } private static long format2(int count) { long start = System.currentTimeMillis(); for(int x = 0; x < count; x++) { String s = MessageFormat.format("Loop {0} of {1} iterations." , x, count); } return System.currentTimeMillis() - start; } private static long append(int count) { long start = System.currentTimeMillis(); for(int x = 0; x < count; x++) { String s = new StringBuilder("Loop ") .append(x) .append(" of ") .append(count) .append("iterations.") .toString(); } return System.currentTimeMillis() - start; } public static void main(String[] args) { int count = 1000000; for(int x = 0; x < 10; x++) { System.out.println("concat = " + concat(count)); System.out.println("String.format = " + format(count)); System.out.println("MessageFormat.format = " + format2(count)); System.out.println("append = " + append(count)); System.out.println(); } } } |
This admittedly naive “benchmark” runs through four options and prints out the basic timing results. I’ve compiled the results below in a table:
| concat | String.format | MessageFormat.format | append |
|---|---|---|---|
| 408 | 3164 | 9099 | 376 |
| 338 | 2876 | 8559 | 340 |
| 300 | 3013 | 8655 | 398 |
| 342 | 2938 | 8511 | 311 |
| 308 | 2911 | 8570 | 310 |
| 306 | 2924 | 8726 | 320 |
| 316 | 3019 | 9006 | 414 |
| 306 | 2994 | 8673 | 331 |
| 346 | 3022 | 9588 | 311 |
| 312 | 2988 | 8590 | 313 |
As you can see both format methods take considerably more time than the other two. I was a little surprised to see this though the magnitude of the difference was more surprising than than the difference itself. So neither of those are options you’ll want to consider in areas that get called often. The one that was really suprising for me was the relative similarity between concatenating strings and appending using StringBuilder. While StringBuilder was generally faster than string concats, the difference was rather minimal and in some runs actually slower. What this says to me is that the generally accepted “wisdom” that String concatenation is slower than using StringBuilder is clearly wrong.
On the other, I’m not really a performance expert so I might be doing something stupid here or missing something fundamental. The test seems rather straightforward, though. What do you think?

via twitter, I was reminded that the compiler optimizes many of those string concats which I’d forgotten about. So one “mystery” solved at least. At least I know for sure not to use format() which is what I really wanted to know anyway. It’s shame because that certainly is easier to read. Oh, well.
You can add one more option:
String s = new StringBuilder(128).append(“Loop “)…..
This is useful for longer Strings.
I updated my test and tried that and it did run consistently faster but only for an aggregate ~6ms over a million runs. So not much of an improvement but might help in some cases. Maybe.
Can we see the variance between runs?
I calculated the variances myself. GC runs quite often but on my kit always for less than .2ms. I’ve just finished running an object creation profiler. The top 5 objects in this test are char[] from 3 different sources, String from a single source, and StringBuilder.
1) char[] from I don’t know where.
2) char[] from
java.util.Arrays.copyOfRange(Arrays.java:3209)
java.lang.String.(String.java:216)
java.lang.StringBuilder.toString(StringBuilder.java:430)
Test.concat(Test.java:8)
Test.main(Test.java:46)
3) char[] from
java.lang.AbstractStringBuilder.(AbstractStringBuilder.java:45)
java.lang.StringBuilder.(StringBuilder.java:68)
Test.concat(Test.java:8)
Test.main(Test.java:46)
4) String from
java.lang.String.(String.java:203)
java.lang.StringBuilder.toString(StringBuilder.java:430)
Test.concat(Test.java:8)
Test.main(Test.java:46)
5) StringBuilder from
java.lang.AbstractStringBuilder.(AbstractStringBuilder.java:44)
java.lang.StringBuilder.(StringBuilder.java:68)
Test.concat(Test.java:8)
Test.main(Test.java:46)
All the top 5 sources of objects appear to come from the concat. If so, this microbenchmark may not properly account for the cost of a proper object lifecycle. IE, GC of objects quickly thrown away is close to 0. If you don’t throw objects away like is done in this bench, gc cost should skyrocket for concat.