Java静态调用比非静态调用更昂贵还是更低?
这种或那种方式是否有任何性能优势? 它是编译器/虚拟机特定? 我正在使用Hotspot。
首先:你不应该在性能的基础上进行静态和非静态的select。
其次:在实践中,没有任何区别。 热点可以select优化的方式,使一个方法的静态调用更快,另一个方法的非静态调用更快。
第三:很多关于静态和非静态的神话都是基于非常古老的JVM(在Hotspot所做的优化之外没有做任何其他的事情),或者是关于C ++的一些记忆琐事(其中一个dynamic调用使用一个更多的内存访问比静态调用)。
四年后…
好吧,为了永远解决这个问题,我写了一个基准,它显示了不同types的调用(虚拟的,非虚拟的,静态的)如何相互比较。
我运行ideone ,这就是我得到的:
(迭代次数越多越好。)
Success time: 3.12 memory: 320576 signal:0 Name | Iterations VirtualTest | 128009996 NonVirtualTest | 301765679 StaticTest | 352298601 Done.
正如所料,虚方法调用是最慢的,非虚方法调用速度更快,静态方法调用更快。
我没有想到的是差异如此明显:虚拟方法调用的测量速度低于非虚方法调用速度的一半 ,而非虚方法调用的速度则比静态调用慢15% 。 这就是这些测量显示的; 实际的差异其实应该稍微更明显,因为对于每个虚拟的,非虚拟的和静态的方法调用,我的基准testing代码都会增加一个额外的常量开销,增加一个整型variables,检查一个布尔型variables,如果不是这样,则循环。
我猜想结果会因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) {} } ); }
值得注意的是,这种性能差异只适用于除调用无参数方法之外什么也不做的代码。 无论调用之间的其他代码是否会淡化差异,这都包括parameter passing。 实际上,静态调用和非调用调用之间15%的区别可能是由于this
指针不必被传递给静态方法所致。 所以,只需要相当less量的代码就可以在调用之间做一些微不足道的事情,以便将不同types的调用之间的差异稀释到不受任何影响的程度。
此外,虚拟方法调用存在的原因; 他们确实有服务的目的,而且他们是使用底层硬件提供的最有效的方法来实现的。 (CPU指令集)如果你想通过用非虚拟或静态调用取代它们,你最终不得不添加额外的代码来模拟它们的function,那么你的结果净开销是绑定的不要less,而要多。 很可能,很多,不可思议的是,更多。
那么,静态调用不能被覆盖(所以总是候选内联),并且不需要任何无效检查。 HotSpot为实例方法做了一些很酷的优化,这可能会否定这些优点,但是它们是静态调用可能更快的可能原因。
然而,这不应该以最易读,自然的方式影响你的devise – 代码,只有在你有正当理由的情况下才会担心这种微型优化(你几乎从不会这么做)。
这是编译器/ VM特定的。
- 从理论上讲 ,静态调用可以稍微高效一些,因为它不需要进行虚拟函数查找,也可以避免隐藏的“this”参数的开销。
- 实际上 ,许多编译器都会优化这个。
因此,除非您在应用程序中将这确定为一个真正关键的性能问题,否则可能不值得烦恼。 过早的优化是所有邪恶等的根源。
不过,我已经看到这种优化在以下情况下会大大提高性能:
- 方法执行一个非常简单的math计算没有内存访问
- 在紧密的内部循环中每秒调用数百万次的方法
- CPU绑定的应用程序,每一点的性能重要
如果上述适用于您,则可能值得testing。
还有一个好的(也可能更重要的!)理由使用静态方法 – 如果方法实际上有静态语义(即逻辑上没有连接到给定的类的实例),那么它是有意义的,使它静态以反映这一事实。 有经验的Java程序员会注意到静态修饰符,并立即认为“这个方法是静态的,所以它不需要一个实例,大概不会操作实例特定的状态”。 所以你会有效地传达这个方法的静态性质。
正如以前的海报所说:这似乎是一个不成熟的优化。
但是,有一点不同(非静态调用要求将被调用对象另外推送到操作数栈上):
由于静态方法不能被覆盖, 因此在运行时将不会有任何虚拟查找来进行静态方法调用。 在某些情况下,这可能会导致可观察到的差异。
字节码级别的不同在于,通过INVOKEVIRTUAL
, INVOKEINTERFACE
或INVOKESPECIAL
完成非静态方法调用,而通过INVOKESTATIC
完成静态方法调用。
静态调用和非静态调用的性能差异在应用程序中发挥作用是不可思议的。 请记住,“过早优化是万恶之源”。
为了决定一个方法是否应该是静态的,性能方面应该是不相关的。 如果你有一个性能问题,使很多方法静态是不会保存一天。 也就是说,静态方法几乎肯定不会比任何实例方法慢 ,在大多数情况下, 速度稍快一些 :
1.)静态方法不是多态的,所以JVM决定要find实际的代码来执行。 这是Hotspot时代的一个争论点,因为Hotspot会优化只有一个实现站点的实例方法调用,所以它们将执行相同的操作。
2.)另一个微妙的区别是静态方法显然没有“this”的引用。 这导致堆栈帧比具有相同签名和主体的实例方法的一个槽更小(在字节码级上,“this”被置于局部variables的槽0中,而对于静态方法,槽0被用于第一个该方法的参数)。
这可能会有所不同,对于任何特定的代码都可能会有变化,甚至可能随着JVM的次要版本而改变。
这绝对是您应该忘记的97%小效率的一部分。
理论上说,比较便宜。
静态初始化即使在创build对象的实例时也要完成,而静态方法不会在构造函数中正常执行任何初始化。
不过,我还没有testing过这个。
正如Jon所说,静态方法不能被重写,所以简单地调用一个静态方法可能会比一个足够天真的Java运行时更快地调用一个实例方法。
但是,即使假设你正在考虑搞乱你的devise以节省几个纳秒,那也只是提出了另一个问题:你是否需要重写自己的方法? 如果你改变你的代码,使一个实例方法变成一个静态方法来节省一个纳秒,然后转而在其上实现你自己的调度器,那么你的效率几乎肯定会比构build的效率低到您的Java运行时已经。
我想补充一下这里的其他很好的答案,这也取决于你的stream程,例如:
Public class MyDao { private String sql = "select * from MY_ITEM"; public List<MyItem> getAllItems() { springJdbcTemplate.query(sql, new MyRowMapper()); }; };
请注意,每次调用都会创build一个新的MyRowMapper对象。
相反,我build议在这里使用一个静态字段。
Public class MyDao { private static RowMapper myRowMapper = new MyRowMapper(); private String sql = "select * from MY_ITEM"; public List<MyItem> getAllItems() { springJdbcTemplate.query(sql, myRowMapper); }; };