何らかの方法でパフォーマンス上の利点はありますか?コンパイラ/ VM固有ですか?ホットスポットを使用しています。
何らかの方法でパフォーマンス上の利点はありますか?コンパイラ/ VM固有ですか?ホットスポットを使用しています。
回答:
まず、パフォーマンスに基づいて静的か非静的かを選択するべきではありません。
第二に、実際には、それは何の違いもありません。Hotspotは、静的呼び出しを1つのメソッドで高速にし、非静的呼び出しを別のメソッドで高速にする方法で最適化することを選択できます。
3番目:静的と非静的を取り巻く神話の多くは、非常に古いJVM(これはHotspotが行う最適化の近くでは機能しませんでした)、またはC ++に関する覚えられた雑学(動的呼び出しがもう1つのメモリアクセスを使用する)に基づいています静的呼び出しよりも)。
4年後...
さて、この質問をいつまでも解決することを期待して、さまざまな種類の呼び出し(仮想、非仮想、静的)を互いに比較する方法を示すベンチマークを作成しました。
私はそれをideoneで実行しました、そしてこれは私が得たものです:
(反復回数が多いほど良いです。)
Success time: 3.12 memory: 320576 signal:0
Name | Iterations
VirtualTest | 128009996
NonVirtualTest | 301765679
StaticTest | 352298601
Done.
予想どおり、仮想メソッド呼び出しは最も遅く、非仮想メソッド呼び出しはより高速で、静的メソッド呼び出しはさらに高速です。
私が予期していなかったことは、その違いがそれほど顕著であるということでした。仮想メソッド呼び出しは、非仮想メソッド呼び出しの速度の半分未満で実行されるように測定され、静的呼び出しよりも全体で15%遅く実行されるように測定されました。これらの測定値はそれを示しています。実際の違いは、実際にはやや顕著である必要があります。仮想、非仮想、および静的メソッド呼び出しごとに、1つの整数変数をインクリメントし、ブール変数をチェックし、trueでない場合はループするという追加の一定のオーバーヘッドがあるためです。
結果は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命令セット。)それらを非仮想呼び出しまたは静的呼び出しで置き換えることによってそれらを排除したい場合、それらの機能をエミュレートするために余分なコードのiotaを追加する必要が生じ、その結果生じるネットオーバーヘッドが制限されます。少なくないために、もっと。おそらく、はるかに、はるかに、信じられないほどはるかに。
VirtualTest | 488846733 -- NonVirtualTest | 480530022 -- StaticTest | 484353198
私のOpenJDKインストールでは。FTR:final
モディファイアを削除した場合も同様です。ところで 私はterminate
フィールド を作らvolatile
なければなりませんでした。さもなければ、テストは終了しませんでした。
VirtualTest | 12451872 -- NonVirtualTest | 12089542 -- StaticTest | 8181170
。私のノートブックのOpenJDKが40倍以上の反復を実行できるだけでなく、静的テストでは常にスループットが約30%低くなります。私は、Android 4.4タブレットに期待される結果を得るため、これは、ARTの特定の現象かもしれません:VirtualTest | 138183740 -- NonVirtualTest | 142268636 -- StaticTest | 161388933
まあ、静的呼び出しはオーバーライドできないため(常にインライン化の候補です)、無効性チェックは必要ありません。HotSpotは、これらの利点を打ち消す可能性のあるインスタンスメソッドの一連の優れた最適化を行いますが、静的呼び出しの方が高速である理由として考えられます。
しかし、それがデザインに影響を与えることはありません。最も読みやすく自然な方法でコードを作成してください。この種のマイクロ最適化について心配するのは、原因が(ほとんどない)場合だけです。
これはコンパイラ/ VM固有です。
したがって、これがアプリケーションの本当に重要なパフォーマンスの問題であると特定しない限り、気にする必要はないでしょう。時期尚早の最適化は、すべての悪などの根源です...
しかし、私はしているこの最適化は、次のような状況の大幅な性能向上を与える見て:
上記が当てはまる場合は、テストする価値があります。
静的メソッドを使用するもう1つの良い(そして潜在的にはさらに重要な!)理由もあります。メソッドが実際に静的セマンティクスを持っている(つまり、クラスの特定のインスタンスに論理的に接続されていない)場合は、静的にするのが理にかなっています。この事実を反映するために。その後、経験豊富なJavaプログラマーは静的修飾子に気づき、「ああ!このメソッドは静的であるため、インスタンスを必要とせず、おそらくインスタンス固有の状態を操作しません」とすぐに思います。したがって、メソッドの静的な性質を効果的に伝えることができます。
以前のポスターが言ったように:これは時期尚早の最適化のようです。
ただし、1つの違いがあります(静的でない呼び出しでは、呼び出し先オブジェクトをオペランドスタックに追加でプッシュする必要があるという事実からの一部)。
静的メソッドはオーバーライドできないため、実行時に静的メソッド呼び出しの仮想ルックアップはありません。これにより、状況によっては観察可能な差異が生じる可能性があります。
バイトコードレベルでの違いは、非静的メソッド呼び出しを介して行われることであるINVOKEVIRTUAL
、INVOKEINTERFACE
またはINVOKESPECIAL
静的メソッドの呼び出しを介して行われている間INVOKESTATIC
。
invokespecial
は仮想ではないため、(少なくとも通常は)を使用して呼び出されます。
静的呼び出しと非静的呼び出しのパフォーマンスの違いがアプリケーションに影響を与えることは、信じられないほど考えられません。「時期尚早の最適化はすべての悪の根源」であることを忘れないでください。
7年後...
Hotspotの最適化に関連するいくつかの一般的な問題に対処していないため、Mike Nakisが発見した結果には大きな自信がありません。私はJMHを使用してベンチマークを計測し、インスタンスメソッドのオーバーヘッドが静的呼び出しと比較して私のマシンで約0.75%であることがわかりました。オーバーヘッドが低いことを考えると、最もレイテンシの影響を受けやすい操作を除いて、それはアプリケーション設計における最大の問題ではないでしょう。私のJMHベンチマークの要約結果は次のとおりです。
java -jar target/benchmark.jar
# -- snip --
Benchmark Mode Cnt Score Error Units
MyBenchmark.testInstanceMethod thrpt 200 414036562.933 ± 2198178.163 ops/s
MyBenchmark.testStaticMethod thrpt 200 417194553.496 ± 1055872.594 ops/s
Githubでコードを確認できます。
https://github.com/nfisher/svsi
ベンチマーク自体は非常に単純ですが、デッドコードの除去と定数の折りたたみを最小限に抑えることを目的としています。私が見落としている/見落としている他の最適化がある可能性があり、これらの結果は、JVMリリースおよびOSごとに異なる可能性があります。
package ca.junctionbox.svsi;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;
class InstanceSum {
public int sum(final int a, final int b) {
return a + b;
}
}
class StaticSum {
public static int sum(final int a, final int b) {
return a + b;
}
}
public class MyBenchmark {
private static final InstanceSum impl = new InstanceSum();
@State(Scope.Thread)
public static class Input {
public int a = 1;
public int b = 2;
}
@Benchmark
public void testStaticMethod(Input i, Blackhole blackhole) {
int sum = StaticSum.sum(i.a, i.b);
blackhole.consume(sum);
}
@Benchmark
public void testInstanceMethod(Input i, Blackhole blackhole) {
int sum = impl.sum(i.a, i.b);
blackhole.consume(sum);
}
}
ops/s
、ART環境以外のメトリック(たとえば、メモリ使用量、.oatファイルサイズの縮小など)にもたらす潜在的な利点について知りたいです。これらの他のメトリックのベンチマークを試みることができる比較的単純なツール/方法を知っていますか?
メソッドを静的にするかどうかを決定する場合、パフォーマンスの側面は無関係である必要があります。パフォーマンスの問題がある場合、多くのメソッドを静的にしても、その日を節約することはできません。そうは言っても、静的メソッドはほとんどの場合、インスタンスメソッドよりも遅くはなく、ほとんどの場合わずかに高速です。
1.)静的メソッドはポリモーフィックではないため、JVMは実行する実際のコードを見つけるための決定が少なくなります。これは、Age of Hotspotの問題点です。Hotspotは、実装サイトが1つしかないインスタンスメソッド呼び出しを最適化し、同じように実行するためです。
2.)もう1つの微妙な違いは、静的メソッドには明らかに「this」参照がないことです。これにより、スタックフレームは、同じシグネチャと本文を持つインスタンスメソッドのスロットよりも1スロット小さくなります(「this」は、バイトコードレベルのローカル変数のスロット0に配置されますが、静的メソッドの場合、最初のスロット0が使用されます。メソッドのパラメーター)。
違いがあるかもしれませんし、それは特定のコードに対してどちらの方向に行くかもしれませんし、JVMのマイナーリリースでさえ変わるかもしれません。
これは間違いなく、忘れてはならない小さな効率の97%の一部です。
TableView
、何百万ものレコードを検索します。
理論的には、より安価です。
静的な初期化は、オブジェクトのインスタンスを作成しても行われますが、静的なメソッドは、コンストラクターで通常行われる初期化を行いません。
しかし、私はこれをテストしていません。
それはあなたの流れにも依存していることをここで他の素晴らしい答えに加えたいと思います、例えば:
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);
};
};