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

问题描述 投票:75回答:12

这种或那种方式有任何性能上的好处吗?它是编译器/ VM特定的吗?我正在使用Hotspot。

java performance premature-optimization
12个回答
70
投票

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

第二:在实践中,它不会有任何区别。热点可以选择优化方式,使一个方法的静态调用更快,另一个方法的非静态调用更快。

第三:围绕静态与非静态的大多数神话都基于非常古老的JVM(在Hotspot所做的优化附近没有做任何事情),或者是一些关于C ++的记忆琐事(其中动态调用使用了一个以上的内存访问)而不是静态电话)。


0
投票

从理论上讲,价格便宜。

即使您创建了对象的实例,静态初始化也将完成,而静态方法不会在构造函数中执行任何通常的初始化。

但是,我没有测试过这个。


0
投票

正如Jon所说,静态方法不能被覆盖,因此简单地调用静态方法可能 - 在一个足够幼稚的Java运行时 - 比调用实例方法更快。

但是,即使假设你正处于关注设计以节省几纳秒的时间点,这也只是提出了另一个问题:你是否需要方法来覆盖自己?如果您改变代码以将实例方法转换为静态方法以在此处和那里保存纳秒,然后转而在其上实现自己的调度程序,那么几乎可以肯定你的代码效率低于构建的代码。已经进入Java运行时了。


-2
投票

我想在这里添加其他很好的答案,它还取决于你的流程,例如:

Public class MyDao {

   private String sql = "select * from MY_ITEM";

   public List<MyItem> getAllItems() {
       springJdbcTemplate.query(sql, new MyRowMapper());
   };
};

请注意,每次调用都会创建一个新的MyRowMapper对象。 相反,我建议在这里使用静态字段。

Public class MyDao {

   private static RowMapper myRowMapper = new MyRowMapper();
   private String sql = "select * from MY_ITEM";

   public List<MyItem> getAllItems() {
       springJdbcTemplate.query(sql, myRowMapper);
   };
};

50
投票

四年后......

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

我跑了on ideone,这就是我得到的:

(更大的迭代次数更好。)

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

正如所料,虚方法调用是最慢的,非虚方法调用更快,静态方法调用甚至更快。

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

我想结果会因CPU和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指令集。)如果您希望通过用非虚拟或静态调用替换它们来消除它们,您最终必须添加尽可能多的额外代码来模拟它们的功能,然后您的结果净开销受到限制不少,但更多。很可能,非常多,不可思议地多,更多。


41
投票

好吧,静态调用不能被覆盖(所以总是候选内联),并且不需要任何无效检查。 HotSpot为实例方法做了很多很好的优化,这可能会抵消这些优势,但它们可能是静态调用可能更快的原因。

但是,这不应该以最可读,最自然的方式影响您的设计 - 代码 - 如果您有正当理由(您几乎从不会这样做),只会担心这种微优化。


18
投票

它是特定于编译器/ VM的。

  • 理论上,静态调用可以稍微提高效率,因为它不需要进行虚函数查找,也可以避免隐藏的“this”参数的开销。
  • 实际上,许多编译器无论如何都会优化它。

因此,除非您在应用程序中将此视为真正关键的性能问题,否则可能不值得烦恼。过早的优化是万恶之源......

但是我看到这种优化在以下情况下会显着提高性能:

  • 在没有内存访问的情况下执行非常简单的数学计算的方法
  • 在紧密的内循环中每秒调用数百万次的方法
  • CPU绑定应用程序,其中每个性能都很重要

如果上述内容适用于您,则可能值得测试。

使用静态方法还有一个好的(可能更重要的!)理由 - 如果方法实际上具有静态语义(即逻辑上没有连接到类的给定实例)那么将它设置为静态是有意义的反映这一事实。经验丰富的Java程序员会注意到静态修饰符并立即想到“啊哈!这个方法是静态的,因此它不需要实例,并且可能不会操纵特定于实例的状态”。所以你将有效地传达方法的静态性质....


14
投票

正如之前的海报所说:这似乎是一个不成熟的优化。

但是,存在一个区别(非静态调用需要额外将被调用对象推送到操作数堆栈的事实):

由于无法覆盖静态方法,因此静态方法调用在运行时不会有任何虚拟查找。在某些情况下,这可能会导致可观察到的差异。

字节码级别的差异在于通过INVOKEVIRTUALINVOKEINTERFACEINVOKESPECIAL进行非静态方法调用,而静态方法调用则通过INVOKESTATIC完成。


11
投票

令人难以置信的是,静态调用与非静态调用的性能差异对您的应用程序产生了影响。请记住,“过早优化是所有邪恶的根源”。


11
投票

对于决定方法是否应该是静态的,性能方面应该是无关紧要的。如果您遇到性能问题,那么将大量方法设置为静态就不会节省时间。也就是说,静态方法几乎肯定不比任何实例方法慢,在大多数情况下稍微快一点:

1.)静态方法不是多态的,因此JVM做出较少的决定来查找要执行的实际代码。这是Hotspot时代的一个有争议的问题,因为Hotspot将优化只有一个实现站点的实例方法调用,因此它们将执行相同的操作。

2.)另一个细微差别是静态方法显然没有“这个”参考。这导致堆栈帧的一个槽小于具有相同签名和主体的实例方法的槽(“this”放在字节码级别的局部变量的槽0中,而对于静态方法,槽0用于第一个方法的参数)。


8
投票

7年后......

我对Mike Nakis发现的结果没有很大的信心,因为他们没有解决与Hotspot优化相关的一些常见问题。我使用JMH检测了基准测试,发现实例方法的开销在我的机器上比静态调用大约0.75%。考虑到开销较低,我认为除了在最具延迟敏感性的操作中,它可能不是应用程序设计中最大的问题。我的JMH基准的总结结果如下;

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

您可以在Github上查看代码;

https://github.com/nfisher/svsi

基准测试本身非常简单,但旨在最大限度地减少死代码消除和不断折叠。我可能错过/忽略了其他一些优化,这些结果可能因JVM版本和操作系统而异。

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

4
投票

可能存在差异,它可能适用于任何特定代码段,并且即使是JVM的次要版本也可能会发生变化。

这绝对是the 97% of small efficiencies that you should forget about的一部分。

© www.soinside.com 2019 - 2024. All rights reserved.