Четыре года спустя ...
Хорошо, в надежде решить этот вопрос раз и навсегда, я написал тест, который показывает, как различные виды вызовов (виртуальные, не виртуальные, статические) сравниваются друг с другом.
Я запустил его на ideone , и вот что я получил:
(Чем больше число итераций, тем лучше.)
Success time: 3.12 memory: 320576 signal:0
Name | Iterations
VirtualTest | 128009996
NonVirtualTest | 301765679
StaticTest | 352298601
Done.
Как и ожидалось, вызовы виртуальных методов - самые медленные, вызовы невиртуальных методов - быстрее, а вызовы статических методов - еще быстрее.
Чего я не ожидал, так это того, что различия будут настолько явными: измеренные вызовы виртуальных методов имели скорость меньше половины скорости вызовов невиртуальных методов, которые, в свою очередь, измерялись для выполнения в целом 15% медленнее , чем статические вызовы. Это то, что показывают эти измерения; фактические различия на самом деле должны быть немного более выраженными, поскольку для каждого вызова виртуального, не виртуального и статического методов мой код для сравнительного анализа имеет дополнительные постоянные накладные расходы, заключающиеся в увеличении одной целочисленной переменной, проверке логической переменной и выполнении цикла, если оно не истинно.
Я полагаю, что результаты будут варьироваться от процессора к процессору и от 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
не нужно передавать статическому методу. Таким образом, потребовалось бы довольно небольшое количество кода, выполняющего тривиальные вещи между вызовами, чтобы различие между различными типами вызовов было сведено до такой степени, чтобы не иметь никакого чистого влияния вообще.
Кроме того, вызовы виртуальных методов существуют по причине; у них есть цель, и они реализуются с использованием наиболее эффективных средств, предоставляемых базовым оборудованием. (Набор инструкций ЦП.) Если, если вы хотите устранить их, заменив их не виртуальными или статическими вызовами, вам придется добавить столько йоты дополнительного кода, чтобы эмулировать их функциональность, тогда ваши чистые накладные расходы будут связаны быть не меньше, а больше. Вполне возможно, много, много, непостижимо много, больше.