System.arraycopyがネイティブメソッドであることをJavaソースで見て驚いた。
もちろん、その理由はそれが速いからです。しかし、コードがそれをより速くするために採用できるネイティブのトリックは何ですか?
元の配列をループして、各ポインターを新しい配列にコピーしないのはなぜですか?確かに、これはそれほど遅くて面倒ではありませんか?
回答:
ネイティブコードでは、n個の個別のコピー操作ではなく、単一のmemcpy
/memmove
で実行できます。パフォーマンスの違いはかなりのものです。
arraycopy
を使用して実装できるのはの一部のサブケースのみです。その他の場合、コピーされた要素ごとに実行時型チェックが必要です。memcpy
memmove
Object[]
入力された場所からにコピーすることを検討String
してくださいString[]
。java.sun.com/javase/6/docs/api/java/lang/の
Javaで書くことはできません。ネイティブコードは、オブジェクトの配列とプリミティブの配列の違いを無視または排除することができます。Javaは、少なくとも効率的にはそれを行うことができません。
また、配列のオーバーラップに必要なセマンティクスのため、単一ので記述することはできませんmemcpy()
。
memmove
。私はそれがこの質問の文脈で大きな違いを生むとは思わないが。
もちろん、実装に依存します。
HotSpotはそれを「固有の」ものとして扱い、呼び出しサイトにコードを挿入します。それはマシンコードであり、遅い古いCコードではありません。これは、メソッドのシグネチャに関する問題が大幅に解消されることも意味します。
単純なコピーループは、明らかな最適化を適用できるほど単純です。たとえば、ループ展開。正確に何が起こるかは、やはり実装に依存します。
私自身のテストでは、複数の次元の配列をコピーするためのSystem.arraycopy()は、forループのインターリーブよりも10〜20倍高速です。
float[][] foo = mLoadMillionsOfPoints(); // result is a float[1200000][9]
float[][] fooCpy = new float[foo.length][foo[0].length];
long lTime = System.currentTimeMillis();
System.arraycopy(foo, 0, fooCpy, 0, foo.length);
System.out.println("native duration: " + (System.currentTimeMillis() - lTime) + " ms");
lTime = System.currentTimeMillis();
for (int i = 0; i < foo.length; i++)
{
for (int j = 0; j < foo[0].length; j++)
{
fooCpy[i][j] = foo[i][j];
}
}
System.out.println("System.arraycopy() duration: " + (System.currentTimeMillis() - lTime) + " ms");
for (int i = 0; i < foo.length; i++)
{
for (int j = 0; j < foo[0].length; j++)
{
if (fooCpy[i][j] != foo[i][j])
{
System.err.println("ERROR at " + i + ", " + j);
}
}
}
これは印刷します:
System.arraycopy() duration: 1 ms
loop duration: 16 ms
System.arraycopy
浅いコピーを実行します(内部のsへの参照のみfloat[]
がコピーされます)が、ネストされたfor
-loopsは(float
によってfloat
)深いコピーを実行します。への変更fooCpy[i][j]
は、のfoo
使用System.arraycopy
に反映されますが、ネストされたfor
ループは使用されません。
いくつかの理由があります:
JITは、手動で記述されたCコードほど効率的な低レベルコードを生成する可能性は低いです。低レベルのCを使用すると、一般的なJITコンパイラーでは不可能に近い多くの最適化が可能になります。
手書きのC実装のいくつかのトリックと速度の比較については、このリンクを参照してください(memcpyですが、原則は同じです):この最適化Memcpyが速度を向上させることを確認してください
Cバージョンは、配列メンバーのタイプとサイズにほとんど依存しません。配列の内容をメモリの生のブロック(ポインタなど)として取得する方法がないため、Javaで同じことを行うことはできません。