4

プロセッサ メモリをエミュレートするための配列を作成するとします。

byte[] mem = new byte[0xF00];

この配列はエミュレーション操作の過程で使用され、最終的には (読み取り: 頻繁に) 破棄またはリセットする必要があります。私の質問は、どちらが速いですか、なぜですか?

mem = new byte[0xF00];

また:

for(int i = 0; i < mem.length; i++) mem[i] = 0;

重要ではないように見えるかもしれませんが、膨大な数のプロセッサをエミュレートする場合、わずかな効率が違いを生みます。速度の違いは、JVM のガベージ コレクションによるものです。1 つは、配列をダンプしてガベージ コレクションを実行する必要がありますが、JVM は新しいメモリを割り当てる (場合によってはゼロにする) 必要がなくなります。2 番目の方法では、JVM のコストは回避されますが、配列内の各要素を繰り返し処理する必要があります。

この質問に対する追加の警告として:

  1. データ型のサイズによってコストの比率は変わりますか? たとえば、 はどうshort[]ですか?
  2. アレイの長さはコスト比に影響しますか?
  3. 最も重要なことは、なぜですか?
4

5 に答える 5

5

自分でテストできますが、配列の削除と再作成はほぼ同じです。

ただし、2つの欠点があります

  • CPU データ キャッシュがスクロールし、その効果が低下します。
  • GC をトリガーする可能性が高くなります。特にこれを頻繁に行うと、システムが一時停止するか、速度が低下します (同時実行の場合)。

私は配列を再利用することを好みます。これが最速だからではありませんが、アプリケーションの残りの部分への影響がはるかに少ないからです。


for (int size = 16; size <= 16* 1024; size *= 2) {
    int count1 = 0, count1b = 0,count2 = 0;
    long total1 = 0, total1b = 0, total2 = 0;
    for (long i = 0; i < 10000000000L; i += size) {
        long start = System.nanoTime();
        long[] longs = new long[size];
        if (longs[0] + longs[longs.length - 1] != 0)
            throw new AssertionError();
        long mid = System.nanoTime();
        long time1 = mid - start;
        Arrays.fill(longs, 1L);
        long time2 = System.nanoTime() - mid;
        count1b++;
        total1b += time1;
        if (time1 < 10e3) {// no GC
            total1 += time1;
            count1++;
        }
        if (time2 < 10e3) {// no GC
            total2 += time2;
            count2++;
        }
    }
    System.out.printf("%s KB took on average of %,d ns to allocate, %,d ns to allocate including GCs and %,d ns to fill%n",
            size * 8 / 1024.0, total1 / count1, total1b/count1b, total2 / count2);
}

版画

0.125 KB took on average of 35 ns to allocate, 36 ns to allocate including GCs and 19 ns to fill
0.25 KB took on average of 39 ns to allocate, 40 ns to allocate including GCs and 31 ns to fill
0.5 KB took on average of 56 ns to allocate, 58 ns to allocate including GCs and 55 ns to fill
1.0 KB took on average of 75 ns to allocate, 77 ns to allocate including GCs and 117 ns to fill
2.0 KB took on average of 129 ns to allocate, 134 ns to allocate including GCs and 232 ns to fill
4.0 KB took on average of 242 ns to allocate, 248 ns to allocate including GCs and 368 ns to fill
8.0 KB took on average of 479 ns to allocate, 496 ns to allocate including GCs and 644 ns to fill
16.0 KB took on average of 1,018 ns to allocate, 1,055 ns to allocate including GCs and 1,189 ns to fill
32.0 KB took on average of 2,119 ns to allocate, 2,200 ns to allocate including GCs and 2,625 ns to fill
64.0 KB took on average of 4,419 ns to allocate, 4,604 ns to allocate including GCs and 4,728 ns to fill
128.0 KB took on average of 8,333 ns to allocate, 9,472 ns to allocate including GCs and 8,685 ns to fill

すべての場合において、一方のアプローチが他方よりも高速であると仮定するのは難しいことを証明するだけです。

を に変更するlong[]と、int[]ほとんど同じことがわかります

0.125 KB took on average of 35 ns to allocate, 36 ns to allocate including GCs and 16 ns to fill
0.25 KB took on average of 40 ns to allocate, 41 ns to allocate including GCs and 24 ns to fill
0.5 KB took on average of 58 ns to allocate, 60 ns to allocate including GCs and 40 ns to fill
1.0 KB took on average of 86 ns to allocate, 87 ns to allocate including GCs and 94 ns to fill
2.0 KB took on average of 139 ns to allocate, 143 ns to allocate including GCs and 149 ns to fill
4.0 KB took on average of 256 ns to allocate, 262 ns to allocate including GCs and 206 ns to fill
8.0 KB took on average of 472 ns to allocate, 481 ns to allocate including GCs and 317 ns to fill
16.0 KB took on average of 981 ns to allocate, 999 ns to allocate including GCs and 516 ns to fill
32.0 KB took on average of 2,098 ns to allocate, 2,146 ns to allocate including GCs and 1,458 ns to fill
64.0 KB took on average of 4,312 ns to allocate, 4,445 ns to allocate including GCs and 4,028 ns to fill
128.0 KB took on average of 8,497 ns to allocate, 9,072 ns to allocate including GCs and 7,141 ns to fill
于 2013-04-23T06:15:24.477 に答える
4

配列を再割り当てしても、実際には GC ごとのコストは増加しません。GC はライブ オブジェクトにアクセスしてコピーするだけで、デッド オブジェクトには何もしないためです。ただし、オブジェクトを割り当てると、マイナー GC がより頻繁に発生します。ただし、最近割り当てられたオブジェクトがまだ生きていない場合、マイナー GC のコストは非常に低く、メジャー GC はまったく発生しません。

また、現在のJavaバージョンではオブジェクトの割り当ては安価であり、割り当てスペースのゼロ化は、JVMが達成できる最も効率的なゼロ化であると簡単に推測できます。JVMと同じくらい速くコード内の配列をゼロにすることができれば(編集:Steven Schlanskerが述べたように、JITコンパイラはメモリ充填ループを最適化するかもしれません)、配列の再利用はより速くなるはずです。とにかく、あなたが説明した for ループが JIT コンパイラによって最適化されるまで、かなり遅くなると思います。

他の質問に答えるには:

  • GC はアロケーション スペース (Eden) を一度にゼロにするので、 a か かの違いはありませshort[]byte[]short[]ただし、 for ループは、 aの代わりに aを使用する場合、同じ量のバイトをゼロにするために半分の反復回数しか必要としbyte[]ません (バイトまたは short を 0 に設定しても違いはありません)。
  • 配列が長くなるほど、for ループに必要な反復回数が増えます。したがって、この成長は直線的です。GC は、関連するバイト範囲をゼロにするために償却された線形時間も必要とするため、両方のアプローチの比率が一定に保たれると思います。ただし、小さなメモリ領域よりも大きなメモリ領域をゼロにするより効率的な方法が存在する可能性があります。これにより、GC によって行われるゼロ化 (割り当て領域全体を一度に) がループ アプローチよりも効率的になります。非常に大きな配列の場合、状況が変わる可能性があります。それらは Tenured 世代に直接割り当てられるため (G1 が使用されない限り)、はるかに高価な主要な GC が発生します。
于 2013-04-23T08:15:51.033 に答える
1

私は間違いなくmem = new byte[0xF00];GCに残りを任せます。

メモリ使用量は少し大きくなるかもしれませんが、1 秒あたり何千回も実行しない限り、アプリケーションに影響を与えることはありません。

実行時間は大幅に短縮され、手動で GC を呼び出す必要はありません。

于 2013-04-23T06:15:02.730 に答える
0

ここで重要な要素は 4 つあります。

1) ターゲット プラットフォームは何ですか? (大量の RAM がありますか? 複数の CPU コアですか?) 2) 割り当てる予定のメモリの最大量はどれくらいですか? (量が多いほど、割り当て/割り当て解除が有利になる可能性があります) 3) どの JVM を使用する予定ですか? 4) アプリケーションのパフォーマンスがそれほど重要である場合、なぜ Java で開発しているのですか?

さらに言えば、「時期尚早の最適化について心配する必要はありません」。最初にソフトウェアを作成し、次にプロファイルを作成してから、パフォーマンスが遅い部分を最適化します。原則として、特にデータ構造が基本的に単なる空白のアドレス空間である場合、アルゴリズムのパフォーマンスは一般にデータ構造のパフォーマンスよりも大きな問題です。

于 2013-04-23T06:51:47.507 に答える