double の大きな配列を次のように割り当てます
double[] x = new double[ n ];
ここで n は大きく、時間を節約するために初期化を避けたいと考えています。出来ますか?
簡単な答え: いいえ。配列は、作成時に常にゼロに設定されます。
プロファイリングでこれが大きなボトルネックであることがわかった場合は、配列インスタンスのプールを保持し、各セットの長さをn
これまで以上に大きくすることを検討できます。問題は、データ配列と使用される実際の長さを含むラッパー オブジェクトがおそらく必要になることですdata.length
。
または何かを使用してArrayList
、要素を追加する必要があるときに配列を構築できますか? それがあなたの問題である場合、それは初期化時間を節約します。
ArrayList<double> x = new ArrayList<double>();
長すぎる配列を初期化したくない場合は、配列サイズの制限を決定して、あまり待機させないようにすることができます。長い配列を小さな配列に分割することをお勧めします。配列を保持するリストを定義します。配列がいっぱいになったら、それをリストに追加します。そして、新しいものを埋めに行きます。
import java.util.ArrayList;
import java.util.List;
public class Tester {
private static final int LIMIT = 30;
private static int index = 0;
private static int[][] lookup;
private static List<int[][]> list = new ArrayList<int[][]>();
public static void main(String[] args) {
lookup = new int[LIMIT][1];
for (int i = 0; i <= 93; i++) {
addToArr(i);
}
list.add(lookup);
for (int[][] intArr : list) {
for (int i = 0; i < intArr.length; i++) {
System.out.print(intArr[i][0] + ",");
}
}
}
public static void addToArr(int value) {
if (index == LIMIT) {
list.add(lookup);
lookup = new int[LIMIT][1];
index = 0;
}
lookup [index++][0] = value;
}
}
版画:
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49, 50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74, 75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
**警告**安全でない代替手段**
これは正確な解決策ではありませんが、実行可能な代替手段になる可能性があります。この方法にはいくつかのリスクがあります。しかし、本当に絶対に必要な場合は、この方法を使用できます。このメソッドは、文書化されていないクラスを使用して、オフヒープメモリをsun.misc.Unsafe
割り当て、double 値を格納しています。オフヒープとは、ガベージコレクションされていないことを意味するため、関連するメモリの割り当てを解除するように注意する必要があります。
次のコードは、sun.misc.Unsafe に関するこのブログ投稿に基づいています。
import java.lang.reflect.Field;
import sun.misc.Unsafe;
@SuppressWarnings("restriction")
public class SuperDoubleArray {
private final static Unsafe unsafe = getUnsafe();
private final static int INDEX_SCALE = 8;
private long size;
private long address;
public SuperDoubleArray(long size) {
this.size = size;
address = unsafe.allocateMemory(size * INDEX_SCALE);
}
private static Unsafe getUnsafe() {
try {
Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
singleoneInstanceField.setAccessible(true);
return (Unsafe) singleoneInstanceField.get(null);
} catch (IllegalArgumentException | SecurityException
| NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public void set(long i, double value) {
unsafe.putDouble(address + i * INDEX_SCALE, value);
}
public double get(long idx) {
return unsafe.getDouble(address + idx * INDEX_SCALE);
}
public long size() {
return size;
}
public void deallocate() {
unsafe.freeMemory(address);
}
}
次のコードは、初期化されたメモリからのランダムな double 値を出力します。
SuperDoubleArray sda = new SuperDoubleArray(100);
for (int i=0; i<sda.size(); i++) {
System.out.println(sda.get(i));
}
sda.deallocate();
安全性/範囲チェックはありません。何もありません。JVM を簡単にクラッシュさせることができます。SUN 以外の JRE では動作しない可能性があり、将来の SUN JRE バージョンでは動作しなくなる可能性さえありますが、場合によってはそれが唯一の解決策になる可能性があります。 . Integer.MAX_VALUE
Java 配列とは異なり、> サイズの疑似配列を割り当てることもできます。
java.nio.ByteBuffer.allocateDirect(...)
実際には、舞台裏で同じ Unsafe クラスを使用してバイト バッファーを割り当てます。これを使用ByteBuffer.allocateDirect(8*size).asDoubleBuffer()
して に適応させることもできますDoubleBuffer
が、ByteBuffer.allocateDirect(...)
まだバッファーをゼロで初期化しているため、パフォーマンスのオーバーヘッドが生じる可能性があります。
他の人がすでに述べたように、簡単な答えは次のとおりです。いいえ、初期化部分を回避することはできません。何らかのnative
割り当てを使用しているか、バイト バッファーのビューとして作成されたIntBufferを使用していない限り、バイト バッファー自体が直接である場合にのみ、直接になります。
それらのいずれも使用していない場合は、できるだけ早く配列を割り当てて初期化するために、GC
呼び出しを最小限に抑える必要があり、その配列を格納して操作するために必要なメモリが JVM にあることを確認する必要があります。
Albert Hendriks のケースstatic int[][] lookup = new int[113088217][2]
では、少なくとも 2.3G (12+113088217*(12+2*4) バイト) のメモリがないと、JVM は必要なスペースを割り当てることができません。必要なパディング スペース (メモリ アラインメント) も追加していないことに注意してください。
lookup = new int[2*113088217];
パフォーマンスが速くなる理由を答えます。これは、サブ配列 (ヘッダー + 要素 + 各サブ配列の配置) がないため、処理されるメモリがはるかに少なく、(2*113088217*4+12) バイト = ~ 804M しか必要ないためです。
次のように double 配列で作業する必要がある場合は、ArrayList を使用して初期化の時間を節約し、それを配列に変換できます。
List<Double> withoutInitializing = new ArrayList<Double>();
Double[] nowYouConvert = (Double[]) withoutInitializing.toArray();
Java ドキュメントから:
toArray: このリスト内のすべての要素を適切な順序 (最初の要素から最後の要素まで) で含む配列を返します。
返された配列は、それへの参照がこのリストによって維持されないという点で「安全」です。(言い換えると、このリストが配列によって支えられている場合でも、このメソッドは新しい配列を割り当てる必要があります)。したがって、呼び出し元は、返された配列を自由に変更できます。
このメソッドは、配列ベースの API とコレクション ベースの API の間のブリッジとして機能します。
定義: コレクション内の toArray()
「new double[n]」ステートメントを宣言するとすぐに、配列が初期化されます。それを回避する方法はありません。
最適化のためにこれを行う場合は、時期尚早の最適化についてお読みください。プログラムが壁にぶつかっていなければ、最適化する価値はありません。そして、それは間違いなく、最適化すべき配列でもありません。
質問を理解しているので、コメントで述べたように、本当の問題は巨大な2次元配列を割り当てることです
"static int[][] lookup = new int[113088217][2]; は機能しませんが、private final static int[][] lookup = new int[11308821][2]; (10 倍少ない) は少なくなります一秒より」
これが正しいと仮定すると、はい、膨大な数の場合、非常に遅いです。113088217 * 2 ints の単一ブロックを割り当てていません! Objectである113088217 個の個別のarrayを割り当てています。それぞれをヒープに割り当てる必要があります。これは、JVM が 1 億回以上スペースを探し、使用済みとしてマークし、メモリが不足したときに GC を実行する可能性があることを意味します。 .. 配列はそれぞれ (これらの膨大な数で) かなりの量の追加メモリを必要とし、さらにそれぞれに 2 つの int が含まれます。
この巨大なケースの場合:
1) インデックスを切り替えて進む
static int[][] lookup = new int[2][113088217]
1 億 1300 万の配列を割り当てる代わりに、これは2 つの を割り当てます。配列内でルックアップを行うときは、2 つのインデックスを切り替えます。
2) 1D 配列を作成し、自分でルックアップを行う
static int[] lookup = new int[2*113088217];
これには、適切なインデックスを見つけるために簡単な計算を行う必要があります。配列に直接アクセスする代わりに、計算を行う関数を作成し、呼び出し元のコードでそれを使用する必要があります。
配列を初期化しない方法のいくつかの回避策。
可能な最大エントリ数よりも大きいことが保証されている配列を作成し、部分的に埋めます。
たとえば、ユーザーが 100 を超える入力値を提供しないように決定できます。次に、サイズ 100 の配列を割り当てます。
final int VALUES_LENGTH = 100;
double[] values = new double[VALUES_LENGTH];
次に、配列内の実際に使用されている要素の数を示すコンパニオン変数を保持します。
int valuesSize = 0;
ここで、values.length は配列値の容量であり、配列valuesSize
の現在のサイズです。配列に要素を追加し続け、そのvaluesSize
たびに変数をインクリメントします。
values[valuesSize] = x;
valuesSize++;
このようにして、valuesSize
常に正しい要素数が含まれます。次のコード セグメントは、部分的に埋められた配列に数値を読み込む方法を示しています。
int valuesSize = 0;
Scanner in = new Scanner(System.in);
while (in.hasNextDouble()) {
if (valuesSize < values.length) {
values[valuesSize] = in.nextDouble();
valuesSize++;
}
}
このループの最後にvaluesSize
、配列内の実際の要素数が含まれます。
たとえば、スペースを使い果たすことなく、任意の長さのシーケンス番号を配列に読み込む方法を次に示します。
int valuesSize = 0;
while (in.hasNextDouble()) {
if (valuesSize == values.length) {
values = Arrays.copyOf(values, 2 * values.length);
}
values[valuesSize] = in.nextDouble();
valuesSize++;
}