web-dev-qa-db-ja.com

Java:ゲッターとセッターは直接アクセスよりも高速ですか?

Linuxネットブックで VisualVM 1.3.7で書いているJavaレイトレーサーのパフォーマンスをテストしました。プロファイラーで測定しました。
楽しみのために、ゲッターとセッターを使用することと、フィールドに直接アクセスすることの間に違いがあるかどうかをテストしました。ゲッターとセッターは、追加のない標準コードです。

違いは予想していませんでした。しかし、コードに直接アクセスするのは遅くなりました。

これがVector3Dでテストしたサンプルです。

public float dot(Vector3D other) {
    return x * other.x + y * other.y + z * other.z;
}

時間:1542ミリ秒/ 1,000,000回の呼び出し

public float dot(Vector3D other) {
    return getX() * other.getX() + getY() * other.getY() + getZ() * other.getZ();
}

時間:1453ミリ秒/ 1,000,000回の呼び出し

私はそれをマイクロベンチマークではなく、レイトレーサーでテストしました。私がコードをテストした方法:

  • 私は最初のコードでプログラムを開始し、それをセットアップしました。レイトレーサーはまだ実行されていません。
  • プロファイラーを起動し、初期化が完了してからしばらく待ちました。
  • 私はレイトレーサーを始めました。
  • VisualVMが十分な呼び出しを示したら、プロファイラーを停止して少し待ちました。
  • レイトレーサープログラムを閉じました。
  • 最初のコードを2番目のコードに置き換え、コンパイル後に上記の手順を繰り返しました。

両方のコードに対して少なくとも20,000,000回の呼び出しを実行しました。不要なプログラムを閉じました。 CPUをパフォーマンスに設定したので、CPUクロックは最大でした。いつも。
2番目のコードが6%高速になるのはどうしてですか?

21
Mr.Yeah

この質問に答えるのを手伝ってくれてありがとう。結局、私は答えを見つけました。

まず、 ボヘミアンは正しいPrintAssembly で生成されたアセンブリコードが同一であるという仮定を確認しました。はい、バイトコードは異なりますが、生成されるコードは同じです。
だから masterxiloは正しい :プロファイラーが犯人でなければならない。しかし、タイミングフェンスやその他の計測コードに関するmasterxiloの推測は真実ではありません。両方のコードは最終的に同一です。

それで、まだ疑問があります:プロファイラーで2番目のコードが6%速いように見えるのはどうしてですか?

答えは、VisualVMがどのように測定するかにあります。 プロファイリングを開始する前に、キャリブレーションデータが必要です。 これは、プロファイラーによって引き起こされるオーバーヘッド時間を取り除くために使用されます。
キャリブレーションデータは正しいですが、測定の最終的な計算は正しくありません。 VisualVMは、バイトコード内のメソッド呼び出しを確認します。しかし、JITコンパイラが最適化中にこれらの呼び出しを削除することはわかりません。
したがって、存在しないオーバーヘッド時間を削除します。そして、それが違いが現れる方法です。

13
Mr.Yeah

たくさんのJVMウォームアップを使用してマイクロベンチマークを実行したところ、2つのアプローチはまったく同じ実行時間かかりますであることがわかりました。

これは、JITコンパイラがフィールドへの直接アクセスでgetterメソッドをインライン化し、それらを同一のバイトコードにするために発生します。

31
Bohemian

統計学のコースを受講していない場合は、どれだけうまく書かれていても、プログラムのパフォーマンスにalwaysのばらつきがあります。これらの2つのメソッドがほぼ同じ速度で実行されているように見える理由は、アクセサーフィールドが実行するのは1つだけであるためです。つまり、特定のフィールドを返します。アクセサメソッドでは他に何も起こらないため、どちらの戦術もほとんど同じことを行います。ただし、カプセル化について知らない場合、つまりプログラマーがデータ(フィールドまたは属性)をユーザーからどれだけ隠すかがわからない場合、カプセル化の主なルールは内部データを公開しないことです。ユーザー。フィールドをパブリックとして変更すると、他のクラスがそれらのフィールドにアクセスでき、ユーザーにとってvery危険になる可能性があります。そのため、私はalwaysJavaプログラマーに、フィールドが悪意のある人の手に渡らないように、アクセサーメソッドとミューテーターメソッドを使用することをお勧めします。

プライベートフィールドにアクセスする方法に興味がある場合は、リフレクションを使用できます。リフレクションは、特定のクラスのデータに実際にアクセスするため、本当に必要な場合は変更できます。些細な例として、Java.lang.Stringクラスにchar []型のプライベートフィールド(つまり、char配列)が含まれていることを知っているとします。ユーザーには表示されないため、フィールドに直接アクセスすることはできません。 (ちなみに、メソッドJava.lang.String.toCharArray()がフィールドにアクセスします。)各文字に連続してアクセスし、各文字をコレクションに格納する場合(簡単にするために、Javaを使用しないのはなぜですか)。 util.List?)、この場合のリフレクションの使用方法は次のとおりです。

/**
    This method iterates through each character in a <code>String</code> and places each of them into a <code>Java.util.List</code> of type <code>Character</code>.
    @param str The <code>String</code> to extract from.
    @param list The list to store each character into. (This is necessary because the compiler knows not which <code>List</code> to use, so it will automatically clear the list anyway.)
*/
public static void extractStringData(String str, List<Character> list) throws IllegalAccessException, NoSuchFieldException
{
    Java.lang.reflect.Field value = String.class.getDeclaredField("value");
    value.setAccessible(true);
    char[] data = (char[]) value.get(str);
    for(char ch : data) list.add(ch);
}

補足として、リフレクションはプログラムのパフォーマンスのlotを奪うことに注意してください。何らかの理由でmustアクセスするフィールド、メソッド、または内部クラスまたはネストされたクラスがある場合(とにかく可能性は非常に低いです)、リフレクションを使用する必要があります。リフレクションが貴重なパフォーマンスを奪う主な理由は、リフレクションがスローする比較的無数の例外のためです。助けてくれてうれしいです!

0
user3709221