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

发布于 2024-09-24 18:55:35 字数 43 浏览 3 评论 0原文

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

韵柒 2024-10-01 18:55:35

四年后...

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

在 ideone 上运行了它,这就是我得到的结果:(

迭代次数越大越好。)

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

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

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

我想结果会因 CPU 和 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 指令集。)如果您希望通过用非虚拟或静态调用替换它们来消除它们,您最终必须添加尽可能多的额外代码来模拟它们的功能,那么您产生的净开销是必然的不是更少,而是更多。很可能,很多,很多,难以想象的很多,更多。

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.

梦在深巷 2024-10-01 18:55:35

首先:您不应该根据性能来选择静态与非静态。

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

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

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).

有深☉意 2024-10-01 18:55:35

那么,静态调用不能被覆盖(因此始终是内联的候选者),并且不需要任何无效检查。 HotSpot 对实例方法进行了一系列很酷的优化,这很可能会抵消这些优点,但它们是静态调用可能更快的可能原因。

然而,这不应该影响您的设计 - 以最易读、最自然的方式编写代码 - 并且只有在您有正当理由时才担心这种微优化(您几乎永远不会)。

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).

定格我的天空 2024-10-01 18:55:35

7 年后...

我对 Mike Nakis 发现的结果没有很大的信心,因为它们没有解决与热点优化相关的一些常见问题。我使用 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);
    }
}

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);
    }
}
横笛休吹塞上声 2024-10-01 18:55:35

它是编译器/VM 特定的。

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

因此,除非您已将其确定为应用程序中真正关键的性能问题,否则可能不值得担心。过早的优化是万恶之源等...

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

  • 方法执行非常简单的数学计算,无需内存访问
  • 方法被调用<在紧密的内循环 CPU 密集型应用程序中每秒数百万次
  • ,其中每一点性能都很重要

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

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

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....

各空 2024-10-01 18:55:35

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

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

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

字节码级别的区别在于,非静态方法调用是通过 INVOKEVIRTUALINVOKEINTERFACEINVOKESPECIAL 而静态方法调用是通过 INVOKESTATIC

As previous posters have said: This seems like a premature optimization.

However, there is one difference (a part from the fact that non-static invokations require an additional push of a callee-object onto the operand stack):

Since static methods can't be overridden, there will not be any virtual lookups in runtime for a static method call. This may result in an observable difference under some circumstances.

The difference on a byte-code level is that a non-static method call is done through INVOKEVIRTUAL, INVOKEINTERFACE or INVOKESPECIAL while a static method call is done through INVOKESTATIC.

你没皮卡萌 2024-10-01 18:55:35

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

It is unbelievably unlikely that any difference in the performance of static versus non-static calls is making a difference in your application. Remember that "premature optimization is the root of all evil".

沫雨熙 2024-10-01 18:55:35

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

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

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

For the decision if a method should be static, the performance aspect should be irrelevant. If you have a performance problem, making lots of methods static isn't going to save the day. That said, static methods are almost certainly not slower than any instance method, in most cases marginally faster:

1.) Static methods are not polymorphic, so the JVM has less decisions to make to find the actual code to execute. This is a moot point in the Age of Hotspot, since Hotspot will optimize instance method calls that have only one implementation site, so they will perform the same.

2.) Another subtle difference is that static methods obviously have no "this" reference. This results in a stack frame one slot smaller than that of an instance method with the same signature and body ("this" is put in slot 0 of the local variables on the bytecode level, whereas for static methods slot 0 is used for the first parameter of the method).

情栀口红 2024-10-01 18:55:35

理论上来说,更便宜。

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

不过,我还没有测试过这个。

In theory, less expensive.

Static initialization is going to be done even if you create an instance of the object, whereas static methods will not do any initialization normally done in a constructor.

However, I haven't tested this.

百善笑为先 2024-10-01 18:55:35

正如 Jon 指出的那样,静态方法无法被重写,因此在足够简单的 Java 运行时调用静态方法可能比调用实例方法更快。

但是,即使假设您正在关心弄乱您的设计以节省几纳秒,这也会带来另一个问题:您是否需要重写自己的方法?如果您更改代码以将实例方法变成静态方法,以节省一纳秒,然后在此基础上实现您自己的调度程序,那么您的调度程序几乎肯定会比构建的调度程序效率低已经进入您的 Java 运行时了。

As Jon notes, static methods can't be overridden, so simply invoking a static method may be -- on a sufficiently naive Java runtime -- faster than invoking an instance method.

But then, even assuming you're at the point where you care about messing up your design to save a few nanoseconds, that just brings up another question: will you need method overriding yourself? If you change your code around to make an instance method into a static method to save a nanosecond here and there, and then turn around and implement your own dispatcher on top of that, yours is almost certainly going to be less efficient than the one built into your Java runtime already.

瞄了个咪的 2024-10-01 18:55:35

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

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

I would like to add to the other great answers here that it also depends on your flow, for example:

Public class MyDao {

   private String sql = "select * from MY_ITEM";

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

Pay attention that you create a new MyRowMapper object per each call.

Instead, I suggest to use here a static field.

Public class MyDao {

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

   public List<MyItem> getAllItems() {
       springJdbcTemplate.query(sql, myRowMapper);
   };
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文