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

时间:2010-09-27 15:10:37

标签: java performance premature-optimization

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

12 个答案:

答案 0 :(得分:70)

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

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

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

答案 1 :(得分: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%的差异可能是由于{{1>}指针不必传递给静态方法而在完整中解释的。因此,只需要相当少量的代码就可以在不同类型的调用之间的差异调用之间做一些微不足道的事情,将其稀释到没有任何净影响的程度。

此外,存在虚拟方法调用的原因;它们确实有服务的目的,它们是使用底层硬件提供的最有效的方法实现的。 (CPU指令集。)如果您希望通过用非虚拟或静态调用替换它们来消除它们,您最终必须添加尽可能多的额外代码来模拟它们的功能,然后您的结果净开销受到限制不少,但更多。很可能,更多,更多,更不可思议,更多。

答案 2 :(得分:41)

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

然而,这不应该以最可读,最自然的方式影响你的设计 - 代码 - 如果你有正当理由(你几乎永远不会将只会担心这种微优化) )。

答案 3 :(得分:18)

特定于编译器/ VM。

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

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

但是我已经看到此优化在以下情况下显着提升了性能:

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

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

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

答案 4 :(得分:14)

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

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

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

字节码级别的差异在于,通过INVOKEVIRTUALINVOKEINTERFACEINVOKESPECIAL进行非静态方法调用,而静态方法调用则通过{{3 }}

答案 5 :(得分:11)

静态调用与非静态调用的性能差异在您的应用程序中产生差异是令人难以置信的。请记住,“过早优化是万恶之源”。

答案 6 :(得分:11)

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

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

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

答案 7 :(得分: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);
    }
}

答案 8 :(得分:4)

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

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

答案 9 :(得分:0)

从理论上讲,价格便宜。

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

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

答案 10 :(得分:0)

正如Jon指出的那样,静态方法无法被覆盖,因此只需调用静态方法 - 在一个足够幼稚的Java运行时 - 比调用更快实例方法。

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

答案 11 :(得分:-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);
   };
};