Java性能漫谈-数组复制之System.arraycopy

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

当我还年幼的时候,我很任性,复制数组也是,写一个for循环,来回倒腾,后来长大了,就发现了System.arraycopy的好处。

为了测试俩者的区别我写了一个简单赋值int[100000]的程序来对比,并且中间使用了nanoTime来计算时间差:

程序如下:

int[] a =newint[100000];for(inti=0;i<a.length;i++){

a[i]=i;

}int[] b =newint[100000];int[] c =newint[100000];for(inti=0;i<c.length;i++){

c[i]=i;

}int[] d =newint[100000];for(intk=0;k<10;k++){longstart1 =System.nanoTime();for(inti=0;i<a.length;i++){

b[i]=a[i];

}longend1 =System.nanoTime();

System.out.PRintln("end1 - start1 = "+(end1-start1));longstart2 =System.nanoTime();

System.arraycopy(c,0, d, 0, 100000);longend2 =System.nanoTime();

System.out.println("end2 - start2 = "+(end2-start2));

System.out.println();

}

为了避免内存不稳定干扰和运行的偶然性结果,我在一开始的时候把所有空间申明完成,并且只之后循环10次执行,得到如下结果:

end1 - start1 = 366806end2- start2 = 109154end1- start1 = 380529end2- start2 = 79849end1- start1 = 421422end2- start2 = 68769end1- start1 = 344463end2- start2 = 72020end1- start1 = 333174end2- start2 = 77277end1- start1 = 377335end2- start2 = 82285end1- start1 = 370608end2- start2 = 66937end1- start1 = 349067end2- start2 = 86532end1- start1 = 389974end2- start2 = 83362end1- start1 = 347937end2- start2 = 63638

可以看出,System.arraycopy的性能很不错,为了看看究竟这个底层是如何处理的,我找到openJDK的一些代码留恋了一些:

System.arraycopy是一个native函数,需要看native层的代码:

publicstaticnativevoidarraycopy(Object src,intsrcPos,

Object dest,intdestPos,intlength);

找到对应的openjdk6-src/hotspot/src/share/vm/prims/jvm.cpp,这里有JVM_ArrayCopy的入口:

JVM_ENTRY(void, JVM_ArrayCopy(JNIEnv *env, jclass ignored, jobject src, jint src_pos,

jobject dst, jint dst_pos, jint length))

JVMWrapper("JVM_ArrayCopy");//Check if we have null pointersif(src == NULL || dst ==NULL) {

THROW(vmSymbols::java_lang_NullPointerException());

}

arrayOop s=arrayOop(JNIHandles::resolve_non_null(src));

arrayOop d=arrayOop(JNIHandles::resolve_non_null(dst));

assert(s->is_oop(),"JVM_ArrayCopy: src not an oop");

assert(d->is_oop(),"JVM_ArrayCopy: dst not an oop");//Do copyKlass::cast(s->klass())->copy_array(s, src_pos, d, dst_pos, length, thread);

JVM_END

前面的语句都是判断,知道最后的copy_array(s, src_pos, d, dst_pos, length, thread)是真正的copy,进一步看这里,在openjdk6-src/hotspot/src/share/vm/oops/typeArrayKlass.cpp中:

voidtypeArrayKlass::copy_array(arrayOop s,intsrc_pos, arrayOop d,intdst_pos,intlength, TRAPS) {

assert(s->is_typeArray(),"must be type array");//Check destinationif(!d->is_typeArray() || element_type() != typeArrayKlass::cast(d->klass())->element_type()) {

THROW(vmSymbols::java_lang_ArrayStoreException());

}//Check is all offsets and lengths are non negativeif(src_pos <0|| dst_pos <0|| length <0) {

THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException());

}//Check if the ranges are validif( (((unsignedint) length + (unsignedint) src_pos) > (unsignedint) s->length())|| (((unsignedint) length + (unsignedint) dst_pos) > (unsignedint) d->length()) ) {

THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException());

}//Check zero copyif(length ==0)return;//This is an attempt to make the copy_array fast.intl2es =log2_element_size();intihs = array_header_in_bytes() /WordSize;char* src = (char*) ((oop*)s + ihs) + ((size_t)src_pos <<l2es);char* dst = (char*) ((oop*)d + ihs) + ((size_t)dst_pos <<l2es);

Copy::conjoint_memory_atomic(src, dst, (size_t)length<<l2es);//还是在这里处理copy

}

这个函数之前的仍然是一堆判断,直到最后一句才是真实的拷贝语句。

在openjdk6-src/hotspot/src/share/vm/utilities/copy.cpp中找到对应的函数:

//Copy bytes; larger units are filled atomically if everything is aligned.voidCopy::conjoint_memory_atomic(void*from,void*to, size_t size) {

address src= (address)from;

address dst=(address) to;

uintptr_t bits= (uintptr_t) src | (uintptr_t) dst |(uintptr_t) size;//(Note: We could improve performance by ignoring the low bits of size,//and putting a short cleanup loop after each bulk copy loop.//There are plenty of other ways to make this faster also,//and it's a slippery slope. For now, let's keep this code simple//since the simplicity helps clarify the atomicity semantics of//thisOperation. There are also CPU-specific assembly versions//which may or may not want to include such optimizations.)if(bits %sizeof(jlong) ==0) {

Copy::conjoint_jlongs_atomic((jlong*) src, (jlong*) dst, size /sizeof(jlong));

}elseif(bits %sizeof(jint) ==0) {

Copy::conjoint_jints_atomic((jint*) src, (jint*) dst, size /sizeof(jint));

}elseif(bits %sizeof(jshort) ==0) {

Copy::conjoint_jshorts_atomic((jshort*) src, (jshort*) dst, size /sizeof(jshort));

}else{//Not aligned, so no need to be atomic.Copy::conjoint_jbytes((void*) src, (void*) dst, size);

}

}

上面的代码展示了选择哪个copy函数,我们选择conjoint_jints_atomic,在openjdk6-src/hotspot/src/share/vm/utilities/copy.hpp进一步查看:

//jints, conjoint, atomic on each jintstaticvoidconjoint_jints_atomic(jint*from, jint*to, size_t count) {

assert_params_ok(from, to, LogBytesPerInt);

pd_conjoint_jints_atomic(from, to, count);

}

继续向下查看,在openjdk6-src/hotspot/src/cpu/zero/vm/copy_zero.hpp中:

staticvoidpd_conjoint_jints_atomic(jint*from, jint*to, size_t count) {

_Copy_conjoint_jints_atomic(from, to, count);

}

继续向下查看,在openjdk6-src/hotspot/src/os_cpu/linux_zero/vm/os_linux_zero.cpp中:

void_Copy_conjoint_jints_atomic(jint*from, jint*to, size_t count) {if(from>to) {

jint*end =from+count;while(from<end)*(to++) = *(from++);

}elseif(from<to) {

jint*end =from;from+= count -1;

to+= count -1;while(from>=end)*(to--) = *(from--);

}

}

可以看到,直接就是内存块赋值的逻辑了,这样避免很多引用来回倒腾的时间,必然就变快了。

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航