java - System.arrayCopy 很慢

标签 java performance arrays

我一直在尝试衡量 System.arrayCopy 与 Arrays.copyOf 的性能,以便正确选择其中之一。为了基准测试,我也添加了手动副本,结果让我感到惊讶。 显然我遗漏了一些非常重要的东西,你能告诉我它是什么吗?实现如下(见前4种方法)。

public class ArrayCopy {

    public static int[] createArray( int size ) {
        int[] array = new int[size];
        Random r = new Random();
        for ( int i = 0; i < size; i++ ) {
            array[i] = r.nextInt();
        }
        return array;
    }

    public static int[] copyByArraysCopyOf( int[] array, int size ) {
        return Arrays.copyOf( array, array.length + size );
    }

    public static int[] copyByEnlarge( int[] array, int size ) {
        return enlarge( array, size );
    }

    public static int[] copyManually( int[] array, int size ) {
        int[] newArray = new int[array.length + size];
        for ( int i = 0; i < array.length; i++ ) {
            newArray[i] = array[i];
        }
        return newArray;
    }

    private static void copyArray( int[] source, int[] target ) {
        System.arraycopy( source, 0, target, 0, Math.min( source.length, target.length ) );
    }

    private static int[] enlarge( int[] orig, int size ) {
        int[] newArray = new int[orig.length + size];
        copyArray( orig, newArray );
        return newArray;
    }

    public static void main( String... args ) {
        int[] array = createArray( 1000000 );
        int runs = 1000;
        int size = 1000000;
        System.out.println( "****************** warm up #1 ******************" );
        warmup( ArrayCopy::copyByArraysCopyOf, array, size, runs );
        warmup( ArrayCopy::copyByEnlarge, array, size, runs );
        warmup( ArrayCopy::copyManually, array, size, runs );
        System.out.println( "****************** warm up #2 ******************" );
        warmup( ArrayCopy::copyByArraysCopyOf, array, size, runs );
        warmup( ArrayCopy::copyByEnlarge, array, size, runs );
        warmup( ArrayCopy::copyManually, array, size, runs );
        System.out.println( "********************* test *********************" );
        System.out.print( "copyByArrayCopyOf" );
        runTest( ArrayCopy::copyByArraysCopyOf, array, size, runs );
        System.out.print( "copyByEnlarge" );
        runTest( ArrayCopy::copyByEnlarge, array, size, runs );
        System.out.print( "copyManually" );
        runTest( ArrayCopy::copyManually, array, size, runs );
    }

    private static void warmup( BiConsumer<int[], Integer> consumer, int[] array, int size, int runs ) {
        for ( int i = 0; i < runs; i++ ) {
            consumer.accept( array, size );
        }
    }

    private static void runTest( BiConsumer<int[], Integer> consumer, int[] array, int size, int runs ) {
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        long currentCpuTime = threadMXBean.getCurrentThreadCpuTime();
        long nanoTime = System.nanoTime();
        for ( int i = 0; i < runs; i++ ) {
            consumer.accept( array, size );
        }
        System.out.println( "-time = " + ( ( System.nanoTime() - nanoTime ) / 10E6 ) + " ms. CPU time = " + ( ( threadMXBean.getCurrentThreadCpuTime() - currentCpuTime ) / 10E6 ) + " ms" );
    }
}

结果表明,手动复制的性能提高了 30% 左右,如下所示:

****************** warm up #1 ******************
****************** warm up #2 ******************
********************* test *********************
copyByArrayCopyOf-time = 162.470107 ms. CPU time = 153.125 ms
copyByEnlarge-time = 168.6757949 ms. CPU time = 164.0625 ms
copyManually-time = 116.3975962 ms. CPU time = 110.9375 ms

我真的很困惑,因为我认为(并且可能我仍然认为)System.arrayCopy 由于它的诞生是复制数组的最佳方式,但我无法解释这个结果.

最佳答案

实际上,HotSpot 编译器足够智能,可以展开和矢量化手动复制循环 - 这就是为什么结果代码看起来优化得很好。

为什么 System.arraycopy 变慢了?它本来是一个 native 方法,在编译器将其优化为 JVM intrinsic 之前,您必须为 native 调用付费。

但是,在您的测试中,编译器没有机会进行此类优化,因为 enlarge 方法没有被调用足够多的次数(即它不被认为是热的)。

我将向您展示一个强制优化的有趣技巧。重写enlarge方法如下:

private static int[] enlarge(int[] array, int size) {
    for (int i = 0; i < 10000; i++) { /* fool the JIT */ }

    int[] newArray = new int[array.length + size];
    System.arraycopy(array, 0, newArray, 0, array.length);
    return newArray;
}

一个空循环触发backedge counter溢出,进而触发enlarge方法的编译。然后从编译代码中消除空循环,因此它是无害的。现在 enlarge 方法比手动循环快 1.5 倍!

重要的是 System.arraycopy 紧跟在 new int[] 之后。在这种情况下,HotSpot 可以优化掉新分配数组的冗余归零。您知道,所有 Java 对象都必须在创建后立即归零。但就编译器检测到数组在创建后立即被填充而言,它可能会消除清零,从而使结果代码更快。

P.S. @assylias 的基准测试很好,但它也受到 System.arraycopy 未针对大型数组进行内化的事实的困扰。在小数组的情况下,每秒调用多次 arrayCopy 基准,JIT 认为它很热并且优化得很好。但对于大型数组,每次迭代都更长,因此每秒迭代次数要少得多,并且 JIT 不会将 arrayCopy 视为热数组。

关于java - System.arrayCopy 很慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40286540/

相关文章:

javascript - Javascript 中最常见的计算能力浪费是什么?

c - 为什么我不能将一个数组分配给另一个数组的子集?

java - try catch 循环(InputMismatchException 和 ArrayIndexOutOfBoundsException 之间的区别)

java - AssetManager空指针异常

java - JFrame 显示不正确

sql - 在包含数百万行的表上创建索引会导致任何问题吗?在实时生产数据库中做同样的事情是否存在危险?

database - 测量 Postgres 中的实际查询性能

java - For循环关闭/退出应用程序

java - 我需要为 JDK 1.6 下载什么 Jersey 版本?

java - 按名称的对象和方法