四年后...
好吧,为了一劳永逸地解决这个问题,我编写了一个基准测试,它显示了不同类型的调用(虚拟、非虚拟、静态)之间的比较。
我运行了它关于ideone,这就是我得到的:
(迭代次数越多越好。)
Success time: 3.12 memory: 320576 signal:0
Name | Iterations
VirtualTest | 128009996
NonVirtualTest | 301765679
StaticTest | 352298601
Done.
正如预期的那样,虚拟方法调用最慢,非虚拟方法调用更快,静态方法调用甚至更快。
我没想到的是差异如此明显:虚拟方法调用被测量为运行在少于一半非虚拟方法调用的速度,进而测量运行整个方法的速度慢 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% 的差异可能是可以解释的in full事实上,this
指针不必传递给静态方法。因此,只需要在调用之间使用相当少量的代码做一些琐碎的事情,就可以将不同类型的调用之间的差异稀释到没有任何净影响的程度。
此外,虚拟方法调用的存在是有原因的;它们确实有服务的目的,并且它们是使用底层硬件提供的最有效的手段来实现的。 (CPU 指令集。)如果您希望通过用非虚拟或静态调用替换它们来消除它们,您最终必须添加尽可能多的额外代码来模拟它们的功能,那么您产生的净开销是必然的不是更少,而是更多。很可能,很多,很多,难以想象的很多,更多。