Java 静态调用比非静态调用更昂贵还是更便宜?

有没有这样或那样的性能优势?它是特定于编译器/VM 的吗?我正在使用热点。


答案 1

首先:你不应该在性能的基础上选择静态与非静态。

第二:在实践中,它不会有任何区别。Hotspot 可能会选择以一种方法使静态调用更快,而对于另一种方法,非静态调用更快的方式进行优化。

第三:围绕静态与非静态的大部分神话要么基于非常旧的JVM(它没有像Hotspot那样进行优化),要么基于一些关于C++的琐事(其中动态调用比静态调用多使用一个内存访问)。


答案 2

四年后...

好吧,为了一劳永逸地解决这个问题,我写了一个基准测试,展示了不同类型的调用(虚拟,非虚拟,静态)如何相互比较。

我在ideone上运行它,这就是我得到的:

(迭代次数越多越好。

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

正如预期的那样,虚拟方法调用是最慢的,非虚拟方法调用更快,静态方法调用更快。

我没想到的是差异如此明显:虚拟方法调用的运行速度不到非虚拟方法调用的一,而非虚拟方法调用又比静态调用慢整整15%。这就是这些测量结果所显示的;实际上,实际差异必须稍微明显一些,因为对于每个虚拟、非虚拟和静态方法调用,我的基准测试代码都有一个额外的常量开销,即递增一个整数变量,检查一个布尔变量,如果不是 true,则循环。

我想结果会因CPU而异,从JVM到JVM,所以试一试,看看你会得到什么:

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) {}
    } );
}

值得注意的是,这种性能差异仅适用于除了调用无参数方法之外什么都不做的代码。在调用之间拥有任何其他代码都会淡化差异,这包括参数传递。实际上,静态调用和非虚拟调用之间 15% 的差异可能完全可以通过指针不必传递给静态方法这一事实来解释。因此,只需要相当少量的代码在调用之间做一些琐碎的事情,就可以将不同类型调用之间的差异稀释到没有任何净影响的程度。this

此外,虚拟方法调用的存在是有原因的。它们确实有一个服务目的,并且它们是使用底层硬件提供的最有效方法实现的。(CPU 指令集。如果您希望通过用非虚拟或静态调用替换它们来消除它们,您最终不得不添加多达一点额外的代码来模拟它们的功能,那么您产生的净开销注定会不会更少,而是更多。很可能,很多,很多,深不可测的很多,更多。


推荐