Search code examples
javaperformancefluent-interface

Why is a fluent implementation slower than a non-fluent one?


I wrote some test code that compares the speed of using the append() method of StringBuilder eight times sequentially as a fluent interface against invoking it separately in 8 lines.

As fluent:

StringBuilder s = new StringBuilder();
s.append(x)
.append(y)
.append(z); //etc

As non-fluent:

StringBuilder s = new StringBuilder();
s.append(x)
s.append(y)
s.append(z); //etc

Each method was invoked 10 million times. GC was called between each block. The order of doing the versions was reversed with the same result.

My testing shows that the fluent version of the code is about 10% slower (fyi, testing code was fair with matched but unpredictable appends and I gave time for JVM warm up etc).

This is a surprise, as the fluent code is one line.

Why would the non-fluent code be faster?


Solution

  • I suspect it's a feature of some versions of Java.

    If I run the following

    public class Main {
    
        public static final int RUNS = 100000000;
    
        static final ThreadLocal<StringBuilder> STRING_BUILDER_THREAD_LOCAL = new ThreadLocal<StringBuilder>() {
            @Override
            protected StringBuilder initialValue() {
                return new StringBuilder();
            }
        };
    
        public static final StringBuilder myStringBuilder() {
            StringBuilder sb = STRING_BUILDER_THREAD_LOCAL.get();
            sb.setLength(0);
            return sb;
        }
    
        public static long testSeparate(String x, String y, String z) {
            long start = System.nanoTime();
            for (int i = 0; i < RUNS; i++) {
                StringBuilder s = myStringBuilder();
                s.append(x)
                        .append(y)
                        .append(z);
                dontOptimiseAway = s.toString();
            }
            long time = System.nanoTime() - start;
            return time;
        }
    
        public static long testChained(String x, String y, String z) {
            long start = System.nanoTime();
            for (int i = 0; i < RUNS; i++) {
                StringBuilder s = myStringBuilder();
                s.append(x);
                s.append(y);
                s.append(z);
                dontOptimiseAway = s.toString();
            }
            long time = System.nanoTime() - start;
            return time;
        }
    
        static String dontOptimiseAway = null;
    
        public static void main(String... args) {
            for (int i = 0; i < 10; i++) {
                long time1 = testSeparate("x", "y", "z");
                long time2 = testChained("x", "y", "z");
                System.out.printf("Average time separate %.1f ns, chained %.1f ns%n",
                        (double) time1 / RUNS, (double) time2 / RUNS);
            }
        }
    }
    

    with Java 7 update 4

    Average time separate 49.8 ns, chained 49.0 ns
    Average time separate 50.7 ns, chained 49.3 ns
    Average time separate 46.9 ns, chained 46.5 ns
    Average time separate 46.6 ns, chained 46.4 ns
    Average time separate 46.6 ns, chained 46.6 ns
    Average time separate 47.6 ns, chained 47.3 ns
    Average time separate 46.7 ns, chained 47.2 ns
    Average time separate 46.7 ns, chained 47.0 ns
    Average time separate 46.0 ns, chained 46.6 ns
    Average time separate 46.7 ns, chained 46.3 ns
    

    with Java 7 update 10

    Average time separate 50.4 ns, chained 50.0 ns
    Average time separate 50.1 ns, chained 50.1 ns
    Average time separate 45.9 ns, chained 46.5 ns
    Average time separate 46.6 ns, chained 46.7 ns
    Average time separate 46.3 ns, chained 46.4 ns
    Average time separate 46.7 ns, chained 46.5 ns
    Average time separate 46.2 ns, chained 46.4 ns
    Average time separate 46.6 ns, chained 46.0 ns
    Average time separate 46.4 ns, chained 46.2 ns
    Average time separate 45.9 ns, chained 46.2 ns
    

    It might look like there is a slight bias initially but if your run update 10 there is no obvious bias over time.