[讨论] 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 #9 = Asciz    [TE;;

 

const #19 = Asciz    (I)TE;;
const #20 = Asciz    <E:Ljava/lang/Object;>Ljava/lang/Object;;

 

 Signature: length = 0x2
   00 10

 

等 这表示什么意思 本人小菜 盼解答 谢谢

 

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
我还要走很长的路。
Global site tag (gtag.js) - Google Analytics