【问题标题】:Why clone() is the best way for copying arrays?为什么 clone() 是复制数组的最佳方式?
【发布时间】:2018-02-19 02:42:00
【问题描述】:

这对我来说是一种耻辱,但我不知道:

您应该使用 clone 来复制数组,因为这通常是 最快的方法。

正如 Josh Bloch 在此博客中所述:http://www.artima.com/intv/bloch13.html

我一直使用System.arraycopy(...)。 这两种方法都是本机的,所以可能没有深入了解库的来源,我无法弄清楚为什么会这样。

我的问题很简单: 为什么它是最快的方式? System.arraycopy有什么区别? here 解释了差异,但它没有回答为什么 Josh Bloch 认为 clone() 是最快的方式的问题。

【问题讨论】:

  • 自那篇文章发表以来已经过去了 15 年。我可以看到 clone() 方法如何绕过 calloc() 调用和数组边界检查,但我会编写一个 jmh 测试并亲自查看
  • 可能@JoshuaBloch 会来这里解释一下:-)
  • cloneArrays.copyOf 都将比 System.arraycopy 快,如果您正在创建和填充一个新数组,因为前两种方法可以避免隐式使用new 创建数组时进行零初始化。尽管没有专门针对这个特定问题,this blog post 有很多相关信息。我很确定我们在这里有关于 SO 的问答,其中涵盖了这一点,但我很难找到一个。
  • 好吧,在他这样做之前,这里是克隆 (JVM_Clone) 的 jdk7 源代码 - hg.openjdk.java.net/jdk7/jdk7/hotspot/file/tip/src/share/vm/…。相同的源文件还包含 JVM_ArrayCopy。也许有更多知识的人可以找出原因
  • 我找到了一些关于这个问题的答案 - stackoverflow.com/questions/2589741/…, stackoverflow.com/questions/7179251/…

标签: java arrays copy clone


【解决方案1】:

我想说明为什么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() 不同)。

【讨论】:

  • 任何证明链接都会有所帮助。没有它们,这些陈述听起来就像猜想。此外,任何能证明这一点的微基准测试也将是一个证明。
  • ArrayCopy.arrayClone=26324 ops/s, ArrayCopy.SystemArrayCopy=55832 ops/s - 这些数据如何得出“克隆System.arraycopy()"的速度几乎快两倍?
  • arrayCopy 不会清除任何内存。实际上,arrayCopy 可用于将同一数组中的数据向前或向后移动。我同意这是您的测试实际所做的,但它是 new int[] 将数组归零,而不是 arrayCopy。
  • 在声明某些性能结果时,这是一个可能犯的错误。您确定基准测试中没有其他错误吗?您是否尝试过使用original.length 而不是访问size 字段的System.arraycopy 的变体?为什么使用batchSize 配置参数?您对从基准测试中获得的数据解释吗?
  • 您确定在运行时需要使用arrayCopy 进行类型检查吗?我很确定编译器可以解析在编译时调用哪个重载变量。此外,clone() 需要虚拟调度,这很便宜但不是免费的。关于将开销归零的观点非常好,并解释了结果中的所有变化。但是arrayCopy() 可以做clone() 做不到的事情。例如。如果您在同一内存中反复移动数据(例如对原始二进制数据进行排序),那么clone() 将无济于事。
【解决方案2】:

一方面,clone() 不必像 System.arraycopy() 那样进行类型检查。

【讨论】:

  • 如果我们只禁用 ReduceBulkZeroing 选项,Object.clone 和 System.arraycopy 之间没有区别。两种方法检查是否调用原始数组并以类似方式进行复制。看我的回答。如果您有另一个证明您的假设的基准,请分享。
  • @egorlitvinenko 这里没有“假设”,只是System.arraycopy() 的合同有义务进行类型检查的可验证事实:例如,如果您尝试将Object[] 复制到@ 987654327@。关于ReduceBulkZeroing 的问题或我的回答中都没有任何内容,无论这可能与它有关。
  • 如何验证这个可验证的事实?从我的角度来看,我可以说只有当我们使用 Object[] 参数创建包装器方法时才会这样做,该参数将传递给 System.arraycopy 并且我们将使用不同的源数组调用此方法。否则它将被内联而不进行类型检查。我尝试创建一个使用 AtomicLong 而不是 long 的基准,禁用 ReduceBulkZeroing 并查看此结果pastebin.com/ufxCZVaC。我没有看到“类型检查”的任何影响。我确定(看到内联 -XX:+PrintInlining),这只是因为内联,在运行时没有类型检查。
  • @egorlitvinenko 您可以从阅读Javadoc 开始,特别是关于何时抛出ArrayStoreException 的部分。
  • 叹息。 Object[] obj = {new Object()}; String[] str = new String[obj.len]; System.arraycopy(obj, 0, str, 0, str.len); 这将抛出ArrayStoreException,正如它在Javadoc 中所说的那样,您显然还没有阅读过,除非它有一些类型检查,否则它无法做到这一点,这就是它在我的回答中所说的。至少它必须检查源数组和目标数组的类型是否相同,如果不是,它必须对复制的每个元素进行类型检查。
【解决方案3】:

我想更正和补充以前的答案。

  1. Object.clone 对数组使用未经检查的 System.arraycopy 实现;
  2. Object.clone 的主要性能改进,它是直接初始化 RAW 内存。在 System.arraycopy 的情况下,它还尝试将数组初始化与复制操作结合起来,正如我们在源代码中看到的那样,但它也对此进行了不同的额外检查,这与 Object.clone 不同。如果您只是禁用此功能(见下文),那么性能会非常接近(尤其是在我的硬件上)。
  3. 一个更有趣的事情是关于 Young 与 Old Gen。如果源数组对齐并在 Old Gen 内,这两种方法的性能都很接近。
  4. 当我们复制原始数组时 System.arraycopy 总是使用 generate_unchecked_arraycopy。
  5. 这取决于硬件/操作系统相关的实现,所以不要相信基准和假设,请自行检查。

说明

首先 clone 方法和 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。

下面我展示了一些基准来查看这种对实践的影响:

  1. 第一个只是简单的基准测试,与上一个答案的唯一区别是数组未排序;我们看到 arraycopy 速度较慢(但不是两倍),结果 - https://pastebin.com/ny56Ag1z;
  2. 其次,我添加选项 -XX:-ReduceBulkZeroing 现在我发现这两种方法的性能非常接近。结果 - https://pastebin.com/ZDAeQWwx;
  3. 我还假设我们会有Old/Young之间的区别,因为数组对齐(这是Java GC的一个特性,当我们调用GC时,数组的对齐方式发生了变化,使用JOL很容易观察到)。令我惊讶的是,这两种方法的性能通常相同,并且降级。结果 - https://pastebin.com/bTt5SJ8r。对于相信具体数字的人来说,System.arraycopy 的吞吐量比 Object.clone 更好。

第一个基准测试:

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/ny56Ag1z。 差别不是很大,但还是存在的。

第二个基准我只添加了一个设置-XX:-ReduceBulkZeroing并得到了这个结果https://pastebin.com/ZDAeQWwx。不,我们看到对于年轻一代来说,差异也小得多。

在第三个基准测试中,我只更改了设置方法并启用了 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次热身后性能下降很奇怪。

更新

什么是类型检查

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 的观点,您会发现阅读 blog post 会很有趣,此博客的作者是 JMH 的创建者(或创建者之一)。

【讨论】:

  • 您假设ibraryCallKit::generate_arraycopy() 中提到的arraycopy() 方法是System.arraycopy(),没有证据,也没有考虑cmets 中表达的任何警告,这几乎可以肯定地表明它们不是一回事。
  • 亲爱的 Egor,我不是反对你的问题,我相信你对这个问题做了很好的研究。但它看起来并不令人信服,尤其是这些长长的源代码粘贴。我宁愿专注于重要部分,但这些看起来不像是研究的结果,而是关于你尝试过的故事。这不是一个答案。
  • 是的,我假设,因为有 cmets 和真正的实现,这取决于在该方法内的较低级别定义的 OS/etc。我不仅在源代码上看到,我还调查了热点分支,并通过基准测试确认了这项调查。
  • @EJP 您可以轻松反驳这一点,只需尝试找到另一个用例。如果您尝试查看 Object.clone 实现,您会发现它使用 generate_unchecked_arraycopy - 在某些情况下与 System.arraycopy 完全相同。
  • @Andremoniy 感谢您的反馈。也许我没有提供可能的解释,以及我是如何实现这一目标的。
【解决方案4】:

就复制而言,System.arrayCopy 在过去和现在都是最快的。

  • System.arrayCopy 不会创建新数组,并且在原始复制速度上无法击败。
  • Arrays.copyOf 只是创建一个数组并调用arrayCopy。方便。
  • Array.clone 效率很高,但需要将复制的数据刷新到所有 cpu 缓存中。

如果您可以使用arrayCopy 以重用数组的方式进行编码,那就去吧。 否则,我个人个人推荐copyOf,因为cpu 内核呈上升趋势,并且因为克隆通常是considered oldproblematic - 开始这个问题的Josh Bloch blog 的要点。 p>

与通常的想法相反,实际的复制循环(类型检查与否)是不是 Java 字节码,不能通过热点进行优化。 这些循环是用 C++ 编码的,是低级 jvm 实现。


长答案:

这个答案基于并链接到 OpenJDK 8 的源代码,据我所知,对于 Sun 来说应该是相同的。

数组复制可能比大多数人想象的要复杂。在C代码层面,可以分为三种情况:

  1. 原始数组是 copied directlycopy loop
  2. 同一类的对象数组,或超类数组的子类,也是copied directly
  3. 否则,在不同类的数组之间,将对each element 进行类型检查。

因此,复制数组的绝对速度会因数组类型而有很大差异。 然而,这三种克隆方法的相对速度并不相同,因为它们都解析为相同的复制循环、内联 C++ 或汇编循环。 因此,速度的不同主要是由开销和其他因素造成的。

  • System.arrayCopy 本质上是类型检查和长度检查,然后直接进入复制循环。 在我自己的测试中,arrayCopy 总是比其他两种方法快,远远超出任何误差范围。

  • Arrays.copyOf 只需调用System.arrayCopy - 创建一个新数组之后。 请注意,它调用 Array.clone。 与 Radiodef 的 comment 相反,没有迹象表明 Java 8 会绕过零初始化。

  • Array.clone 很有趣。 它直接调用堆分配和复制循环,只需最少的检查。 因此,它的数组创建速度应该比Arrays.copyOf 快,并且它的复制速度应该与System.arrayCopy 一样快。

但在我的测试中Array.clonecopyOf 稍慢。

我怀疑是因为复制后的memory barrier。 像构造函数一样,clone 将确保复制的数据对所有线程都是可见的——System.arrayCopyArray.copyOf 都没有。 这意味着Array.clone 需要花时间等待 CPU 缓存更新。

如果这是真的,Array.clone vs Arrays.copyOf 的结果取决于clone 的缓存刷新是否比copyOf 的开销快,并且应该取决于平台。

除此之外,由于克隆总是产生相同类型的数组,因此所有三种方法最终都使用相同的复制循环。

如果你只想复制,arrayCopy 总是最快的,因为它不会创建新数组。 其余的,如果 java 邮件列表有什么用,Arrays.copyOfArray.clone 之间的选择应该主要是matter of taste


我的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();
   }
}

【讨论】:

  • 对不起:您可能应该阅读有关如何正确编写微基准测试的信息。你的奶头不合适。除此之外,您没有提及 why 本质上 'clone()' 会最慢。至少您甚至没有阅读我答案中的链接。
  • @Andremoniy 抱歉让您久等了。我的宝宝睡着了,我已经完成了答案。我还没有时间设置 JMH,我只能希望我的测试代码能够避免常见的陷阱,比如上面链接中链接到的陷阱等等。不过,我的重点是评估 JDK 源代码,所以我大部分时间都花在这上面。希望你对那部分感到满意​​。
  • 只有在您编写了适当的微基准测试后,我才会支持您的答案
  • @Andremoniy 抱歉花了一段时间,但我已经用 jmh 替换了测试并添加了我的测试结果。结论保持不变。
【解决方案5】:

性能上的差异来自于跳过将数组归零的步骤。

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);

【讨论】:

    【解决方案6】:

    不太同意拖延者的回答。我不知道您使用哪个 jdk 启动了 jmh 测试,但我没有相同的结果。

    对我来说,System.arraycopyClone() 快。

    @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,您可以重用您的数组并避免 waallocation/deallocation 时间

    【讨论】:

    • 您应该再试一次,但使用未排序的数组
    猜你喜欢
    • 2012-09-02
    • 2012-05-15
    • 1970-01-01
    • 2012-07-13
    • 2013-01-19
    • 1970-01-01
    • 1970-01-01
    • 2010-09-05
    • 2017-06-09
    相关资源
    最近更新 更多