Are Java static calls more or less expensive than non-static calls?

前端 未结 12 2351
夕颜
夕颜 2020-11-27 11:57

Is there any performance benefit one way or another? Is it compiler/VM specific? I am using Hotspot.

相关标签:
12条回答
  • 2020-11-27 12:58

    It is compiler/VM specific.

    • In theory, a static call can be made slightly more efficient because it doesn't need to do a virtual function lookup, and it can also avoid the overhead of the hidden "this" parameter.
    • In practice, many compilers will optimize this out anyway.

    Hence it's probably not worth bothering about unless you have identified this as a truly critical performance issue in your application. Premature optimization is the root of all evil etc...

    However I have seen this optimization give a substantial performance increase in the following situation:

    • Method performing a very simple mathematical calculation with no memory accesses
    • Method being invoked millions of times per second in a tight inner loop
    • CPU bound application where every bit of performance mattered

    If the above applies to you, it may be worth testing.

    There is also one other good (and potentially even more important!) reason to use a static method - if the method actually has static semantics (i.e. logically is not connected to a given instance of the class) then it makes sense to make it static to reflect this fact. Experienced Java programmers will then notice the static modifier and immediately think "aha! this method is static so it doesn't need an instance and presumably doesn't manipulate instance specific state". So you will have communicated the static nature of the method effectively....

    0 讨论(0)
  • 2020-11-27 13:00

    Four years later...

    Okay, in the hope of settling this question once and forever, I have written a benchmark which shows how the different kinds of calls (virtual, non-virtual, static) compare to each other.

    I ran it on ideone, and this is what I got:

    (Larger number of iterations is better.)

        Success time: 3.12 memory: 320576 signal:0
      Name          |  Iterations
        VirtualTest |  128009996
     NonVirtualTest |  301765679
         StaticTest |  352298601
    Done.
    

    As expected, virtual method calls are the slowest, non-virtual method calls are faster, and static method calls are even faster.

    What I did not expect was the differences to be so pronounced: Virtual method calls were measured to run at less than half the speed of non-virtual method calls, which in turn were measured to run a whole 15% slower than static calls. That's what these measurements show; the actual differences must in fact be slightly more pronounced, since for each virtual, nonvirtual, and static method call, my benchmarking code has an additional constant overhead of incrementing one integer variable, checking a boolean variable, and looping if not true.

    I suppose the results will vary from CPU to CPU, and from JVM to JVM, so give it a try and see what you get:

    import java.io.*;
    
    class StaticVsInstanceBenchmark
    {
        public static void main( String[] args ) throws Exception
        {
            StaticVsInstanceBenchmark program = new StaticVsInstanceBenchmark();
            program.run();
        }
    
        static final int DURATION = 1000;
    
        public void run() throws Exception
        {
            doBenchmark( new VirtualTest( new ClassWithVirtualMethod() ), 
                         new NonVirtualTest( new ClassWithNonVirtualMethod() ), 
                         new StaticTest() );
        }
    
        void doBenchmark( Test... tests ) throws Exception
        {
            System.out.println( "  Name          |  Iterations" );
            doBenchmark2( devNull, 1, tests ); //warmup
            doBenchmark2( System.out, DURATION, tests );
            System.out.println( "Done." );
        }
    
        void doBenchmark2( PrintStream printStream, int duration, Test[] tests ) throws Exception
        {
            for( Test test : tests )
            {
                long iterations = runTest( duration, test );
                printStream.printf( "%15s | %10d\n", test.getClass().getSimpleName(), iterations );
            }
        }
    
        long runTest( int duration, Test test ) throws Exception
        {
            test.terminate = false;
            test.count = 0;
            Thread thread = new Thread( test );
            thread.start();
            Thread.sleep( duration );
            test.terminate = true;
            thread.join();
            return test.count;
        }
    
        static abstract class Test implements Runnable
        {
            boolean terminate = false;
            long count = 0;
        }
    
        static class ClassWithStaticStuff
        {
            static int staticDummy;
            static void staticMethod() { staticDummy++; }
        }
    
        static class StaticTest extends Test
        {
            @Override
            public void run()
            {
                for( count = 0;  !terminate;  count++ )
                {
                    ClassWithStaticStuff.staticMethod();
                }
            }
        }
    
        static class ClassWithVirtualMethod implements Runnable
        {
            int instanceDummy;
            @Override public void run() { instanceDummy++; }
        }
    
        static class VirtualTest extends Test
        {
            final Runnable runnable;
    
            VirtualTest( Runnable runnable )
            {
                this.runnable = runnable;
            }
    
            @Override
            public void run()
            {
                for( count = 0;  !terminate;  count++ )
                {
                    runnable.run();
                }
            }
        }
    
        static class ClassWithNonVirtualMethod
        {
            int instanceDummy;
            final void nonVirtualMethod() { instanceDummy++; }
        }
    
        static class NonVirtualTest extends Test
        {
            final ClassWithNonVirtualMethod objectWithNonVirtualMethod;
    
            NonVirtualTest( ClassWithNonVirtualMethod objectWithNonVirtualMethod )
            {
                this.objectWithNonVirtualMethod = objectWithNonVirtualMethod;
            }
    
            @Override
            public void run()
            {
                for( count = 0;  !terminate;  count++ )
                {
                    objectWithNonVirtualMethod.nonVirtualMethod();
                }
            }
        }
    
        static final PrintStream devNull = new PrintStream( new OutputStream() 
        {
            public void write(int b) {}
        } );
    }
    

    It is worth noting that this performance difference is only applicable to code which does nothing other than invoking parameterless methods. Whatever other code you have between the invocations will dilute the differences, and this includes parameter passing. Actually, the 15% difference between static and nonvirtual calls is probably explained in full by the fact that the this pointer does not have to be passed to the static method. So, it would only take a fairly small amount of code doing trivial stuff in between calls for the difference between different kinds of calls to be diluted to the point of having no net impact whatsoever.

    Also, virtual method calls exist for a reason; they do have a purpose to serve, and they are implemented using the most efficient means provided by the underlying hardware. (The CPU instruction set.) If, in your desire to eliminate them by replacing them with nonvirtual or static calls, you end up having to add as much as an iota of extra code to emulate their functionality, then your resulting net overhead is bound to be not less, but more. Quite possibly, much, much, unfathomably much, more.

    0 讨论(0)
  • 2020-11-27 13:01

    7 years later...

    I don't have a huge degree of confidence in the results that Mike Nakis found because they don't address some common issues relating to Hotspot optimisations. I've instrumented benchmarks using JMH and found the overhead of an instance method to be about 0.75% on my machine vs a static call. Given that low overhead I think except in the most latency sensitive operations it's arguably not the biggest concern in an applications design. The summary results from my JMH benchmark are as follows;

    java -jar target/benchmark.jar
    
    # -- snip --
    
    Benchmark                        Mode  Cnt          Score         Error  Units
    MyBenchmark.testInstanceMethod  thrpt  200  414036562.933 ± 2198178.163  ops/s
    MyBenchmark.testStaticMethod    thrpt  200  417194553.496 ± 1055872.594  ops/s
    

    You can look at the code here on Github;

    https://github.com/nfisher/svsi

    The benchmark itself is pretty simple but aims to minimise dead code elimination and constant folding. There are possibly other optimisations that I've missed/overlooked and these results are likely to vary per JVM release and OS.

    package ca.junctionbox.svsi;
    
    import org.openjdk.jmh.annotations.Benchmark;
    import org.openjdk.jmh.annotations.Scope;
    import org.openjdk.jmh.annotations.State;
    import org.openjdk.jmh.infra.Blackhole;
    
    class InstanceSum {
        public int sum(final int a, final int b) {
            return a + b;
        }
    }
    
    class StaticSum {
        public static int sum(final int a, final int b) {
            return a + b;
        }
    }
    
    public class MyBenchmark {
        private static final InstanceSum impl = new InstanceSum();
    
        @State(Scope.Thread)
        public static class Input {
            public int a = 1;
            public int b = 2;
        }
    
        @Benchmark
        public void testStaticMethod(Input i, Blackhole blackhole) {
            int sum = StaticSum.sum(i.a, i.b);
            blackhole.consume(sum);
        }
    
        @Benchmark
        public void testInstanceMethod(Input i, Blackhole blackhole) {
            int sum = impl.sum(i.a, i.b);
            blackhole.consume(sum);
        }
    }
    
    0 讨论(0)
  • 2020-11-27 13:04

    First: you shouldn't be making the choice of static vs non-static on the basis of performance.

    Second: in practice, it won't make any difference. Hotspot may choose to optimize in ways that make static calls faster for one method, non-static calls faster for another.

    Third: much of the mythos surrounding static versus non-static are based either on very old JVMs (which did not do anywhere near the optimization that Hotspot does), or some remembered trivia about C++ (in which a dynamic call uses one more memory access than a static call).

    0 讨论(0)
  • 2020-11-27 13:04

    Well, static calls can't be overridden (so are always candidates for inlining), and don't require any nullity checks. HotSpot does a bunch of cool optimizations for instance methods which may well negate these advantages, but they're possible reasons why a static call may be faster.

    However, that shouldn't affect your design - code in the most readable, natural way - and only worry about this sort of micro-optimization if you have just cause (which you almost never will).

    0 讨论(0)
  • 2020-11-27 13:04

    There might be a difference, and it might go either way for any particular piece of code, and it might change with even a minor release of the JVM.

    This is most definitely part of the 97% of small efficiencies that you should forget about.

    0 讨论(0)
提交回复
热议问题