配列をコピーするためのforループよりもSystem.arraycopy(…)を使用する方が良いですか?


89

2つの小さな配列をまとめてオブジェクトの新しい配列を作成したいと思います。

nullにすることはできませんが、サイズは0にすることができます。

これら2つの方法を選択することはできません。それらは同等ですか、それとももう1つ効率的ですか(たとえば、system.arraycopy()はチャンク全体をコピーします)?

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
System.arraycopy(publicThings, 0, things, 0, publicThings.length);
System.arraycopy(privateThings, 0, things,  publicThings.length, privateThings.length);

または

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
for (int i = 0; i < things.length; i++) {
    if (i<publicThings.length){
        things[i] = publicThings[i]
    } else {
        things[i] = privateThings[i-publicThings.length]        
    }
}

唯一の違いはコードの外観ですか?

編集:リンクされた質問に感謝しますが、彼らは未解決の議論を持っているようです:

it is not for native typesバイト[]、オブジェクト[]、文字[]の場合、本当に高速ですか?他のすべてのケースでは、タイプチェックが実行されます。これは私のケースなので、同等です...いいえ?

別のリンクされた質問で、彼らはthe size matters a lot、サイズが> 24のsystem.arraycopy()が勝つ場合、10より小さい場合は、手動forループの方が良いと述べています...

今、私は本当に混乱しています。


16
arraycopy()ネイティブコールであり、確実に高速です。
Sotirios Delimanolis 2013

4
2つの異なる実装のベンチマークを試みましたか?
Alex


15
将来、最も読みやすく、維持しやすいと思われる方を選択する必要があります。これがボトルネックの原因であると判断した場合にのみ、アプローチを変更する必要があります。
arshajii 2013

1
車輪を再発明しないでください!
camickr 2013

回答:


87
public void testHardCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    for(int i = 0; i < out.length; i++)
    {
        out[i] = bytes[i];
    }
}

public void testArrayCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    System.arraycopy(bytes, 0, out, 0, out.length);
}

JUnitテストは実際にはベンチマークに最適ではないことを知っていますが、
testHardCopyBytesは完了するまで

0.157秒かかり、testArrayCopyBytesは完了するまでに0.086秒かかりました。

それは仮想マシンに依存すると思いますが、単一の配列要素をコピーするのではなく、メモリのブロックをコピーするように見えます。これは絶対にパフォーマンスを向上させます。

編集:
System.arraycopyのパフォーマンスはどこにでもあるようです。バイトの代わりに文字列が使用され、配列が小さい(サイズ10)場合、次の結果が得られます。

    String HC:  60306 ns
    String AC:  4812 ns
    byte HC:    4490 ns
    byte AC:    9945 ns

配列のサイズが0x1000000の場合、次のようになります。System.arraycopyは間違いなく大きな配列で勝つようです。

    Strs HC:  51730575 ns
    Strs AC:  24033154 ns
    Bytes HC: 28521827 ns
    Bytes AC: 5264961 ns

なんて奇妙なことでしょう。

参照のコピーが異なることを指摘してくれたDarenに感謝します。これにより、これはより興味深い問題になりました。


2
あなたの努力に感謝しますが、一見重要なポイントを逃しました:非ネイティブ型(anythignでランダムクラスを作成して、配列に参照が含まれるようにします)とサイズ...配列サイズが小さい場合、手動のforループの方が高速です。これを修正しますか?
Daren

2
わあ、そうだね!それは興味深いです。バイトの代わりにこれらの配列に文字列を配置すると、大きな違いが生じます:<<< testHardCopyStrs:0.161s >>> <<< testArrayCopyStrs:0.170s >>>
Trent Small

その後、どのような結果が得られますか?また、配列サイズ= 10で試してみるのも興味深いでしょう...ありがとう!(ここにIDEがあればいいのに、コンパイラなしでコーディングしています)。
Daren

さて、私はそれらをいくつかのSystem.nanoTime()呼び出しでラップし、サイズ= 10に設定して、それぞれが何ナノ秒かかるかを確認しました。小さなプリミティブ配列のように見えますが、ループの方が優れています。参照については、arrayCopyの方が優れています。<<< testHardCopyBytes:4491 ns >>> <<< testHardCopyStrs:56778 ns >>> <<< testArrayCopyBytes:10265 ns >>> <<< testArrayCopyStrs:4490 ns >>>
トレント小

非常に興味深い結果!どうもありがとうございます!これを含めるためにあなたの答えを編集してください。そうすれば私は喜んでそれを受け入れます。そうすれば、誰もが最初に見るようになります...あなたはすでに私の投票を獲得しました。:)
Daren

36

Arrays.copyOf(T[], int)読みやすいです。内部的にはSystem.arraycopy()ネイティブコールです。

あなたはそれを速く得ることはできません!


あなたはかなり少ないことに頼ることができるようですが、私が知らなかった、確かに読みやすいその機能を指摘してくれてありがとう。:)
Daren

はい!@Svetoslav Tsolovが言ったように、それはかなり多くのものに依存します。Arrays.copyOfを指摘したかった
フィリップ・サンダー

2
copyOfは常にを置き換えることはできませんarraycopyが、この使用例には適しています。
Blaisorblade、2014

1
注意:パフォーマンスを調べている場合、メモリの割り当てが必要なため、System.arraycopy()ほど高速ではありません。これがループ内にある場合、割り当てを繰り返すとガベージコレクションが発生し、パフォーマンスが大幅に低下します。
ウィルカルダーウッド

1
@PhilippSander私が愚かではないことを確認するためだけに、ゲームループの1MB配列のコピーにコードを追加しました。Array.copyOf()を使用すると、DVMが1秒あたり5回GCを呼び出していたため、ゲームが非常に遅延しました。メモリ割り当てが発生していると言っても安全だと思います。
Calderwoodの

17

それは仮想マシンに依存しますが、System.arraycopyは、ネイティブパフォーマンスに到達できる最も近いものを提供します。

私は組み込みシステム(パフォーマンスが最優先事項)のJava開発者として2年間働いており、System.arraycopyを使用できるところならどこでも使用できます。パフォーマンスが問題になる場合は、ループよりも常に推奨されます。パフォーマンスが大きな問題でない場合は、ループを使用します。はるかに読みやすい。


配列のサイズやタイプ(基本vs継承)などは、パフォーマンスに影響を与えるようです。
Daren

2
ええ、それ自体は「ネイティブパフォーマンス」ではないので、私はできる限り「ほとんど」使用していると述べました(ほとんどがループコピーに勝っていることがわかります)。その理由は、プリミティブ型の小さな配列の場合、「呼び出しコスト」がパフォーマンスの向上よりも大きいためだと思います。同じ理由でJNIを使​​用すると、パフォーマンスが低下する可能性があります。ネイティブコード自体は高速ですが、Javaプロセスから呼び出すと、それほどではありません。

マイナーな修正、Arrays.copyはJNIではなく、組み込みです。組み込み関数はJNIよりもはるかに高速です。JITコンパイラーがいつどのように組み込み関数に変換するかは、使用するJVM /コンパイラーによって異なります。
Nitsan Wakart 2013年

1
Arrays.copy存在しないArrays.copyOf、ライブラリ関数です。
Blaisorblade、2014

12

憶測やおそらく時代遅れの情報に依存する代わりに、私はいくつかのベンチマークを実行しました 。実際、Caliperには、CopyArrayBenchmarkこの質問を正確に測定するを含む、いくつかの例が付属しています。あなたがしなければならないすべては実行されます

mvn exec:java -Dexec.mainClass=com.google.caliper.runner.CaliperMain -Dexec.args=examples.CopyArrayBenchmark

私の結果は、2010年半ばのMacBook Pro(Intel Arrandale i7、8 GiB RAMを搭載したmacOS 10.11.6)で実行されているOracleのJava HotSpot(TM)64ビットサーバーVM、1.8.0_31-b13に基づいています。私は生のタイミングデータを投稿するのが便利だとは思いません。むしろ、サポートする視覚化で結論を要約します。

要約すれば:

  • 手動のforループを記述して、各要素を新しくインスタンス化された配列にコピーすることは、短い配列でも長い配列でも、決して有利ではありません。
  • Arrays.copyOf(array, array.length)そしてarray.clone()両方とも一貫して高速です。これら2つの手法のパフォーマンスはほぼ同じです。どちらを選ぶかは好みの問題です。
  • System.arraycopy(src, 0, dest, 0, src.length)およびとほぼ同じくらい高速ですが、一貫性はありません。(50000 秒のケースを参照してください。)そのため、呼び出しの冗長性のため、どの要素をどこにコピーするかを細かく制御する必要があるかどうかをお勧めします。Arrays.copyOf(array, array.length)array.clone()intSystem.arraycopy()

タイミングプロットは次のとおりです。

長さ5の配列をコピーするタイミング 長さ500の配列をコピーするタイミング 長さ50000の配列をコピーするタイミング


3
intコピーについて何か奇妙なことはありますか?大規模なintでは、arraycopyが遅いのは奇妙に思われます。
ホエールバーグ

6

のようなネイティブメソッドを実行Arrays.copyOf(T[], int)するとオーバーヘッドが発生しますが、JNIを使​​用して実行しているため高速ではないという意味ではありません。

最も簡単な方法は、ベンチマークとテストを作成することです。

Arrays.copyOf(T[], int)通常のforループよりも速いことを確認できます。

ここからのベンチマークコード:-

public void test(int copySize, int copyCount, int testRep) {
    System.out.println("Copy size = " + copySize);
    System.out.println("Copy count = " + copyCount);
    System.out.println();
    for (int i = testRep; i > 0; --i) {
        copy(copySize, copyCount);
        loop(copySize, copyCount);
    }
    System.out.println();
}

public void copy(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        System.arraycopy(src, 1, dst, 0, copySize);
        dst[copySize] = src[copySize] + 1;
        System.arraycopy(dst, 0, src, 0, copySize);
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Arraycopy: " + (end - begin) / 1e9 + " s");
}

public void loop(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        for (int i = copySize - 1; i >= 0; --i) {
            dst[i] = src[i + 1];
        }
        dst[copySize] = src[copySize] + 1;
        for (int i = copySize - 1; i >= 0; --i) {
            src[i] = dst[i];
        }
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Man. loop: " + (end - begin) / 1e9 + " s");
}

public int[] newSrc(int arraySize) {
    int[] src = new int[arraySize];
    for (int i = arraySize - 1; i >= 0; --i) {
        src[i] = i;
    }
    return src;
}

System.arraycopy()ここで確認できるように、JNI(Java Native Interface)を使用して配列(またはその一部)をコピーするので、非常に高速 です。


「1」、「2」など、彼らは不変であるため、このコードは、異なる値で初期化(] [[]あなたは文字列でそれを試すことができますint型を使用しています
ダレン

1
JNIは非常に遅いです。System.arraycopy使用しません。
Chai T. Rex

いいえ、System.arraycopyJNIは使用しません。これは、サードパーティのライブラリを呼び出すためだけのものです。代わりに、それはネイティブコールです。つまり、VMにはネイティブ実装があります。
spheenik

6

これはの実装であるため、これArrays.copyOfよりも高速にすることはできません。 System.arraycopycopyOf

public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

4

System.arraycopy()メモリで直接コピー操作を行うネイティブコールです。シングルメモリコピーは、常にforループよりも高速です。


3
非ネイティブ型(私のような作成されたクラス)の場合はそれほど効率的ではない可能性があることを読みました...小さなサイズ(私の場合)の場合、ループのマニュアルはより良いかもしれません...コメントに気を付けますか?
Daren

実際、System.arraycopy()にはある程度のオーバーヘッドがあるため、小さな配列(n =〜10)の場合、ループは実際に高速です
RecursiveExceptionException
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.