0%

Groovy 3之Native Lambda及Method/Constructor Reference

  虽然Groovy 3 Alpha版本就已经支持Java 8的Lambda、Method Reference以及Constructor Reference全部语法,但从bytecode层面来看,其本质上依然是Closure及Method Pointer,换言之,只是语法上的兼容,但性能上并没有提升。而Groovy的性能常为人诟病,所以Groovy 2引入了静态编译,其编译出来的bytecode跟Java的相近,所以其性能逼近Java。为了延续静态编译的性能优势,Groovy核心团队决定在Groovy 3 Beta版本中实现Lambda、Method Reference以及Constructor Reference的Native版本,其语法不变,但生成的bytecode与Java的相似,这便是”Native”的由来。

  Groovy 3编译器会借助静态编译的类型推断以及Java 7引入的invokedynamic指令生成与Java相似的bytecode,但如果类型推断失败,那么生成的bytecode则与Closure及Method Pointer相同。需要注意一点的是,除了性能方面的考量,在设计和实现Native Lambda时还考虑了尽可能保留Closure既有特性,为此每个Native Lambda都会对应一个内部类实例,性能方面稍有影响。另外,从DSL角度来看,Native Lambda无法引用自身,也无法引用外层Native Lambda实例,所以Native Lambda相对Closure而言不太适合应用于DSL的实现。

示例

  下面提供一个简单例子来展示Native Lambda、Native Method Reference以及Native Constructor Reference的bytecode

NativeDemo的Groovy源码

import groovy.transform.CompileStatic

@CompileStatic
class NativeDemo {
def nativeLambda() {
[1, -2, 3].stream().map(e -> Math.abs(e)).toList()
}
def nativeMethodReference() {
[1, -2, 3].stream().map(Math::abs).toList()
}
def nativeConstructorReference() {
[1, -2, 3].stream().toArray(Integer[]::new)
}
}

NativeDemo的bytecode

// class version 52.0 (52)
// access flags 0x21
public class NativeDemo implements groovy/lang/GroovyObject {

// compiled from: script1617499378185.groovy
// access flags 0x11
public final INNERCLASS NativeDemo$_nativeLambda_lambda1 null _nativeLambda_lambda1

// access flags 0x100A
private static synthetic Lorg/codehaus/groovy/reflection/ClassInfo; $staticClassInfo

// access flags 0x1089
public static transient synthetic Z __$stMC

// access flags 0x1082
private transient synthetic Lgroovy/lang/MetaClass; metaClass

// access flags 0x1
public <init>()V
@Lgroovy/transform/Generated;()
L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
ALOAD 0
INVOKEVIRTUAL NativeDemo.$getStaticMetaClass ()Lgroovy/lang/MetaClass;
ASTORE 1
ALOAD 1
ALOAD 0
SWAP
PUTFIELD NativeDemo.metaClass : Lgroovy/lang/MetaClass;
ALOAD 1
POP
L1
RETURN
LOCALVARIABLE this LNativeDemo; L0 L1 0
MAXSTACK = 2
MAXLOCALS = 2

// access flags 0x1
public nativeLambda()Ljava/lang/Object;
L0
LINENUMBER 6 L0
ICONST_3
ANEWARRAY java/lang/Object
DUP
ICONST_0
ICONST_1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
DUP
ICONST_1
BIPUSH -2
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
DUP
ICONST_2
ICONST_3
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.createList ([Ljava/lang/Object;)Ljava/util/List;
INVOKEINTERFACE java/util/Collection.stream ()Ljava/util/stream/Stream; (itf)
NEW NativeDemo$_nativeLambda_lambda1
DUP
LDC LNativeDemo;.class
DUP
INVOKESPECIAL NativeDemo$_nativeLambda_lambda1.<init> (Ljava/lang/Object;Ljava/lang/Object;)V
INVOKEDYNAMIC apply(LNativeDemo$_nativeLambda_lambda1;)Ljava/util/function/Function; [
// handle kind 0x6 : INVOKESTATIC
java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
// arguments:
(Ljava/lang/Object;)Ljava/lang/Object;,
// handle kind 0x5 : INVOKEVIRTUAL
NativeDemo$_nativeLambda_lambda1.doCall(Ljava/lang/Integer;)Ljava/lang/Object;,
(Ljava/lang/Integer;)Ljava/lang/Object;
]
INVOKEINTERFACE java/util/stream/Stream.map (Ljava/util/function/Function;)Ljava/util/stream/Stream; (itf)
INVOKESTATIC org/codehaus/groovy/vmplugin/v8/PluginDefaultGroovyMethods.toList (Ljava/util/stream/Stream;)Ljava/util/List;
ARETURN
L1
LOCALVARIABLE this LNativeDemo; L0 L1 0
MAXSTACK = 5
MAXLOCALS = 1

// access flags 0x1
public nativeMethodReference()Ljava/lang/Object;
L0
LINENUMBER 9 L0
ICONST_3
ANEWARRAY java/lang/Object
DUP
ICONST_0
ICONST_1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
DUP
ICONST_1
BIPUSH -2
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
DUP
ICONST_2
ICONST_3
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.createList ([Ljava/lang/Object;)Ljava/util/List;
INVOKEINTERFACE java/util/Collection.stream ()Ljava/util/stream/Stream; (itf)
INVOKEDYNAMIC apply()Ljava/util/function/Function; [
// handle kind 0x6 : INVOKESTATIC
java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
// arguments:
(Ljava/lang/Object;)Ljava/lang/Object;,
// handle kind 0x6 : INVOKESTATIC
java/lang/Math.abs(I)I,
(Ljava/lang/Integer;)I
]
INVOKEINTERFACE java/util/stream/Stream.map (Ljava/util/function/Function;)Ljava/util/stream/Stream; (itf)
INVOKESTATIC org/codehaus/groovy/vmplugin/v8/PluginDefaultGroovyMethods.toList (Ljava/util/stream/Stream;)Ljava/util/List;
ARETURN
L1
LOCALVARIABLE this LNativeDemo; L0 L1 0
MAXSTACK = 4
MAXLOCALS = 1

// access flags 0x1
public nativeConstructorReference()Ljava/lang/Object;
L0
LINENUMBER 12 L0
ICONST_3
ANEWARRAY java/lang/Object
DUP
ICONST_0
ICONST_1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
DUP
ICONST_1
BIPUSH -2
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
DUP
ICONST_2
ICONST_3
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.createList ([Ljava/lang/Object;)Ljava/util/List;
INVOKEINTERFACE java/util/Collection.stream ()Ljava/util/stream/Stream; (itf)
INVOKEDYNAMIC apply()Ljava/util/function/IntFunction; [
// handle kind 0x6 : INVOKESTATIC
java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
// arguments:
(I)Ljava/lang/Object;,
// handle kind 0x6 : INVOKESTATIC
NativeDemo.ctorRef$nativeConstructorReference$0(I)[Ljava/lang/Integer;,
(I)[Ljava/lang/Integer;
]
INVOKEINTERFACE java/util/stream/Stream.toArray (Ljava/util/function/IntFunction;)[Ljava/lang/Object; (itf)
ARETURN
L1
LOCALVARIABLE this LNativeDemo; L0 L1 0
MAXSTACK = 4
MAXLOCALS = 1

// access flags 0x1004
protected synthetic $getStaticMetaClass()Lgroovy/lang/MetaClass;
ALOAD 0
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
LDC LNativeDemo;.class
IF_ACMPEQ L0
ALOAD 0
INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.initMetaClass (Ljava/lang/Object;)Lgroovy/lang/MetaClass;
ARETURN
L0
FRAME SAME
GETSTATIC NativeDemo.$staticClassInfo : Lorg/codehaus/groovy/reflection/ClassInfo;
ASTORE 1
ALOAD 1
IFNONNULL L1
ALOAD 0
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
INVOKESTATIC org/codehaus/groovy/reflection/ClassInfo.getClassInfo (Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
DUP
ASTORE 1
PUTSTATIC NativeDemo.$staticClassInfo : Lorg/codehaus/groovy/reflection/ClassInfo;
L1
FRAME APPEND [org/codehaus/groovy/reflection/ClassInfo]
ALOAD 1
INVOKEVIRTUAL org/codehaus/groovy/reflection/ClassInfo.getMetaClass ()Lgroovy/lang/MetaClass;
ARETURN
MAXSTACK = 2
MAXLOCALS = 2

// access flags 0x1
public getMetaClass()Lgroovy/lang/MetaClass;
@Lgroovy/transform/Generated;()
@Lgroovy/transform/Internal;()
@Ljava/beans/Transient;()
ALOAD 0
GETFIELD NativeDemo.metaClass : Lgroovy/lang/MetaClass;
DUP
IFNULL L0
ARETURN
L0
FRAME SAME1 groovy/lang/MetaClass
POP
ALOAD 0
DUP
INVOKEVIRTUAL NativeDemo.$getStaticMetaClass ()Lgroovy/lang/MetaClass;
PUTFIELD NativeDemo.metaClass : Lgroovy/lang/MetaClass;
ALOAD 0
GETFIELD NativeDemo.metaClass : Lgroovy/lang/MetaClass;
ARETURN
MAXSTACK = 2
MAXLOCALS = 1

// access flags 0x1
public setMetaClass(Lgroovy/lang/MetaClass;)V
@Lgroovy/transform/Generated;()
@Lgroovy/transform/Internal;()
ALOAD 0
ALOAD 1
PUTFIELD NativeDemo.metaClass : Lgroovy/lang/MetaClass;
RETURN
MAXSTACK = 2
MAXLOCALS = 2

// access flags 0x101A
private final static synthetic ctorRef$nativeConstructorReference$0(I)[Ljava/lang/Integer;
@Lgroovy/transform/Generated;() // invisible
@Lgroovy/transform/CompileStatic;() // invisible
L0
ILOAD 0
MULTIANEWARRAY [Ljava/lang/Integer; 1
ARETURN
L1
LOCALVARIABLE param0 I L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}

NativeDemo$_nativeLambda_lambda1的bytecode(为Native Lambda而生成)

// class version 52.0 (52)
// access flags 0x31
public final class NativeDemo$_nativeLambda_lambda1 extends groovy/lang/Closure implements org/codehaus/groovy/runtime/GeneratedLambda {

// compiled from: script1617499378185.groovy
OUTERCLASS NativeDemo nativeLambda ()Ljava/lang/Object;
// access flags 0x11
public final INNERCLASS NativeDemo$_nativeLambda_lambda1 null _nativeLambda_lambda1

// access flags 0x100A
private static synthetic Lorg/codehaus/groovy/reflection/ClassInfo; $staticClassInfo

// access flags 0x1089
public static transient synthetic Z __$stMC

// access flags 0x1
public <init>(Ljava/lang/Object;Ljava/lang/Object;)V
L0
ALOAD 0
ALOAD 1
ALOAD 2
INVOKESPECIAL groovy/lang/Closure.<init> (Ljava/lang/Object;Ljava/lang/Object;)V
L1
RETURN
LOCALVARIABLE this LNativeDemo$_nativeLambda_lambda1; L0 L1 0
LOCALVARIABLE _outerInstance Ljava/lang/Object; L0 L1 1
LOCALVARIABLE _thisObject Ljava/lang/Object; L0 L1 2
MAXSTACK = 3
MAXLOCALS = 3

// access flags 0x1
// signature (Ljava/lang/Integer;)TR;
// declaration: R doCall(java.lang.Integer)
public doCall(Ljava/lang/Integer;)Ljava/lang/Object;
L0
LINENUMBER 6 L0
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue ()I
INVOKESTATIC java/lang/Math.abs (I)I
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ARETURN
L1
LINENUMBER 6 L1
FRAME FULL [] [java/lang/Throwable]
NOP
ATHROW
L2
LOCALVARIABLE this LNativeDemo$_nativeLambda_lambda1; L0 L2 0
LOCALVARIABLE e Ljava/lang/Integer; L0 L2 1
MAXSTACK = 1
MAXLOCALS = 2

// access flags 0x1004
protected synthetic $getStaticMetaClass()Lgroovy/lang/MetaClass;
ALOAD 0
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
LDC LNativeDemo$_nativeLambda_lambda1;.class
IF_ACMPEQ L0
ALOAD 0
INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.initMetaClass (Ljava/lang/Object;)Lgroovy/lang/MetaClass;
ARETURN
L0
FRAME SAME
GETSTATIC NativeDemo$_nativeLambda_lambda1.$staticClassInfo : Lorg/codehaus/groovy/reflection/ClassInfo;
ASTORE 1
ALOAD 1
IFNONNULL L1
ALOAD 0
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
INVOKESTATIC org/codehaus/groovy/reflection/ClassInfo.getClassInfo (Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
DUP
ASTORE 1
PUTSTATIC NativeDemo$_nativeLambda_lambda1.$staticClassInfo : Lorg/codehaus/groovy/reflection/ClassInfo;
L1
FRAME APPEND [org/codehaus/groovy/reflection/ClassInfo]
ALOAD 1
INVOKEVIRTUAL org/codehaus/groovy/reflection/ClassInfo.getMetaClass ()Lgroovy/lang/MetaClass;
ARETURN
MAXSTACK = 2
MAXLOCALS = 2
}