我一直在努力测量System.arrayCopy vs
Arrays.copyOf的性能,以便正确选择其中之一.只是为了基准,我添加了手动拷贝,结果令我惊讶.
显然我错过了一些非常重要的东西,可以请告诉我,它是什么?实现如下(见前4种方法).
显然我错过了一些非常重要的东西,可以请告诉我,它是什么?实现如下(见前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,target,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,runs ); warmup( ArrayCopy::copyManually,runs ); System.out.println( "****************** warm up #2 ******************" ); warmup( ArrayCopy::copyByArraysCopyOf,runs ); System.out.println( "********************* test *********************" ); System.out.print( "copyByArrayCopyOf" ); runTest( ArrayCopy::copyByArraysCopyOf,runs ); System.out.print( "copyByEnlarge" ); runTest( ArrayCopy::copyByEnlarge,runs ); System.out.print( "copyManually" ); runTest( ArrayCopy::copyManually,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[],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更慢?它最初是一种本机方法,您必须支付本机调用,直到编译器将其优化为JVM内在函数.
然而,在测试中,编译器没有机会进行这种优化,因为放大方法不被称为很多次(即不被认为是热).
我会给你一个有趣的技巧来强制优化.重写放大法如下:
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,newArray,array.length); return newArray; }
空循环触发反向计数器溢出,这反过来触发了放大方法的编译.然后从编译的代码中消除空循环,因此它是无害的.现在的放大方法比手动循环快约1.5倍!
重要的是System.arraycopy紧随新的int [].在这种情况下,HotSpot可以优化新分配的阵列的冗余归零.您知道,所有Java对象必须在创建后立即归零.但是,就编译器检测到数组在创建后立即被填充,可能会消除归零,从而使结果代码更快.
附: @assylias’的基准是好的,但也是因为System.arraycopy并没有为大数组内化的事实.在小数组的情况下,ArrayCopy基准测试每秒调用多次,JIT认为它很热,并能很好地优化.但是,对于大型阵列,每次迭代都会更长,因此每秒重复次数少得多,JIT不会将arrayCopy视为热.