Performance benefits of a static empty array instance

前端 未结 4 1657
夕颜
夕颜 2020-12-11 17:47

It seems common practice to extract a constant empty array return value into a static constant. Like here:

public class NoopParser implements Parser {
    pr         


        
4条回答
  •  借酒劲吻你
    2020-12-11 18:08

    I'm most interested in the actual performance difference between these two idioms in practical, real-world situations. I have no experience in micro-benchmarking (and it is probably not the right tool for such a question) but I gave it a try anyway.

    This benchmark models a somewhat more typical, "realistic" setting. The returned array is just looked at and then discarded. No references hanging around, no requirement for reference equality.

    One interface, two implementations:

    public interface Parser {
        String[] supportedSchemas();
        void parse(String s);
    }
    
    public class NoopParserStaticArray implements Parser {
        private static final String[] EMPTY_STRING_ARRAY = new String[0];
    
        @Override public String[] supportedSchemas() {
            return EMPTY_STRING_ARRAY;
        }
    
        @Override public void parse(String s) {
            s.codePoints().count();
        }
    }
    
    public class NoopParserNewArray implements Parser {
        @Override public String[] supportedSchemas() {
            return new String[0];
        }
    
        @Override public void parse(String s) {
            s.codePoints().count();
        }
    }
    

    And the JMH benchmark:

    import org.openjdk.jmh.annotations.Benchmark;
    
    public class EmptyArrayBenchmark {
        private static final Parser NOOP_PARSER_STATIC_ARRAY = new NoopParserStaticArray();
        private static final Parser NOOP_PARSER_NEW_ARRAY = new NoopParserNewArray();
    
        @Benchmark
        public void staticEmptyArray() {
            Parser parser = NOOP_PARSER_STATIC_ARRAY;
            for (String schema : parser.supportedSchemas()) {
                parser.parse(schema);
            }
        }
    
        @Benchmark
        public void newEmptyArray() {
            Parser parser = NOOP_PARSER_NEW_ARRAY;
            for (String schema : parser.supportedSchemas()) {
                parser.parse(schema);
            }
        }
    }
    

    The result on my machine, Java 1.8.0_51 (HotSpot VM):

    Benchmark                              Mode  Cnt           Score          Error  Units
    EmptyArrayBenchmark.staticEmptyArray  thrpt   60  3024653836.077 ± 37006870.221  ops/s
    EmptyArrayBenchmark.newEmptyArray     thrpt   60  3018798922.045 ± 33953991.627  ops/s
    EmptyArrayBenchmark.noop              thrpt   60  3046726348.436 ±  5802337.322  ops/s
    

    There is no significant difference between the two approaches in this case. In fact, they are indistinguishable from the no-op case: apparently the JIT compiler recognises that the returned array is always empty and optimises the loop away entirely!

    Piping parser.supportedSchemas() into the black hole instead of looping over it, gives the static array instance approach a ~30% advantage. But they're definitely of the same magnitude:

    Benchmark                              Mode  Cnt           Score         Error  Units
    EmptyArrayBenchmark.staticEmptyArray  thrpt   60   338971639.355 ±  738069.217  ops/s
    EmptyArrayBenchmark.newEmptyArray     thrpt   60   266936194.767 ±  411298.714  ops/s
    EmptyArrayBenchmark.noop              thrpt   60  3055609298.602 ± 5694730.452  ops/s
    

    Perhaps in the end the answer is the usual "it depends". I have a hunch that in many practical scenarios, the performance benefit in factoring out the array creation is not significant.

    I think it is fair to say that

    • if the method contract gives you the freedom to return a new empty array instance every time, and
    • unless you need to guard against problematic or pathological usage patterns and/or aim for theoretical max performance,

    then returning new String[0] directly is fine.

    Personally, I like the expressiveness and concision of return new String[0]; and not having to introduce an extra static field.


    By some strange coincidence, a month after I wrote this a real performance engineer investigated the problem: see this section in Alexey Shipilёv's blog post 'Arrays of Wisdom of the Ancients':

    As expected, the only effect whatsoever can be observed on a very small collection sizes, and this is only a marginal improvement over new Foo[0]. This improvement does not seem to justify caching the array in the grand scheme of things. As a teeny tiny micro-optimization, it might make sense in some tight code, but I wouldn’t care otherwise.

    That settles it. I'll take the tick mark and dedicate it to Alexey.

提交回复
热议问题