Java では、ゲッターを介してフィールドを参照する場合と変数を介してフィールドを参照する場合にパフォーマンスの違いはありますか?
-
20-09-2019 - |
質問
やることに違いはありますか
Field field = something.getSomethingElse().getField();
if (field == 0) {
//do something
}
somelist.add(field);
対
if (something.getSomethingElse().getField() == 0) {
//do something
}
somelist.add(something.getSomethingElse().getField());
ゲッターを介したフィールドへの参照にはパフォーマンス上のペナルティが発生しますか、それとも割り当てられた変数を参照するのと同じですか? 変数はメモリ空間への単なる参照であるため、ゲッターはそのメモリ空間にアクセスするための別の方法である必要があることを理解しています。
これは実践的な問題ではなく、学術的な問題 (単なる好奇心の学校) であることに注意してください。
解決
仮定して getSomethingElse()
と定義されている
public SomethingElse getSomethingElse() {
return this.somethingElse;
}
パフォーマンスの違いは最小限になります (インライン化される場合はゼロになります)。ただし、現実には、常にそうなるとは限りません。バックグラウンドで何らかの処理が行われている可能性があります (必ずしもオブジェクト自体でではなく、たとえば AOP プロキシ経由で)。したがって、繰り返しアクセスできるように結果を変数に保存することは良い考えかもしれません。
他のヒント
それは無視できるほどの損害です。あまり気にしすぎないでください。そうしないと、時期尚早な最適化の餌食になってしまいます。アプリケーションが遅い場合、それが原因ではありません。
ゲッターを介して変数にアクセスするとメソッド呼び出しが発生するという点が異なります。JVM は状況によってはメソッド呼び出しを最適化できるかもしれませんが、 は メソッド呼び出し。
とはいえ、コードの最大のボトルネックやパフォーマンスの問題がアクセサー メソッドによるオーバーヘッドである場合は、あまり心配する必要はないと思います。
パフォーマンスの低下はありますが (無視できるほど小さい可能性があります)、JVM はパフォーマンスを向上させるために、この呼び出しとすべての呼び出しをインライン化する場合があります。
2番目の方法はそのままにしておいた方が良いでしょう。
Sun の HotSpot のような優れた JVM を使用している場合は別です。ゲッターをインライン化して (ネイティブ コードに) コンパイルします。
ゲッターの使用は、防御手段として、また一般的な情報隠蔽として、一般に非常に良い方法です。
Java を使用して Android アプリケーションを作成する場合に注意すべき点が 1 つあります。http://developer.android.com/training/articles/perf-tips.html#GettersSetters
C ++のような母国語では、フィールドに直接アクセスする代わりに、getters(i = getCount())を使用するのが一般的です(i = mcount)。これはC ++の優れた習慣であり、C#やJavaなどの他のオブジェクト指向言語でしばしば練習されます。これは、コンパイラが通常アクセスをインラインできるため、フィールドアクセスを制限またはデバッグする必要がある場合は、いつでもコードを追加できます。
ただし、これは Android では悪い考えです。 仮想メソッド呼び出しは高価であり、インスタンスのフィールドルックアップよりもはるかに高価です。一般的なオブジェクト指向のプログラミングプラクティスに従い、パブリックインターフェイスにゲッターとセッターを持つことは合理的ですが、クラス内では常に直接フィールドにアクセスする必要があります。
JITがなければ、直接フィールドアクセスは、些細なゲッターを呼び出すよりも約3倍高速です。JIT(直接フィールドアクセスはローカルにアクセスするのと同じくらい安い)で、 直接フィールドアクセスは、些細なゲッターを呼び出すよりも約7倍高速です。
Proguardを使用している場合は、Proguardがアクセサをインラインできるため、両方の世界で最高のものを持つことができることに注意してください。
メソッドが処理を伴わない単純なゲッターの場合は問題ありません。広範な計算が必要な場合は、いずれにせよ、プロパティは希望どおりにはなりません。
違いを心配するのは、膨大な数 (数千回) の反復を伴うタイトなループの場合だけです。それでも、これはおそらく、アスペクトを使用して追加の処理を織り込んでいる場合にのみ問題となるでしょう (例:ロギング)、これには何千もの追加オブジェクトの作成が含まれる場合があります(例:JoinPoints とパラメーターのオートボックス化)、およびその結果として生じる GC の問題。
性能差は気にならないと思います。それについては考えず、現実的なシナリオでコードのプロファイリングに時間を費やしたほうがよいでしょう。おそらく、プログラムの遅い部分が思ったところにないことに気づくでしょう。
この投稿では JVM ではなく CLI VM について説明しますが、それぞれが同様のことを実行できるため、関連性があると思います。
私はこの特定の問題を JIT 用の特別な方法で処理しています。ここでの説明は概念的なものであり、コードではパフォーマンス上の理由から若干異なる方法で実装されていることに注意してください。アセンブリを読み込むときに、単にメンバー フィールドを返すだけかどうかをメソッド記述子にメモします。後で他のメソッドを JIT するときに、すべてを置き換えます call
これらのメソッドへの命令は、バイトコードで ldfld
命令をネイティブ コード ジェネレーターに渡す前に実行します。このようにして、次のことが可能になります。
- JIT で時間を節約します (
ldfld
JIT にかかるプロセッサ時間が短くなります。call
). - インラインプロパティでも ベースラインコンパイラ.
- 概して、パブリック プロパティ/プライベート フィールド パターンを使用すると、デバッガが切り離されたときにいかなる種類のパフォーマンス ペナルティも発生しないことが保証されます。(デバッガーが接続されている場合、アクセサーをインライン化できません。)
VM テクノロジの大手企業が、これと同様の (そしておそらくはそれよりも優れた) ものをすでに自社の製品に実装していることに疑いの余地はありません。