【问题标题】: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 会来这里解释一下:-)
  • clone 和 Arrays.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 old 和problematic - 开始这个问题的Josh Bloch blog 的要点。 p>

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


长答案:

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

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

  1. 原始数组是 copied directly 和 copy 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.clone 比copyOf 稍慢。

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

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

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

如果你只想复制,arrayCopy 总是最快的,因为它不会创建新数组。 其余的,如果 java 邮件列表有什么用,Arrays.copyOf 和Array.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.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,您可以重用您的数组并避免 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
    相关资源
    最近更新 更多