私にとっては残念ですが、私はそれを知りませんでした:
配列をコピーするには、一般に最速の方法であるため、クローンを使用してください。
josh Blochがこのブログで述べているように: http://www.artima.com/intv/bloch13.html
私は常にSystem.arraycopy(...)
を使用しました。どちらのアプローチもネイティブであるため、おそらくライブラリのソースを深く掘り下げることなく、なぜそうなのかを理解できません。
私の質問は簡単です:なぜfastestなのですか? _ 違いは ここ で説明されていますが、なぜJosh BlochがSystem.arraycopy
_との違いは何ですか?clone()
を最速の方法と見なすのかという質問には答えていません。
clone()
がSystem.arraycopy(..)
や他のものよりも配列をコピーする最も速い方法である理由について、いくつかの点を述べたいと思います。
1。clone()
は、提供されたソース配列をデスティネーション配列にコピーする前に型チェックを行う必要はありません こちら 。単純に新しいメモリ空間を割り当て、オブジェクトを割り当てます。一方、System.arraycopy(..)
は型をチェックしてから配列をコピーします。
2。clone()
も最適化を中断して、冗長なゼロ化を排除します。ご存じのように、Javaに割り当てられたすべての配列は、_0s
_またはそれぞれのデフォルト値で初期化する必要があります。ただし、JITは、作成直後に配列がいっぱいになった場合、この配列のゼロ化を回避できます。これにより、既存の_0s
_またはそれぞれのデフォルト値でコピー値を変更するのに比べて、確実に高速になります。 System.arraycopy(..)
を使用すると、初期化された配列の消去とコピーにかなりの時間がかかります。そのために、ベンチマークテストのいくつかを実行しました。
_@BenchmarkMode(Mode.Throughput)
@Fork(1)
@State(Scope.Thread)
@Warmup(iterations = 10, time = 1, batchSize = 1000)
@Measurement(iterations = 10, time = 1, batchSize = 1000)
public class BenchmarkTests {
@Param({"1000","100","10","5", "1"})
private int size;
private int[] original;
@Setup
public void setup() {
original = new int[size];
for (int i = 0; i < size; i++) {
original[i] = i;
}
}
@Benchmark
public int[] SystemArrayCopy() {
final int length = size;
int[] destination = new int[length];
System.arraycopy(original, 0, destination, 0, length);
return destination;
}
@Benchmark
public int[] arrayClone() {
return original.clone();
}
}
_
出力:
_Benchmark (size) Mode Cnt Score Error Units
ArrayCopy.SystemArrayCopy 1 thrpt 10 26324.251 ± 1532.265 ops/s
ArrayCopy.SystemArrayCopy 5 thrpt 10 26435.562 ± 2537.114 ops/s
ArrayCopy.SystemArrayCopy 10 thrpt 10 27262.200 ± 2145.334 ops/s
ArrayCopy.SystemArrayCopy 100 thrpt 10 10524.117 ± 474.325 ops/s
ArrayCopy.SystemArrayCopy 1000 thrpt 10 984.213 ± 121.934 ops/s
ArrayCopy.arrayClone 1 thrpt 10 55832.672 ± 4521.112 ops/s
ArrayCopy.arrayClone 5 thrpt 10 48174.496 ± 2728.928 ops/s
ArrayCopy.arrayClone 10 thrpt 10 46267.482 ± 4641.747 ops/s
ArrayCopy.arrayClone 100 thrpt 10 19837.480 ± 364.156 ops/s
ArrayCopy.arrayClone 1000 thrpt 10 1841.145 ± 110.322 ops/s
_
出力によると、clone
はSystem.arraycopy(..)
からほぼ2倍高速であることがわかります。
3。また、clone()
のような手動コピー方法を使用すると、VM呼び出し(System.arraycopy()
とは異なります)。
一つには、clone()
はSystem.arraycopy()
が行うタイプチェックを行う必要がない。
以前の回答を修正して補完したいと思います。
説明
まず、クローンメソッドとSystem.arraycopyは組み込み関数です。 Object.cloneおよびSystem.arraycopyはgenerate_unchecked_arraycopyを使用します。さらに深く掘り下げると、HotSpotの後、OSなどに依存する具体的な実装が選択されることがわかります。
長く。 Hotspot のコードを見てみましょう。まず、Object.clone(LibraryCallKit :: inline_native_clone)がgenerate_arraycopyを使用し、-XX:-ReduceInitialCardMarksの場合にSystem.arraycopyに使用されることを確認します。それ以外の場合、LibraryCallKit :: copy_to_cloneを実行し、RAWメモリ内の新しい配列を初期化します(デフォルトで有効になっている-XX:+ ReduceBulkZeroingの場合)。対照的に、System.arraycopyはgenerate_arraycopyを直接使用し、ReduceBulkZeroing(および他の多くの場合)をチェックし、前述の追加チェックで配列のゼロ化を排除し、Object.cloneとは異なり、すべての要素が初期化されていることを確認する追加チェックを行います。最後に、最良の場合、両方ともgenerate_unchecked_arraycopyを使用します。
以下に、この効果が実際にどのように作用するかを示すベンチマークをいくつか示します。
最初のベンチマーク:
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import Java.util.concurrent.ThreadLocalRandom;
import Java.util.concurrent.TimeUnit;
@State(Scope.Benchmark)
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class CloneVsArraycopy {
@Param({"10", "1000", "100000"})
int size;
int[] source;
@Setup(Level.Invocation)
public void setup() {
source = create(size);
}
@Benchmark
public int[] clone(CloneVsArraycopy cloneVsArraycopy) {
return cloneVsArraycopy.source.clone();
}
@Benchmark
public int[] arraycopy(CloneVsArraycopy cloneVsArraycopy) {
int[] dest = new int[cloneVsArraycopy.size];
System.arraycopy(cloneVsArraycopy.source, 0, dest, 0, dest.length);
return dest;
}
public static void main(String[] args) throws Exception {
new Runner(new OptionsBuilder()
.include(CloneVsArraycopy.class.getSimpleName())
.warmupIterations(20)
.measurementIterations(20)
.forks(20)
.build()).run();
}
private static int[] create(int size) {
int[] a = new int[size];
for (int i = 0; i < a.length; i++) {
a[i] = ThreadLocalRandom.current().nextInt();
}
return a;
}
}
PCでこのテストを実行すると、これが得られました- https://Pastebin.com/ny56Ag1z 。違いはそれほど大きくありませんが、まだ存在しています。
2番目のベンチマークでは、1つの設定-XX:-ReduceBulkZeroingのみを追加し、この結果を得ました https://Pastebin.com/ZDAeQWwx =。いいえ、Young Genの場合、その差もはるかに小さいことがわかります。
3番目のベンチマークでは、セットアップ方法のみを変更し、ReduceBulkZeroingオプションを有効に戻しました。
@Setup(Level.Invocation)
public void setup() {
source = create(size);
// try to move to old gen/align array
for (int i = 0; i < 10; ++i) {
System.gc();
}
}
違いははるかに小さいです(エラー間隔にある可能性があります)- https://Pastebin.com/bTt5SJ8r 。
免責事項
それも間違っている可能性があります。自分で確認する必要があります。
さらに
ベンチマークプロセスを見るのは興味深いと思います。
# Benchmark: org.egorlitvinenko.arrays.CloneVsArraycopy.arraycopy
# Parameters: (size = 50000)
# Run progress: 0,00% complete, ETA 00:07:30
# Fork: 1 of 5
# Warmup Iteration 1: 8,870 ops/ms
# Warmup Iteration 2: 10,912 ops/ms
# Warmup Iteration 3: 16,417 ops/ms <- Hooray!
# Warmup Iteration 4: 17,924 ops/ms <- Hooray!
# Warmup Iteration 5: 17,321 ops/ms <- Hooray!
# Warmup Iteration 6: 16,628 ops/ms <- What!
# Warmup Iteration 7: 14,286 ops/ms <- No, stop, why!
# Warmup Iteration 8: 13,928 ops/ms <- Are you kidding me?
# Warmup Iteration 9: 13,337 ops/ms <- pff
# Warmup Iteration 10: 13,499 ops/ms
Iteration 1: 13,873 ops/ms
Iteration 2: 16,177 ops/ms
Iteration 3: 14,265 ops/ms
Iteration 4: 13,338 ops/ms
Iteration 5: 15,496 ops/ms
Object.cloneの場合
# Benchmark: org.egorlitvinenko.arrays.CloneVsArraycopy.clone
# Parameters: (size = 50000)
# Run progress: 0,00% complete, ETA 00:03:45
# Fork: 1 of 5
# Warmup Iteration 1: 8,761 ops/ms
# Warmup Iteration 2: 12,673 ops/ms
# Warmup Iteration 3: 20,008 ops/ms
# Warmup Iteration 4: 20,340 ops/ms
# Warmup Iteration 5: 20,112 ops/ms
# Warmup Iteration 6: 20,061 ops/ms
# Warmup Iteration 7: 19,492 ops/ms
# Warmup Iteration 8: 18,862 ops/ms
# Warmup Iteration 9: 19,562 ops/ms
# Warmup Iteration 10: 18,786 ops/ms
ここでSystem.arraycopyのパフォーマンスのダウングレードを確認できます。 Streamsについても同様の図を見ましたが、コンパイラにバグがありました。コンパイラのバグかもしれません。とにかく、3回のウォームアップ後、パフォーマンスが低下するのは奇妙です。
[〜#〜] update [〜#〜]
型チェックについて
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import Java.util.concurrent.ThreadLocalRandom;
import Java.util.concurrent.TimeUnit;
import Java.util.concurrent.atomic.AtomicLong;
@State(Scope.Benchmark)
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class CloneVsArraycopyObject {
@Param({"100"})
int size;
AtomicLong[] source;
@Setup(Level.Invocation)
public void setup() {
source = create(size);
}
@Benchmark
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
public AtomicLong[] clone(CloneVsArraycopyObject cloneVsArraycopy) {
return cloneVsArraycopy.source.clone();
}
@Benchmark
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
public AtomicLong[] arraycopy(CloneVsArraycopyObject cloneVsArraycopy) {
AtomicLong[] dest = new AtomicLong[cloneVsArraycopy.size];
System.arraycopy(cloneVsArraycopy.source, 0, dest, 0, dest.length);
return dest;
}
public static void main(String[] args) throws Exception {
new Runner(new OptionsBuilder()
.include(CloneVsArraycopyObject.class.getSimpleName())
.jvmArgs("-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintInlining", "-XX:-ReduceBulkZeroing")
.warmupIterations(10)
.measurementIterations(5)
.forks(5)
.build())
.run();
}
private static AtomicLong[] create(int size) {
AtomicLong[] a = new AtomicLong[size];
for (int i = 0; i < a.length; i++) {
a[i] = new AtomicLong(ThreadLocalRandom.current().nextLong());
}
return a;
}
}
違いは観察されません- https://Pastebin.com/ufxCZVaC 。 System.arraycopyはその場合ホット組み込みなので、説明は簡単だと思います。実際の実装は、型チェックなどをすることなくインライン化されます。
注
私はあなたが読むのが面白いと思うRadiodefに同意しました ブログ投稿 、このブログの著者は [〜#〜] jmh [〜#〜]の作成者(または作成者の一人)です 。
パフォーマンスの違いは、配列がゼロになるステップをスキップすることから生じます。
public static int[] copyUsingArraycopy(int[] original)
{
// Memory is allocated and zeroed out
int[] copy = new int[original.Length];
// Memory is copied
System.arraycopy(original, 0, copy, 0, original.length);
}
public static int[] copyUsingClone(int[] original)
{
// Memory is allocated, but not zeroed out
// Unitialized memory is then copied into
return (int[])original.clone();
}
ただし、配列のコピーのパフォーマンスが大きく異なる場合は、一般にダブルバッファリングを使用することをお勧めします。
int[] backBuffer = new int[BUFFER_SIZE];
int[] frontBuffer = new int[BUFFER_SIZE];
...
// Swap buffers
int[] temp = frontBuffer;
frontBuffer = backBuffer;
backBuffer = temp;
System.arraycopy(frontBuffer, 0, backBuffer, 0, BUFFER_SIZE);