[讨论] Java泛型的性能损耗
marmot
2011-05-05
对于使用泛型引入的性能损耗有一点不太明白,还请各位指教
对于 Lits list1 = new ArrayList(); list1.add("mmm"); String str2 = (String)list1.get(0); List<String> list list2 = new ArrayList<String>(); list2.add("mmm"); String str2 = list2.get(0); 这样的情况javac编译出来的字节码是一样的这个无须讨论
但如果是我自定义的泛型集合呢? public class ClassGenericDemo { public static void main(String[] args){ GenericClass<String> gc = new GenericClass<String>(); gc.add("mmm"); String str1 = gc.remove(0); System.out.println( str1 ); System.out.println( "========================" ); NonGenericClass ngc = new NonGenericClass(); ngc.add("mmm"); String str2 = (String)ngc.remove(0); System.out.println( str2 ); } } class GenericClass<E>{ private E[] ary = (E[])new Object[3]; public void add(E ele){ ary[0] = ele; } public E remove(int i){ E res = ary[i]; ary[i] = null; return res; } } class NonGenericClass{ private Object[] ary = new Object[3]; public void add(Object ele){ ary[0] = ele; } public Object remove(int i){ Object res = ary[i]; ary[i] = null; return res; } } 我用javap看了一下 main方法里面执行这里是没有差异 public static void main(java.lang.String[]); Code: Stack=2, Locals=5, Args_size=1 0: new #2; //class GenericClass 3: dup 4: invokespecial #3; //Method GenericClass."<init>":()V 7: astore_1 8: aload_1 9: ldc #4; //String mmm 11: invokevirtual #5; //Method GenericClass.add:(Ljava/lang/Object;)V 14: aload_1 15: iconst_0 16: invokevirtual #6; //Method GenericClass.remove:(I)Ljava/lang/Object; 19: checkcast #7; //class java/lang/String 22: astore_2 23: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream; 26: aload_2 27: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream; 33: ldc #10; //String ======================== 35: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 38: new #11; //class NonGenericClass 41: dup 42: invokespecial #12; //Method NonGenericClass."<init>":()V 45: astore_3 46: aload_3 47: ldc #4; //String mmm 49: invokevirtual #13; //Method NonGenericClass.add:(Ljava/lang/Object;)V 52: aload_3 53: iconst_0 54: invokevirtual #14; //Method NonGenericClass.remove:(I)Ljava/lang/Object; 57: checkcast #7; //class java/lang/String 60: astore 4 62: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream; 65: aload 4 67: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 70: return 我接着看了一下 采用了泛型声明和未采用泛型声明的这两个类,发现在有泛型声明的类的初始化时会多出来一个 9: checkcast #3; //class "[Ljava/lang/Object;" 请问这多出来的类型转换会影响性能吗? GenericClass GenericClass(); Code: Stack=2, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: aload_0 5: iconst_3 6: anewarray #2; //class java/lang/Object 9: checkcast #3; //class "[Ljava/lang/Object;" 12: putfield #4; //Field ary:[Ljava/lang/Object; 15: return LineNumberTable: line 16: 0 line 17: 4 NonGenericClass NonGenericClass(); Code: Stack=2, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: aload_0 5: iconst_3 6: anewarray #2; //class java/lang/Object 9: putfield #3; //Field ary:[Ljava/lang/Object; 12: return LineNumberTable: line 30: 0 line 31: 4 还有 声明泛型的字节码比未声明中泛型的多出有 const #8 = Asciz Signature;
const #19 = Asciz (I)TE;;
Signature: length = 0x2
等 这表示什么意思 本人小菜 盼解答 谢谢
|
|
RednaxelaFX
2011-05-06
关于signature可以参考以前的一帖,答复: Java获得泛型类型。帖里我放了JVM规范里相关内容的链接,可以参考。
也可以等IcyFenix的书出来读一下Class文件格式的章节,也有些讲解的 ^_^ 至于执行效率,你也应该发现了,当你要做的事情实际上跟泛型的实现帮你做的是一模一样的时候,执行效率自然完全没有区别。表现就是你例子中的main()方法。 可以看到,当泛型参数出现在方法的输入位置(参数)的时候,传参过程是可以不需要checkcast的,因为泛型参数被擦除后就是Object,而所有Java的引用类型都可以安全的向上转型为Object而不需要checkcast。但要留意带约束的泛型的情况会略有区别,会出现checkcast。另外如果传入的是原始类型的值则会在传参数时带有自动装箱逻辑,这是由Java的源码级编译器(例如javac)完成的。 而当泛型参数出现在方法的输出位置(返回值)的时候,调用该方法的地方就可能会插入checkcast指令。插入了checkcast的例子看上面的main()里代码就有了。如果返回值被忽略了的话则不会插入checkcast,看下面的例子。 public class Z { public static <T> T foo() { return (T) new Object(); } public static void main(String[] args) { Z.<String>foo(); } } rednaxelafx@fx-laptop:~/experiment$ javac Z.java Note: Z.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. rednaxelafx@fx-laptop:~/experiment$ javap -verbose Z Compiled from "Z.java" public class Z extends java.lang.Object SourceFile: "Z.java" minor version: 0 major version: 50 Constant pool: const #1 = Method #2.#17; // java/lang/Object."<init>":()V const #2 = class #18; // java/lang/Object const #3 = Method #4.#19; // Z.foo:()Ljava/lang/Object; const #4 = class #20; // Z const #5 = Asciz <init>; const #6 = Asciz ()V; const #7 = Asciz Code; const #8 = Asciz LineNumberTable; const #9 = Asciz foo; const #10 = Asciz ()Ljava/lang/Object;; const #11 = Asciz Signature; const #12 = Asciz <T:Ljava/lang/Object;>()TT;; const #13 = Asciz main; const #14 = Asciz ([Ljava/lang/String;)V; const #15 = Asciz SourceFile; const #16 = Asciz Z.java; const #17 = NameAndType #5:#6;// "<init>":()V const #18 = Asciz java/lang/Object; const #19 = NameAndType #9:#10;// foo:()Ljava/lang/Object; const #20 = Asciz Z; { public Z(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public static java.lang.Object foo(); Code: Stack=2, Locals=0, Args_size=0 0: new #2; //class java/lang/Object 3: dup 4: invokespecial #1; //Method java/lang/Object."<init>":()V 7: areturn LineNumberTable: line 3: 0 Signature: length = 0x2 00 0C public static void main(java.lang.String[]); Code: Stack=1, Locals=1, Args_size=1 0: invokestatic #3; //Method foo:()Ljava/lang/Object; 3: pop 4: return LineNumberTable: line 7: 0 line 8: 4 } rednaxelafx@fx-laptop:~/experiment$ java Z rednaxelafx@fx-laptop:~/experiment$ << 这个foo()本来“应该”保证它返回的值符合泛型参数的要求(String),但这里反正没使用那个返回值,所以编译就没生成checkcast,然后运行也不会报错。 至于你的例子里构造起里出现的checkcast,那个如果被一个JVM解释执行的话是会有额外开销的。HotSpot VM里的解释器就如此,会真的按照编译出来的字节码一条条执行过去。 但如果被JIT编译过的话就不同了。以HotSpot Server VM的实际情况为例,在64位的JDK 6 update 25上跑, rednaxelafx@fx-laptop:~/experiment$ java -Xcomp -version java version "1.6.0_25" Java(TM) SE Runtime Environment (build 1.6.0_25-b06) Java HotSpot(TM) 64-Bit Server VM (build 20.0-b11, compiled mode) rednaxelafx@fx-laptop:~/experiment$ java -Xcomp -XX:CompileCommand=dontinline,*GenericClass.* -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly ClassGenericDemo > comp.log 加的几个参数的用途是: 1. -Xcomp 让所有方法都直接被JIT编译(懒得改代码去预热了) 2. -XX:CompileCommand=dontinline,*GenericClass.* 让GenericClass与NonGenericClass里的所有方法都不要被内联(inline)。内联了的话要解释生成出来的代码会麻烦一些,所以这里也是为了偷懒加的参数 3. -XX:+UnlockDiagnosticVMOptions 这是为了后面的PrintAssembly参数可用 4. -XX:+PrintAssembly 将JIT编译生成的方法的汇编代码打印到标准输出流。相关可以参考另一帖。 可以从输出的反汇编结果里看到GenericClass与NonGenericClass的构造器实际编译出来的代码: Decoding compiled method 0x00007f3ea5f039d0: Code: [Disassembling for mach='i386:x86-64'] [Entry Point] [Constants] # {method} '<init>' '()V' in 'GenericClass' # [sp+0x20] (sp of caller) 0x00007f3ea5f03b40: mov 0x8(%rsi),%r10d 0x00007f3ea5f03b44: shl $0x3,%r10 0x00007f3ea5f03b48: cmp %r10,%rax 0x00007f3ea5f03b4b: jne 0x00007f3ea5e4a620 ; {runtime_call} 0x00007f3ea5f03b51: xchg %ax,%ax 0x00007f3ea5f03b54: nopl 0x0(%rax,%rax,1) 0x00007f3ea5f03b5c: xchg %ax,%ax [Verified Entry Point] 0x00007f3ea5f03b60: mov %eax,-0x6000(%rsp) 0x00007f3ea5f03b67: push %rbp 0x00007f3ea5f03b68: sub $0x10,%rsp ;*synchronization entry ; - GenericClass::<init>@-1 (line 15) 0x00007f3ea5f03b6c: mov %rsi,%rbp 0x00007f3ea5f03b6f: callq 0x00007f3ea5e4a820 ; OopMap{rbp=Oop off=52} ;*invokespecial <init> ; - GenericClass::<init>@1 (line 15) ; {optimized virtual_call} 0x00007f3ea5f03b74: mov 0x60(%r15),%rax 0x00007f3ea5f03b78: mov %rax,%r10 0x00007f3ea5f03b7b: add $0x20,%r10 0x00007f3ea5f03b7f: cmp 0x70(%r15),%r10 0x00007f3ea5f03b83: jae 0x00007f3ea5f03bf2 0x00007f3ea5f03b85: mov %r10,0x60(%r15) 0x00007f3ea5f03b89: prefetchnta 0xc0(%r10) 0x00007f3ea5f03b91: movq $0x1,(%rax) 0x00007f3ea5f03b98: prefetchnta 0x100(%r10) 0x00007f3ea5f03ba0: movl $0xeffd4ccb,0x8(%rax) ; {oop('java/lang/Object'[])} 0x00007f3ea5f03ba7: prefetchnta 0x140(%r10) 0x00007f3ea5f03baf: movl $0x3,0xc(%rax) 0x00007f3ea5f03bb6: prefetchnta 0x180(%r10) 0x00007f3ea5f03bbe: mov %r12,0x10(%rax) 0x00007f3ea5f03bc2: mov %r12,0x18(%rax) ;*anewarray ; - GenericClass::<init>@6 (line 16) 0x00007f3ea5f03bc6: mov %rbp,%r10 0x00007f3ea5f03bc9: mov %rax,%r11 0x00007f3ea5f03bcc: shr $0x3,%r11 0x00007f3ea5f03bd0: mov %r11d,0xc(%rbp) 0x00007f3ea5f03bd4: shr $0x9,%r10 0x00007f3ea5f03bd8: mov $0x7f3ea1e12000,%r11 0x00007f3ea5f03be2: mov %r12b,(%r11,%r10,1) ;*invokespecial <init> ; - GenericClass::<init>@1 (line 15) 0x00007f3ea5f03be6: add $0x10,%rsp 0x00007f3ea5f03bea: pop %rbp 0x00007f3ea5f03beb: test %eax,0x55f440f(%rip) # 0x00007f3eab4f8000 ; {poll_return} 0x00007f3ea5f03bf1: retq 0x00007f3ea5f03bf2: mov $0x3,%edx 0x00007f3ea5f03bf7: mov $0x77fea6658,%rsi ; {oop('java/lang/Object'[])} 0x00007f3ea5f03c01: xchg %ax,%ax 0x00007f3ea5f03c03: callq 0x00007f3ea5e6fb20 ; OopMap{rbp=Oop off=200} ;*anewarray ; - GenericClass::<init>@6 (line 16) ; {runtime_call} 0x00007f3ea5f03c08: jmp 0x00007f3ea5f03bc6 ;*anewarray ; - GenericClass::<init>@6 (line 16) 0x00007f3ea5f03c0a: mov %rax,%rsi 0x00007f3ea5f03c0d: jmp 0x00007f3ea5f03c12 ;*invokespecial <init> ; - GenericClass::<init>@1 (line 15) 0x00007f3ea5f03c0f: mov %rax,%rsi ;*anewarray ; - GenericClass::<init>@6 (line 16) 0x00007f3ea5f03c12: add $0x10,%rsp 0x00007f3ea5f03c16: pop %rbp 0x00007f3ea5f03c17: jmpq 0x00007f3ea5e6f4a0 ; {runtime_call} 0x00007f3ea5f03c1c: hlt 0x00007f3ea5f03c1d: hlt 0x00007f3ea5f03c1e: hlt 0x00007f3ea5f03c1f: hlt [Stub Code] 0x00007f3ea5f03c20: mov $0x0,%rbx ; {no_reloc} 0x00007f3ea5f03c2a: jmpq 0x00007f3ea5f03c2a ; {runtime_call} [Exception Handler] 0x00007f3ea5f03c2f: jmpq 0x00007f3ea5e6fca0 ; {runtime_call} [Deopt Handler Code] 0x00007f3ea5f03c34: callq 0x00007f3ea5f03c39 0x00007f3ea5f03c39: subq $0x5,(%rsp) 0x00007f3ea5f03c3e: jmpq 0x00007f3ea5e4b7c0 ; {runtime_call} 0x00007f3ea5f03c43: add %al,(%rax) 0x00007f3ea5f03c45: add %al,(%rax) 0x00007f3ea5f03c47: .byte 0x0 Decoding compiled method 0x00007f3ea5f14310: Code: [Disassembling for mach='i386:x86-64'] [Entry Point] [Constants] # {method} '<init>' '()V' in 'NonGenericClass' # [sp+0x20] (sp of caller) 0x00007f3ea5f14480: mov 0x8(%rsi),%r10d 0x00007f3ea5f14484: shl $0x3,%r10 0x00007f3ea5f14488: cmp %r10,%rax 0x00007f3ea5f1448b: jne 0x00007f3ea5e4a620 ; {runtime_call} 0x00007f3ea5f14491: xchg %ax,%ax 0x00007f3ea5f14494: nopl 0x0(%rax,%rax,1) 0x00007f3ea5f1449c: xchg %ax,%ax [Verified Entry Point] 0x00007f3ea5f144a0: mov %eax,-0x6000(%rsp) 0x00007f3ea5f144a7: push %rbp 0x00007f3ea5f144a8: sub $0x10,%rsp ;*synchronization entry ; - NonGenericClass::<init>@-1 (line 29) 0x00007f3ea5f144ac: mov %rsi,%rbp 0x00007f3ea5f144af: callq 0x00007f3ea5e4a820 ; OopMap{rbp=Oop off=52} ;*invokespecial <init> ; - NonGenericClass::<init>@1 (line 29) ; {optimized virtual_call} 0x00007f3ea5f144b4: mov 0x60(%r15),%rax 0x00007f3ea5f144b8: mov %rax,%r10 0x00007f3ea5f144bb: add $0x20,%r10 0x00007f3ea5f144bf: cmp 0x70(%r15),%r10 0x00007f3ea5f144c3: jae 0x00007f3ea5f14532 0x00007f3ea5f144c5: mov %r10,0x60(%r15) 0x00007f3ea5f144c9: prefetchnta 0xc0(%r10) 0x00007f3ea5f144d1: movq $0x1,(%rax) 0x00007f3ea5f144d8: prefetchnta 0x100(%r10) 0x00007f3ea5f144e0: movl $0xeffd4ccb,0x8(%rax) ; {oop('java/lang/Object'[])} 0x00007f3ea5f144e7: prefetchnta 0x140(%r10) 0x00007f3ea5f144ef: movl $0x3,0xc(%rax) 0x00007f3ea5f144f6: prefetchnta 0x180(%r10) 0x00007f3ea5f144fe: mov %r12,0x10(%rax) 0x00007f3ea5f14502: mov %r12,0x18(%rax) ;*anewarray ; - NonGenericClass::<init>@6 (line 30) 0x00007f3ea5f14506: mov %rbp,%r10 0x00007f3ea5f14509: mov %rax,%r11 0x00007f3ea5f1450c: shr $0x3,%r11 0x00007f3ea5f14510: mov %r11d,0xc(%rbp) 0x00007f3ea5f14514: shr $0x9,%r10 0x00007f3ea5f14518: mov $0x7f3ea1e12000,%r11 0x00007f3ea5f14522: mov %r12b,(%r11,%r10,1) ;*invokespecial <init> ; - NonGenericClass::<init>@1 (line 29) 0x00007f3ea5f14526: add $0x10,%rsp 0x00007f3ea5f1452a: pop %rbp 0x00007f3ea5f1452b: test %eax,0x55e3acf(%rip) # 0x00007f3eab4f8000 ; {poll_return} 0x00007f3ea5f14531: retq 0x00007f3ea5f14532: mov $0x3,%edx 0x00007f3ea5f14537: mov $0x77fea6658,%rsi ; {oop('java/lang/Object'[])} 0x00007f3ea5f14541: xchg %ax,%ax 0x00007f3ea5f14543: callq 0x00007f3ea5e6fb20 ; OopMap{rbp=Oop off=200} ;*anewarray ; - NonGenericClass::<init>@6 (line 30) ; {runtime_call} 0x00007f3ea5f14548: jmp 0x00007f3ea5f14506 ;*anewarray ; - NonGenericClass::<init>@6 (line 30) 0x00007f3ea5f1454a: mov %rax,%rsi 0x00007f3ea5f1454d: jmp 0x00007f3ea5f14552 ;*invokespecial <init> ; - NonGenericClass::<init>@1 (line 29) 0x00007f3ea5f1454f: mov %rax,%rsi ;*anewarray ; - NonGenericClass::<init>@6 (line 30) 0x00007f3ea5f14552: add $0x10,%rsp 0x00007f3ea5f14556: pop %rbp 0x00007f3ea5f14557: jmpq 0x00007f3ea5e6f4a0 ; {runtime_call} 0x00007f3ea5f1455c: hlt 0x00007f3ea5f1455d: hlt 0x00007f3ea5f1455e: hlt 0x00007f3ea5f1455f: hlt [Stub Code] 0x00007f3ea5f14560: mov $0x0,%rbx ; {no_reloc} 0x00007f3ea5f1456a: jmpq 0x00007f3ea5f1456a ; {runtime_call} [Exception Handler] 0x00007f3ea5f1456f: jmpq 0x00007f3ea5e6fca0 ; {runtime_call} [Deopt Handler Code] 0x00007f3ea5f14574: callq 0x00007f3ea5f14579 0x00007f3ea5f14579: subq $0x5,(%rsp) 0x00007f3ea5f1457e: jmpq 0x00007f3ea5e4b7c0 ; {runtime_call} 0x00007f3ea5f14583: add %al,(%rax) 0x00007f3ea5f14585: add %al,(%rax) 0x00007f3ea5f14587: .byte 0x0 找个diff工具不难发现这两个版本的代码主要的差异就在地址上——毕竟它们是两份独立的代码,地址是不会一样的。但除去地址的差异,里面实际的指令全部是一样的。这是因为编译器从上下文能得到足够信息,发现那个checkcast其实是必然成功的,所以可以安全的将起削除掉。 但当然,不是所有情况都一定那么好运。也有削除不掉的时候。不过,得益于HotSpot中的快速子类型检查(论文:Fast Subtype Checking in the HotSpot JVM)的实现,checkcast的快速路径也是比较快的。 |
|
marmot
2011-05-06
非常感谢 RednaxelaFX 的回答
这么晚了还让您惦记着回帖 真是很过意不去 身体是革 命的本钱 还望多多注意休息 IcyFenix的书出来一定得仔细读读 好好补补vm这块的知识 经RednaxelaFX详尽的解答 我有点明白但又不太明白 呵呵 看来是我的知识点掌握得还不够 你提到的知识点比较多 我先好好补补 再次感谢RednaxelaFX |
|
RednaxelaFX
2011-05-06
marmot 写道 经RednaxelaFX详尽的解答 我有点明白但又不太明白
嘛,我也没特别详细的说就是了,像是“JIT编译器怎么就知道可以削除掉checkcast了”。有什么具体的点还是不理解的话请继续提问~ |
|
竹隐江南
2011-05-10
我还要走很长的路。
|