何らかの方法でパフォーマンス上の利点はありますか? コンパイラ/VM固有ですか? ホットスポットを使用しています。
12 に答える
まず、パフォーマンスに基づいて静的と非静的の選択を行うべきではありません。
2 番目: 実際には、違いはありません。Hotspot は、あるメソッドの静的呼び出しを高速化し、別のメソッドの非静的呼び出しを高速化する方法で最適化することを選択する場合があります。
3 番目: 静的と非静的を取り巻く神話の多くは、非常に古い JVM (Hotspot が行う最適化の近くにはどこにも行っていない)、または C++ に関するいくつかの記憶されたトリビア (動的呼び出しがもう 1 つのメモリ アクセスを使用する) に基づいています。静的呼び出しより)。
4年後...
さて、この問題を永遠に解決することを願って、さまざまな種類の呼び出し (仮想、非仮想、静的) を相互に比較する方法を示すベンチマークを作成しました。
ideoneで実行したところ、次のようになりました。
(反復回数は多い方が良いです。)
Success time: 3.12 memory: 320576 signal:0
Name | Iterations
VirtualTest | 128009996
NonVirtualTest | 301765679
StaticTest | 352298601
Done.
予想どおり、仮想メソッドの呼び出しは最も遅く、非仮想メソッドの呼び出しはより速く、静的メソッドの呼び出しはさらに高速です。
私が予想していなかったのは、違いがそれほど顕著であったことです.仮想メソッド呼び出しは、非仮想メソッド呼び出しの半分以下の速度で実行されることが測定されました。それがこれらの測定値が示していることです。仮想、非仮想、および静的メソッド呼び出しごとに、私のベンチマーク コードには、1 つの整数変数をインクリメントし、ブール変数をチェックし、真でない場合はループする追加の一定のオーバーヘッドがあるため、実際の違いは実際にはもう少し顕著である必要があります。
結果は 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 命令セット。) それらを非仮想呼び出しまたは静的呼び出しに置き換えることでそれらを排除したい場合、それらの機能をエミュレートするために余分なコードを追加する必要がある場合、結果として得られる正味のオーバーヘッドは制限されます。少なくなるのではなく、より多くなるように。おそらく、はるかに、はるかに、計り知れないほど、はるかに。
まあ、静的呼び出しはオーバーライドできません (したがって、常にインライン化の候補です)、nullity チェックは必要ありません。HotSpot は、これらの利点を無効にする可能性のあるインスタンス メソッドの一連の優れた最適化を行いますが、静的呼び出しが高速になる可能性がある理由として考えられるのは、これらです。
ただし、それは設計に影響を与えるべきではありません-コードは最も読みやすく自然な方法です-そして、正当な理由がある場合にのみ、この種のマイクロ最適化について心配する必要があります (ほとんどありません)。
これはコンパイラ/VM 固有です。
- 理論的には、静的呼び出しは仮想関数のルックアップを行う必要がないため、わずかに効率的であり、非表示の「this」パラメーターのオーバーヘッドも回避できます。
- 実際には、多くのコンパイラはとにかくこれを最適化します。
したがって、これがアプリケーションの本当に重大なパフォーマンスの問題であると特定されない限り、おそらく気にする価値はありません。時期尚早の最適化は諸悪の根源など...
ただし、この最適化により、次の状況でパフォーマンスが大幅に向上することがわかりました。
- メモリアクセスなしで非常に単純な数学的計算を実行するメソッド
- タイトな内部ループで毎秒数百万回呼び出されるメソッド
- あらゆるパフォーマンスが重要な CPU バウンド アプリケーション
上記に該当する場合は、テストする価値があるかもしれません。
静的メソッドを使用するもう 1 つの良い (そして潜在的にさらに重要な!) 理由もあります。メソッドが実際に静的セマンティクスを持っている (つまり、論理的にクラスの特定のインスタンスに接続されていない) 場合、静的にするのが理にかなっています。この事実を反映するために。経験豊富な Java プログラマーは static 修飾子に気付き、すぐに「ああ、このメソッドは静的なので、インスタンスを必要とせず、おそらくインスタンス固有の状態を操作しない」と考えるでしょう。したがって、メソッドの静的な性質を効果的に伝えることができます....
以前の投稿者が言ったように: これは時期尚早の最適化のようです。
ただし、違いが 1 つあります (非静的呼び出しでは、呼び出し先オブジェクトをオペランド スタックに追加でプッシュする必要があるという事実とは異なります)。
静的メソッドはオーバーライドできないため、実行時に静的メソッド呼び出しの仮想ルックアップは行われません。これにより、状況によっては目に見える違いが生じる場合があります。
バイト コード レベルでの違いは、非静的メソッドの呼び出しが を介して行われるINVOKEVIRTUAL
かINVOKEINTERFACE
、INVOKESPECIAL
静的メソッドの呼び出しが を介して行われることINVOKESTATIC
です。
静的呼び出しと非静的呼び出しのパフォーマンスの違いがアプリケーションに違いをもたらすことは、信じられないほどありそうにありません。「時期尚早の最適化は諸悪の根源」であることを忘れないでください。
メソッドを静的にするかどうかを決定する場合、パフォーマンスの側面は関係ありません。パフォーマンスに問題がある場合、多くのメソッドを静的にしても問題は解決しません。とはいえ、静的メソッドはほとんどの場合、どのインスタンス メソッドよりも遅くはなく、ほとんどの場合わずかに高速です。
1.) 静的メソッドは多態的ではないため、JVM が実行する実際のコードを見つけるために行う決定が少なくなります。Hotspot は、実装サイトが 1 つしかないインスタンス メソッド呼び出しを最適化し、同じように実行するため、これは Hotspot の時代の論点です。
2.) もう 1 つの微妙な違いは、静的メソッドには明らかに "this" 参照がないことです。これにより、同じシグネチャとボディを持つインスタンス メソッドのスタック フレームよりも 1 スロット小さいスタック フレームが生成されます ("this" は、バイトコード レベルのローカル変数のスロット 0 に配置されますが、静的メソッドの場合、スロット 0 が最初の変数に使用されます)。メソッドのパラメーター)。
違いがある可能性があり、コードの特定の部分についてはどちらの方向にも進む可能性があり、JVM のマイナー リリースでも変更される可能性があります。
これは間違いなく、忘れるべき小さな効率の 97% の一部です。
理論的には、より安価です。
オブジェクトのインスタンスを作成しても静的初期化が行われますが、静的メソッドはコンストラクターで通常行われる初期化を行いません。
ただし、これはテストしていません。
Jon が指摘しているように、静的メソッドはオーバーライドできないため、単純に静的メソッドを呼び出す方が、十分に単純な Java ランタイムでは、インスタンス メソッドを呼び出すよりも高速です。
しかし、数ナノ秒を節約するために設計を台無しにすることに関心があると仮定しても、別の疑問が生じます: 自分自身をオーバーライドするメソッドが必要ですか? あちこちでナノ秒を節約するためにインスタンス メソッドを静的メソッドにするようにコードを変更し、その上に独自のディスパッチャを実装すると、ほとんどの場合、作成したものよりも効率が低下します。すでに Java ランタイムに組み込まれています。
ここで、他の素晴らしい回答に追加したいと思います。たとえば、次のように、フローにも依存します。
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);
};
};