Java 静态调用比非静态调用更昂贵还是更便宜?
有没有这样或那样的性能优势?它是特定于编译器/VM 的吗?我正在使用热点。
有没有这样或那样的性能优势?它是特定于编译器/VM 的吗?我正在使用热点。
首先:你不应该在性能的基础上选择静态与非静态。
第二:在实践中,它不会有任何区别。Hotspot 可能会选择以一种方法使静态调用更快,而对于另一种方法,非静态调用更快的方式进行优化。
第三:围绕静态与非静态的大部分神话要么基于非常旧的JVM(它没有像Hotspot那样进行优化),要么基于一些关于C++的琐事(其中动态调用比静态调用多使用一个内存访问)。
四年后...
好吧,为了一劳永逸地解决这个问题,我写了一个基准测试,展示了不同类型的调用(虚拟,非虚拟,静态)如何相互比较。
我在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 指令集。如果您希望通过用非虚拟或静态调用替换它们来消除它们,您最终不得不添加多达一点额外的代码来模拟它们的功能,那么您产生的净开销注定会不会更少,而是更多。很可能,很多,很多,深不可测的很多,更多。