Javaキャストではオーバーヘッドが発生しますか?どうして?


103

あるタイプのオブジェクトを別のタイプにキャストするときにオーバーヘッドはありますか?または、コンパイラはすべてを解決し、実行時にコストはかかりませんか?

これは一般的なことですか、それともさまざまなケースがありますか?

たとえば、Object []の配列があり、各要素の型が異なる可能性があるとします。ただし、たとえば、要素0はDouble、要素1はStringであることを常に確信しています。(私はこれが間違った設計であることを知っていますが、私がこれをしなければならなかったと仮定しましょう。)

Javaの型情報は実行時に保持されますか?または、コンパイル後にすべてが忘れられ、(Double)elements [0]を実行した場合、ポインタをたどり、それらの8バイトをdoubleとして解釈します。

Javaで型がどのように行われるかについては非常に不明確です。本や記事についての推薦があれば、感謝します。


instanceofとキャストのパフォーマンスは非常に優れています。私はここでの問題に異なるアプローチの周りのJava7でのいくつかのタイミングを投稿:stackoverflow.com/questions/16320014/...
Wheezil

この他の質問は非常に良い答えていstackoverflow.com/questions/16741323/...
user454322

回答:


77

キャストには2つのタイプがあります。

暗黙的なキャスト。型からより広い型にキャストする場合、これは自動的に行われ、オーバーヘッドはありません。

String s = "Cast";
Object o = s; // implicit casting

明示的なキャスト。より広いタイプからより狭いタイプに移動する場合。この場合、そのようなキャストを明示的に使用する必要があります。

Object o = someObject;
String s = (String) o; // explicit casting

この2番目のケースでは、2つのタイプをチェックする必要があるため、実行時にオーバーヘッドが発生し、キャストが実行できない場合、JVMはClassCastExceptionをスローする必要があります。

JavaWorldからの引用:キャストのコスト

キャストは、タイプ間、特に参照タイプ間での変換に使用されます。ここでは、キャスト操作のタイプについて説明します。

アップキャスト操作(Java言語仕様では拡大変換とも呼ばれます)は、サブクラス参照を祖先クラス参照に変換します。このキャスト操作は常に安全であり、コンパイラーによって直接実装できるため、通常は自動で行われます。

ダウンキャスト操作(Java言語仕様ではローイング変換とも呼ばれます)は、祖先クラス参照をサブクラス参照に変換します。Javaではキャストが有効であることを確認するために実行時にキャストをチェックする必要があるため、このキャスト操作では実行オーバーヘッドが発生します。参照されるオブジェクトがキャストのターゲットタイプまたはそのタイプのサブクラスのインスタンスでない場合、試行されたキャストは許可されず、java.lang.ClassCastExceptionをスローする必要があります。


100
そのJavaWorldの記事は10年以上前のものであるため、最高級のソルトの非常に大きな粒度でのパフォーマンスについての記述はあります。
skaffman 2010年

@skaffman、実際、私はそれが(パフォーマンスに関するかどうかに関わらず)細かい塩を使ってどんな発言もします。
Pacerier 2014

キャストされたオブジェクトを参照に割り当てず、その上でメソッドを呼び出すだけの場合、同じケースになりますか?いいね((String)o).someMethodOfCastedClass()
Parth Vishvajit 2017年

2
現在、記事はほぼ20年前のものです。そして答えも何年も前のものです。この質問には現代的な答えが必要です。
Raslanove

プリミティブ型はどうですか?たとえば、intからshortにキャストすると、同様のオーバーヘッドが発生しますか?
luke1985

44

Javaの合理的な実装の場合:

各オブジェクトは、とりわけ含むヘッダを有し、実行時の型へのポインタ(例えば、Double又はString、それはすることができませんでしたCharSequence又はAbstractList)。ランタイムコンパイラ(通常、Sunの場合はHotSpot)がタイプを静的に決定できないと仮定すると、生成されたマシンコードでいくつかのチェックを実行する必要があります。

最初に、ランタイムタイプへのポインタを読み取る必要があります。いずれにしても、同様の状況で仮想メソッドを呼び出すために必要です。

クラス型にキャストする場合、を押すまでスーパークラスがいくつあるかが正確にわかるjava.lang.Objectため、型ポインターから一定のオフセットで型を読み取ることができます(実際にはHotSpotの最初の8つ)。これも、仮想メソッドのメソッドポインターを読み取ることに似ています。

次に、読み取られた値は、キャストの予想される静的タイプとの比較が必要です。命令セットのアーキテクチャによっては、別の命令が誤った分岐で分岐(またはフォールト)する必要があります。32ビットARMなどのISAには条件付き命令があり、悲しいパスをハッピーパスを通過させることができる場合があります。

インターフェイスの多重継承により、インターフェイスはより困難になります。通常、インターフェースへの最後の2つのキャストは、ランタイムタイプにキャッシュされます。非常に早い段階(10年以上前)では、インターフェースは少し遅くなりましたが、それはもはや関係ありません。

うまくいけば、この種のことはパフォーマンスにほとんど関係がないことがわかります。あなたのソースコードはより重要です。パフォーマンスの点では、シナリオの最大のヒットは、オブジェクトポインターをいたるところに追跡することによるキャッシュミスである可能性があります(型情報はもちろん一般的です)。


1
おもしろい-これは、インターフェイスではないクラスで、スーパークラスsc =(Superclass)subclassと書いた場合、(jit ie:load time)コンパイラーは、スーパークラスとサブクラスのそれぞれのObjectからのオフセットを「クラス」ヘッダーに「静的に」入れ、単純な追加+比較によって問題を解決できることを確認します。-それは素晴らしくて高速です:)インターフェースについては、小さなハッシュテーブルまたはbtreeよりも悪くないと思いますか?
ピーターク2012年

@peterkクラス間のキャストでは、オブジェクトアドレスと "vtbl"(メソッドポインターのテーブル、クラス階層のテーブル、インターフェイスキャッシュなど)の両方が変更されません。したがって、[クラス]キャストは型をチェックし、それが当てはまる場合は他に何もする必要はありません。
トム・ホーティン-

8

たとえば、Object []の配列があり、各要素の型が異なる可能性があるとします。ただし、たとえば、要素0はDouble、要素1はStringであることを常に確信しています。(私はこれが間違った設計であることを知っていますが、私がこれをしなければならなかったと仮定しましょう。)

コンパイラーは、配列の個々のエレメントのタイプを認識しません。各要素式のタイプが配列要素タイプに割り当て可能であることを確認するだけです。

Javaの型情報は実行時に保持されますか?または、コンパイル後にすべてが忘れられ、(Double)elements [0]を実行した場合、ポインタをたどり、それらの8バイトをdoubleとして解釈します。

一部の情報は実行時に保持されますが、個々の要素の静的タイプは保持されません。これは、クラスファイル形式を見ればわかります。

理論的には、JITコンパイラが「エスケープ分析」を使用して、一部の割り当てで不要な型チェックを排除できる可能性があります。ただし、これをあなたが提案している程度に行うことは、現実的な最適化の範囲を超えてしまいます。個々の要素のタイプを分析することの見返りは小さすぎるでしょう。

その上、人々はとにかくそのようなアプリケーションコードを書くべきではありません。


1
プリミティブはどうですか? (float) Math.toDegrees(theta)ここでもかなりのオーバーヘッドがありますか?
SD

2
一部のプリミティブキャストにはオーバーヘッドがあります。それが重要であるかどうかは、状況によって異なります。
スティーブンC

6

実行時にキャストを実行するためのバイトコード命令が呼び出されcheckcastます。を使用javapしてJavaコードを逆アセンブルし、生成される命令を確認できます。

配列の場合、Javaは実行時に型情報を保持します。ほとんどの場合、コンパイラーは型エラーをキャッチしますがArrayStoreException、オブジェクトを配列に格納しようとすると、型に一致しない(そしてコンパイラーがキャッチしなかった)場合があります。 。Java言語仕様は、次の例を示します:

class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
    public static void main(String[] args) {
        ColoredPoint[] cpa = new ColoredPoint[10];
        Point[] pa = cpa;
        System.out.println(pa[1] == null);
        try {
            pa[0] = new Point();
        } catch (ArrayStoreException e) {
            System.out.println(e);
        }
    }
}

Point[] pa = cpaColoredPointPointのサブクラスでpa[0] = new Point()あるため有効ですが、無効です。

これは、実行時に型情報が保持されないジェネリック型とは対照的です。コンパイラーcheckcastは、必要に応じて命令を挿入します。

このジェネリック型と配列の型付けの違いにより、配列とジェネリック型を混在させることはしばしば不適切になります。


2

理論的には、オーバーヘッドが導入されます。ただし、最新のJVMはスマートです。実装はそれぞれ異なりますが、JITがキャスティングチェックを最適化して、競合が発生しないことを保証できる実装が存在すると想定することは不合理ではありません。どの特定のJVMがこれを提供するかについては、私はあなたに言うことができませんでした。私はJIT最適化の詳細を自分で知りたいと認めなければなりませんが、これらはJVMエンジニアが心配することです。

この話の教訓は、最初に理解しやすいコードを書くことです。スローダウンが発生している場合は、問題をプロファイリングして特定します。キャストが原因ではない可能性が高いです。クリーンで安全なコードを犠牲にして、最適化を試みてください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.