这对我来说是一种耻辱,但我不知道:
你应该使用clone来复制数组,因为那通常是 最快的方法。
正如Josh Bloch在本博客中所述:http://www.artima.com/intv/bloch13.html
我总是使用System.arraycopy(...)
。
这两种方法都是原生的,所以可能没有深入到我无法弄清楚的库的来源,为什么会如此。
我的问题很简单:
为什么它是最快的方式?
与
区别在于here,但它没有回答为什么Josh Bloch认为System.arraycopy
有什么区别?clone()
是最快的方式的问题。
答案 0 :(得分:20)
我想谈谈为什么clone()
是复制数组的最快方法,而不是System.arraycopy(..)
或其他人:
1。 clone()
在将源数组复制到目标服务器here之前,不必进行类型检查。它只是简单地分配新的内存空间并将对象分配给它。另一方面,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(..)
3。此外,使用像clone()
这样的手动复制方法会导致更快的输出,因为它不必进行任何VM调用(与System.arraycopy()
不同)。
答案 1 :(得分:4)
首先,clone()
不必进行System.arraycopy()
进行的类型检查。
答案 2 :(得分:3)
我想纠正并补充以前的答案。
<强>解释强>
首先,克隆方法和System.arraycopy是内在函数。 Object.clone和System.arraycopy使用generate_unchecked_arraycopy。 如果我们更深入,我们可以看到在HotSpot之后选择具体实现,依赖于操作系统等
朗力。 我们来看看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;
}
}
在我的电脑上运行此测试,我得到了这个 - https://pastebin.com/bTt5SJ8r。 差异不是很大,但仍然存在。
第二个基准测试我只添加了一个设置 -XX:-ReduceBulkZeroing 并得到了这个结果https://pastebin.com/ny56Ag1z。不,我们看到Young Gen的差异也不大。
在第三个基准测试中,我只更改了设置方法并启用了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/ZDAeQWwx。
声明
这也可能是错的。你应该自己检查一下。
另外
我认为,查看基准测试过程很有意思:
# 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
For 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次热身表演降级后很奇怪。
<强>更新强>
什么是类型检查
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/bTt5SJ8r。 我想解释很简单,因为在这种情况下System.arraycopy是热内在的,真正的实现只是内联而没有任何类型转换等。
注意的
我同意Radiodef您会发现有趣的阅读https://pastebin.com/ufxCZVaC,此博客的作者是blog post的创作者(或创作者之一)。
答案 3 :(得分:1)
就副本而言System.arrayCopy
是最快的,然后现在。
System.arrayCopy
不会创建新数组,也无法以原始复制速度进行打击。Arrays.copyOf
只需创建一个数组并调用arrayCopy
。便利性。Array.clone
效率很高,但需要将复制的数据刷新到所有cpu缓存。如果您可以使用arrayCopy
重用数组的方式进行编码,那就去吧。
否则,我个人建议copyOf
给出cpu核心的上升趋势,因为克隆通常是considered old和problematic - {{3}的主要观点那个问题开始了。
与常见的想法相反,实际的复制循环(类型是否已选中)是不 Java字节码,并且热点无法优化。 循环用C ++编写,是低级jvm实现。
答案很长:
这个答案是基于和链接到OpenJDK 8的源代码,据我所知,对于Sun来说应该是相同的。
数组副本可能比大多数人想象的要复杂得多。在C代码级别,它可以分为三种情况:
复制数组的绝对速度因此在很大程度上取决于数组类型。 但是,三种克隆方法的相对速度并不是因为它们都解析为相同的复制循环,内联的C ++或汇编循环。 因此,速度的不同主要是由间接费用和其他因素造成的。
each element 本质上是类型检查和长度检查,然后直接进行复制循环。
在我自己的测试中,arrayCopy
总是比其他两种方法更快,超出任何误差范围。
System.arrayCopy
只需在创建新数组后调用System.arrayCopy
- 。
请注意,不调用Array.clone。
与Radiodef的Arrays.copyOf
相反,没有迹象表明Java 8将绕过零初始化。
comment 很有意思。
它使用最少的检查直接调用堆分配和复制循环。
因此,它的数组创建速度应该比Arrays.copyOf
快,如果速度不快,它的副本速度应该快System.arrayCopy
。
但在我的测试中,Array.clone
略慢于copyOf
。
我怀疑这是因为复制后的Array.clone
。
与构造函数一样,clone
将确保复制的数据对所有线程都可见 - System.arrayCopy
和Array.copyOf
都不会。
这意味着Array.clone
需要花时间等待CPU缓存更新。
如果这是真的,Array.clone
vs Arrays.copyOf
的结果取决于clone
的缓存刷新是否比copyOf
的开销更快,并且应该是平台相关的
除此之外,由于克隆总是会产生相同类型的数组,因此所有三种方法最终都使用相同的复制循环。
如果您只想复制,arrayCopy
总是最快,只是因为它不会创建新数组。
对于其他人来说,如果java邮件列表是可以使用的,Arrays.copyOf
和Array.clone
之间的选择应该主要是memory barrier。
我的jmh测试结果和下面的代码。
NoClone
不会克隆任何东西,并且是确保更高速度更快的标准。如上所述,Clone和CopyOf是一场紧密的竞赛,您的里程可能会有所不同。
/* # Run complete. Total time: 00:06:44
Benchmark Mode Cnt Score Error Units
MyBenchmark.ArrayCloneByteOneWay thrpt 20 1048588.503 ± 2608.862 ops/s
MyBenchmark.ArrayCloneByteTwoWay thrpt 20 523782.848 ± 1613.823 ops/s
MyBenchmark.ArrayCloneObjOneWay thrpt 20 260903.006 ± 1311.827 ops/s
MyBenchmark.ArrayCloneObjTwoWay thrpt 20 129448.639 ± 1179.122 ops/s
MyBenchmark.ArraysCopyOfByteOneWay thrpt 20 1065995.804 ± 2197.919 ops/s
MyBenchmark.ArraysCopyOfByteTwoWay thrpt 20 533025.610 ± 2831.955 ops/s
MyBenchmark.ArraysCopyOfObjOneWay thrpt 20 266134.565 ± 1536.756 ops/s
MyBenchmark.ArraysCopyOfObjTwoWay thrpt 20 130821.380 ± 274.325 ops/s
MyBenchmark.NoClone thrpt 20 308776528.157 ± 2546848.128 ops/s
MyBenchmark.SystemArrayCopyByteOneWay thrpt 20 1232733.367 ± 8439.409 ops/s
MyBenchmark.SystemArrayCopyByteTwoWay thrpt 20 859387.983 ± 1919.359 ops/s
MyBenchmark.SystemArrayCopyObjOneWay thrpt 20 239532.442 ± 775.193 ops/s
MyBenchmark.SystemArrayCopyObjTwoWay thrpt 20 167235.661 ± 503.141 ops/s
*/
import java.util.Arrays;
import java.util.Random;
import org.openjdk.jmh.annotations.*;
@Fork(2) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 10, time = 1)
public class Q46230557 {
private static final int ARRAY_SIZE = 8192;
@State(Scope.Thread) public static class Data {
public byte[] bytes = new byte[ ARRAY_SIZE ];
public Object[] objs = new Object[ ARRAY_SIZE ];
@Setup public void setup() {
final Random RNG = new Random();
RNG.nextBytes( bytes );
for ( int i = 0 ; i < ARRAY_SIZE ; i++ )
objs[i] = RNG.nextInt();
}
}
@Benchmark public byte[] NoClone( final Data data ) {
return data.bytes;
}
@Benchmark public byte[] SystemArrayCopyByteOneWay( final Data data ) {
final byte[] dest = new byte[ ARRAY_SIZE ];
System.arraycopy( data.bytes, 0, dest, 0, ARRAY_SIZE );
return dest;
}
@Benchmark public byte[] SystemArrayCopyByteTwoWay( final Data data ) {
final byte[] buf = new byte[ ARRAY_SIZE ];
System.arraycopy( data.bytes, 0, buf, 0, ARRAY_SIZE );
System.arraycopy( buf, 0, data.bytes, 0, ARRAY_SIZE );
return data.bytes;
}
@Benchmark public byte[] ArraysCopyOfByteOneWay( final Data data ) {
return Arrays.copyOf( data.bytes, ARRAY_SIZE );
}
@Benchmark public byte[] ArraysCopyOfByteTwoWay( final Data data ) {
final byte[] buf = Arrays.copyOf( data.bytes, ARRAY_SIZE );
return data.bytes = Arrays.copyOf( buf, ARRAY_SIZE );
}
@Benchmark public byte[] ArrayCloneByteOneWay( final Data data ) {
return data.bytes.clone();
}
@Benchmark public byte[] ArrayCloneByteTwoWay( final Data data ) {
final byte[] buf = data.bytes.clone();
return data.bytes = buf.clone();
}
@Benchmark public Object[] SystemArrayCopyObjOneWay( final Data data ) {
final Object[] dest = new Object[ ARRAY_SIZE ];
System.arraycopy( data.objs, 0, dest, 0, ARRAY_SIZE );
return dest;
}
@Benchmark public Object[] SystemArrayCopyObjTwoWay( final Data data ) {
final Object[] buf = new Object[ ARRAY_SIZE ];
System.arraycopy( data.objs, 0, buf, 0, ARRAY_SIZE );
System.arraycopy( buf, 0, data.objs, 0, ARRAY_SIZE );
return data.objs;
}
@Benchmark public Object[] ArraysCopyOfObjOneWay( final Data data ) {
return Arrays.copyOf( data.objs, ARRAY_SIZE );
}
@Benchmark public Object[] ArraysCopyOfObjTwoWay( final Data data ) {
final Object[] buf = Arrays.copyOf( data.objs, ARRAY_SIZE );
return data.objs = Arrays.copyOf( buf, ARRAY_SIZE );
}
@Benchmark public Object[] ArrayCloneObjOneWay( final Data data ) {
return data.objs.clone();
}
@Benchmark public Object[] ArrayCloneObjTwoWay( final Data data ) {
final Object[] buf = data.objs.clone();
return data.objs = buf.clone();
}
}
答案 4 :(得分:0)
性能上的差异来自于跳过数组被清零的步骤。
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);
答案 5 :(得分:0)
不太同意拖延者的答案。我不知道您使用哪个jdk启动了jmh测试,但结果却不一样。
对我来说, System.arraycopy 比 Clone()快。
@BenchmarkMode(Mode.Throughput)
@Fork(1)
@State(Scope.Thread)
@Warmup(iterations = 10, time = 1, batchSize = 1000)
@Measurement(iterations = 10, time = 1, batchSize = 1000)
public class ArrayCopyTest {
@Param({"1000","100","10","5", "1"})
private int size;
private int[] original;
private int[] dest;
@Setup
public void setup() {
original = new int[size];
for (int i = 0; i < size; i++) {
original[i] = i;
}
dest = new int[size];
}
@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[] SystemArrayCopyCache() {
System.arraycopy(original, 0, dest, 0, original.length);
return dest;
}
@Benchmark
public int[] arrayClone() {
return original.clone();
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(ArrayCopyTest.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
然后使用jdk8.1.0_121
获得结果Benchmark (size) Mode Cnt Score Error Units
ops/s
ArrayCopyTest.SystemArrayCopy 1000 thrpt 10 1332,640 ± 79,860 ops/s
ArrayCopyTest.SystemArrayCopy 100 thrpt 10 11850,158 ± 617,639 ops/s
ArrayCopyTest.SystemArrayCopy 10 thrpt 10 50440,946 ± 1409,152 ops/s
ArrayCopyTest.SystemArrayCopy 5 thrpt 10 68791,250 ± 1538,610 ops/s
ArrayCopyTest.SystemArrayCopy 1 thrpt 10 95913,164 ± 671,765 ops/s
ArrayCopyTest.SystemArrayCopyCache 1000 thrpt 10 13514,812 ± 211,703 ops/s
ArrayCopyTest.SystemArrayCopyCache 100 thrpt 10 74976,673 ± 2026,528 ops/s
ArrayCopyTest.SystemArrayCopyCache 10 thrpt 10 108410,738 ± 576,100 ops/s
ArrayCopyTest.SystemArrayCopyCache 5 thrpt 10 118921,286 ± 1354,365 ops/s
ArrayCopyTest.SystemArrayCopyCache 1 thrpt 10 141092,949 ± 2872,961 ops/s
ArrayCopyTest.arrayClone 1000 thrpt 10 1030,526 ± 40,950 ops/s
ArrayCopyTest.arrayClone 100 thrpt 10 5233,746 ± 163,820 ops/s
ArrayCopyTest.arrayClone 10 thrpt 10 8556,687 ± 77,213 ops/s
ArrayCopyTest.arrayClone 5 thrpt 10 8895,238 ± 241,374 ops/s
ArrayCopyTest.arrayClone 1 thrpt 10 9036,695 ± 243,890 ops/s
令人惊讶的是,arraycopy比克隆更好。我建议使用它,它在大循环算法中也非常有用,可以重用对象并避免垃圾收集器带来的压力。使用arraycopy,您可以重复使用阵列,避免浪费时间/取消分配时间