String Concatenation optimization

From EggeWiki
Revision as of 00:17, 9 July 2008 by Egge (talk | contribs) (New page: I often see developers needlessly using StringBuffer.append in preference to the built in string concatenation operator. This is based on the idea that the "+" operator always creates a n...)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

I often see developers needlessly using StringBuffer.append in preference to the built in string concatenation operator. This is based on the idea that the "+" operator always creates a new object. While in general it does, a compiler is allowed to optimise this with an alternative implementation. However, is there any speed difference, or should one care? Well, I ran some tests to find out. Here's the tests I created:

<geshi lang="java"> private static final P TEST1a = new P() { public String print(int i) { return "" + i + ", " + ++i + ", " + ++i; } };

private static final P TEST1b = new P() { public String print(int i) { return i + ", " + ++i + ", " + ++i; } };

private static final P TEST2 = new P() { public String print(int i) { StringBuffer sb = new StringBuffer(); sb.append(i); sb.append(", "); sb.append(++i); sb.append(", "); sb.append(++i); return sb.toString(); } };

private static final P TEST3 = new P() { public String print(int i) { StringBuilder sb = new StringBuilder(); sb.append(i); sb.append(", "); sb.append(++i); sb.append(", "); sb.append(++i); return sb.toString(); } };

private static final P TEST4 = new P() { public String print(int i) { return new StringBuilder() .append(i) .append(", ") .append(++i) .append(", ") .append(++i).toString(); } }; </geshi>

And here's my test harness: <geshi lang="java"> public void testToString() {

List

tests = Arrays.asList(TEST1a, TEST1b, TEST2, TEST3, TEST4); for (P p : tests) { long l = 0; long start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { l += p.print(i).length(); } assertEquals(246666691L, l); System.out.println(System.currentTimeMillis() - start); } } </geshi> And here are the results:

7391
8954
12344
7500
7376

Well TEST1a and TEST4 appear to run at about the same speed. Running the excellent Jad decompiler we can see that they produce the same byte code. You might see it as strange that TEST1a and TEST1b differ. This is because TEST1a produces slightly different byte code.

<geshi lang="java">

   private static final P TEST1a = new P() {
       public String print(int i)
       {
           return (new StringBuilder()).append(i).append(", ").append(++i).append(", ").append(++i).toString();
       }
   }
   private static final P TEST1b = new P() {
       public String print(int i)
       {
           return (new StringBuilder(String.valueOf(i))).append(", ").append(++i).append(", ").append(++i).toString();
       }
   }

</geshi>

Without the initial "", the compiler uses the String.valueOf, as it can't pass the integer to the StringBuilder directly. String.valueOf calls Integer.toString. However, StringBuilder.append(int) contains a better performing implementation. It can do this because it assumes a radix of 10 and it can write directly to the buffer.

The other thing to note is of course StringBuilder is faster than StringBuffer. By letting the compiler optimize the code, you can enjoy the benefits of faster implementations in future versions.