Java 为什么对于小型阵列,Arrays.copyOf的速度是System.arraycopy的2倍?

Java 为什么对于小型阵列,Arrays.copyOf的速度是System.arraycopy的2倍?,java,arrays,performance,microbenchmark,Java,Arrays,Performance,Microbenchmark,我最近在玩一些基准测试,发现了非常有趣的结果,我现在无法解释。以下是基准: @BenchmarkMode(Mode.Throughput) @Fork(1) @State(Scope.Thread) @Warmup(iterations = 10, time = 1, batchSize = 1000) @Measurement(iterations = 10, time = 1, batchSize = 1000) public class ArrayCopy { @Param({"

我最近在玩一些基准测试,发现了非常有趣的结果,我现在无法解释。以下是基准:

@BenchmarkMode(Mode.Throughput)
@Fork(1)
@State(Scope.Thread)
@Warmup(iterations = 10, time = 1, batchSize = 1000)
@Measurement(iterations = 10, time = 1, batchSize = 1000)
public class ArrayCopy {

    @Param({"1","5","10","100", "1000"})
    private int size;
    private int[] ar;

    @Setup
    public void setup() {
        ar = new int[size];
        for (int i = 0; i < size; i++) {
            ar[i] = i;
        }
    }

    @Benchmark
    public int[] SystemArrayCopy() {
        final int length = size;
        int[] result = new int[length];
        System.arraycopy(ar, 0, result, 0, length);
        return result;
    }

    @Benchmark
    public int[] javaArrayCopy() {
        final int length = size;
        int[] result = new int[length];
        for (int i = 0; i < length; i++) {
            result[i] = ar[i];
        }
        return result;
    }

    @Benchmark
    public int[] arraysCopyOf() {
        final int length = size;
        return Arrays.copyOf(ar, length);
    }

}
这里有两件奇怪的事:

  • Arrays.copyOf
    System.arraycopy
    快2倍 阵列(1,5,10大小)。但是,在大小为1000的大型阵列上
    Arrays.copyOf
    的速度几乎慢了两倍。我都知道 方法是内在的,所以我期望有相同的性能。哪里 这种差异来自哪里
  • 单元素数组的手动复制比System.arraycopy快。我不清楚为什么。有人知道吗

VM版本:JDK 1.8.0131,VM 25.131-b11

您的
SystemArrayCopy
基准在语义上与
arraysCopyOf
不等价

如果你换个新的,它会是

    System.arraycopy(ar, 0, result, 0, length);

随着这一变化,两个基准的性能也将变得相似

为什么第一种变体速度较慢

  • 不知道
    length
    ar.length
    JVM需要执行额外的边界检查,并准备在
    length>ar.length
    时抛出
    IndexOutOfBoundsException
  • 这也破坏了优化以消除冗余调零。您知道,每个分配的数组都必须初始化为零。但是,如果JIT看到数组在创建后立即被填充,则可以避免归零。但是
    -prof perfasm
    清楚地表明原始
    SystemArrayCopy
    基准测试花费了大量时间来清除分配的数组:

     0,84%    0x000000000365d35f: shr    $0x3,%rcx
     0,06%    0x000000000365d363: add    $0xfffffffffffffffe,%rcx
     0,69%    0x000000000365d367: xor    %rax,%rax
              0x000000000365d36a: shl    $0x3,%rcx
    21,02%    0x000000000365d36e: rep rex.W stos %al,%es:(%rdi)  ;*newarray
    

  • 对于小型阵列,手动复制速度更快,因为与System.arraycopy不同,它不执行对VM函数的任何运行时调用。

    因为
    copyOf
    在内部使用
    arraycopy
    ,所以您的基准测试就是问题所在。@Andreas您的错误<代码>数组。copyOf是JVM固有的。一旦方法被JIT编译,数组中的Java代码就不会被执行。@Andreas您认为基准测试有什么特别的问题吗?它明智地避免了常见的基准测试陷阱。@apangin这是一个没有区别的区别。这同样适用于任何Java代码。这不会使任何任意方法成为“JVM内在方法”。@EJP好的,我会改写。JIT编译器不会查看
    Arrays.copyOf
    的字节码,因为它对这种方法应该做什么有内部知识。ar.length和
    length
    不一样吗?
    Math.min(ar.length,length)
    有什么影响?@Boann它们是一样的,但JVM不知道这一点
    Math.min
    让编译器知道
    System.arraycopy
    永远不会抛出
    IndexOutOfBoundsException
    。请解释。编译器从地上的一个洞里不知道
    System.arraycopy()
    ,除了它的名称、调用序列、结果和
    抛出的
    子句(实际上它没有)
    System.arraycopy()
    在其第五个参数中只接收到
    Math.min()
    的结果,并且不知道它是如何派生的。@EJP我不确定您指的是什么编译器,但HotSpot JIT编译器(实际上,它们都是-C1和C2)肯定是关于
    System.arraycopy()
    和对IR节点图的调用。然后有助于减少这个图表。@EJP谁使这个术语消失了?“JIT’仍然在和由Oracle工程师正式使用。
        System.arraycopy(ar, 0, result, 0, Math.min(ar.length, length));
    
     0,84%    0x000000000365d35f: shr    $0x3,%rcx
     0,06%    0x000000000365d363: add    $0xfffffffffffffffe,%rcx
     0,69%    0x000000000365d367: xor    %rax,%rax
              0x000000000365d36a: shl    $0x3,%rcx
    21,02%    0x000000000365d36e: rep rex.W stos %al,%es:(%rdi)  ;*newarray