1

多くのオブジェクトを格納するために効率的なメモリが必要な場合があります。Java でこれを行うには、いくつかのプリミティブ配列 (以下の理由を参照) を使用するか、変換のために少し CPU オーバーヘッドを生成する大きなバイト配列を使用する必要があります。

例: を持っていclass Point { float x; float y;}ます。ここで、32 ビット JVM で、少なくとも浮動小数に N * 8 バイト、参照に N * 4 バイトかかる N ポイントを配列に格納する必要があります。したがって、少なくとも 1/3 はガベージです (ここでは、通常のオブジェクト オーバーヘッドはカウントされません)。しかし、これを 2 つの float 配列に格納すれば、すべて問題ありません。

私の質問: Java が参照配列のメモリ使用量を最適化しないのはなぜですか? C++ で行われているように、オブジェクトを配列に直接埋め込まないのはなぜですか?

たとえば、クラス Point final のマーク付けは、JVM が Point クラスのデータの最大長を確認するのに十分なはずです。または、これはどこで仕様に反するのでしょうか? また、これにより、大きなn次元行列などを処理するときに多くのメモリが節約されます

更新

JVM が理論的に (舞台裏などで) 最適化できるかどうか、またどのような条件下で JVM を強制できるかどうかではなく、知りたいです。結論の 2 点目は、簡単にできない理由だと思います。

JVM が知る必要がある結論:

  1. JVM が 1 つの配列エントリの長さを推測できるようにするには、クラスを final にする必要があります。
  2. 配列は読み取り専用である必要があります。もちろん、 のように値を変更することはできPoint p = arr[i]; p.setX(i)ますが、 を介して配列に書き込むことはできませんinlineArr[i] = new Point()。または、JVM は「Java 方式」に反するコピー セマンティクスを導入する必要があります。アロスの答えを見る
  3. 配列を初期化する方法 (既定のコンストラクターを呼び出すか、メンバーを既定値に初期化したままにする)
4

3 に答える 3

3

これは言語レベルの選択ではないため、Java ではこれを行う方法が提供されていません。C、C++ などは、システム レベルの機能を理解し、使用している特定のアーキテクチャに基づいて決定を下すことが期待されるシステム レベルのプログラミング言語であるため、これを行う方法を公開しています。

Java では、JVM をターゲットにしています。JVM は、これが許容されるかどうかを指定しません (私はこれが正しいと仮定しています。私がここにいることを証明するために JLS を徹底的に調べていません)。Java コードを作成するときは、JIT が賢明な決定を下すことを信頼するという考え方です。ここで、参照型を配列などに折りたたむことができます。したがって、ここでの「Java の方法」は、それが発生するかどうかを指定できないということですが、JIT がその最適化を行い、パフォーマンスを向上させることができれば、可能であり、そうすべきです。

特にこの最適化が実装されているかどうかはわかりませんが、似たようなものがあることは知っていますnew。オブジェクトはメソッド ローカルであり、オブジェクトのフィールドをスタック上または直接 CPU レジスタに割り当てることができ、言語を変更することなく「ヒープ割り当て」のオーバーヘッドを完全に取り除くことができます。

更新された質問の更新

「これはまったくできるか」という質問であれば、答えはイエスだと思います。いくつかの特殊なケース (null ポインターなど) がありますが、それらを回避できるはずです。null 参照の場合、JVM は、null 要素が存在しないことを確信したり、前述のようにビット ベクトルを保持したりできます。これらの手法はどちらも、配列参照がメソッドから離れないことを示すエスケープ分析に基づいている可能性があります。たとえば、オブジェクト フィールドに格納しようとすると、簿記が難しくなることがわかります。

于 2012-02-22T00:07:46.833 に答える
1

あなたが説明するシナリオはメモリを節約するかもしれませんが(実際にはそれができるかどうかはわかりませんが)、実際にオブジェクトを配列に配置するときにかなりの計算オーバーヘッドが追加される可能性があります。new Point()作成するオブジェクトを実行すると、ヒープに動的に割り当てられることを考慮してください。したがって、Point呼び出しによって100個のインスタンスを割り当てる場合new Point()、それらの場所がメモリ内で隣接するという保証はありません(実際、それらはメモリの連続するブロックに割り当てられない可能性があります)。

では、インスタンスは実際にどのようPointにして「圧縮された」配列になりますか?Javaは、配列に割り当てられたメモリの連続するブロックにすべてのフィールドを明示的にコピーする必要があるように思われます。Pointこれは、多くのフィールドを持つオブジェクトタイプではコストがかかる可能性があります。それだけでなく、元のPointインスタンスはまだヒープ上と配列内のスペースを占有しています。したがって、すぐにガベージコレクションされない限り(配列に配置されたコピーを指すように参照を書き換えることができ、理論的には元のインスタンスの即時のガベージコレクションが可能になると思います)、実際にはより多くのストレージを使用しています。参照を配列に格納したばかりの場合です。

さらに、複数の「圧縮された」配列と可変オブジェクトタイプがある場合はどうなりますか?オブジェクトを配列に挿入すると、必然的にそのオブジェクトのフィールドが配列にコピーされます。したがって、次のようなことを行う場合:

Point p = new Point(0, 0);
Point[] compressedA = {p};  //assuming 'p' is "optimally" stored as {0,0}
Point[] compressedB = {p};  //assuming 'p' is "optimally" stored as {0,0}

compressedA[0].setX(5)  
compressedB[0].setX(1)  

System.out.println(p.x);
System.out.println(compressedA[0].x);
System.out.println(compressedB[0].x);

...あなたは得るでしょう:

0
5
1

...論理的には。のインスタンスは1つだけである必要がありますPoint。参照を保存すると、この種の問題が回避されます。また、重要なオブジェクトが複数の配列間で共有されている場合は、各配列にそのオブジェクトのすべてのフィールドのコピーが保存されている場合よりも 、合計ストレージ使用量がおそらく少なくなります。

于 2012-02-22T00:58:24.823 に答える
0

これは、次のような些細なクラスを提供することと同じではありませんか?

class Fixed {
   float hiddenArr[];
   Point pointArray(int position) {
      return new Point(hiddenArr[position*2], hiddenArr[position*2+1]);
   }
}

また、プログラマーがそれを望んでいると明示的に言わなくても、これを実装することは可能です。JVM はすでに「値の型」 (C++ の POD 型) を認識しています。それらの中に他の単純な古いデータ型のみを持つもの。HotSpot はスタックの省略中にこの情報を使用すると思いますが、配列に対しても使用できない理由はありませんか?

于 2012-02-21T23:51:08.687 に答える