inline ,noinline 和 crossinline

290 阅读7分钟
  • inline 在编译时,会将此修饰符修饰的函数复制到调用处(称为内联),避免创建 Function 对象,以减少创建对象的内存开销。
  • noinline 需要配合 inline 使用,使用在函数型参上,告诉编译器当前这个函数不能内联
  • crossinline 需要配合 inline 使用,告诉编译器不能使用 return,否则就跳出调用处函数,但是可以使用 return@label 跳出指定外层。(本身在 lambda 内部就是不能使用 return的,而只能使用 return@label

kotlin 中函数是一等公民,可以存储于变量和其他数据结构中,可以当作参数传递,也可以当做返回类型返回。

高阶函数:使用了函数参数或者返回函数类型的函数。

在编译时,每一个函数都会被编译为一个 FunctionX 类型,运行时,每一个函数都是一个对象。

为了优化此现象,可以使用 inline 修饰符,它会将函数的代码内联(复制)到调用处。

话语过于枯燥,实践出真知。

简单创建一个高阶函数,使用了两个函数型参。

 fun cal(a: Int, b: Int, operate: (Int, Int) -> Int, result: (Int) -> Unit) {
    var c = operate(a, b)
    println("cal : $a ,$b = $c")
    result(c)
}

调用处

fun test(): Int {
    var product = 0
    for (i in 1 until 10) {
        cal(product, i, { a, b ->
            var c = a + b
            println("$a + $b = $c")
            c
        }, {
            product += it
            println("product -> $product")
        })
        println("for -> $i")
    }
    println(" 是不是到了这里,没有到这里,说明直接跳出 test 函数了 product ->$product")
    return product
}

cal 函数使用了两个函数类型的参数,在调用处使用两个 lambda 表达式。 在编译时,每个 lambda 表达式都会被编译成 FunctionX 类,有几个参数编译成几,例如这里的 Function1 Function2 类(默认最多22个,再多就得自己写扩展了)。

在调用处就是创建对象使用了,看一下编译后的代码就很明确了。

public static final void cal(int a, int b, @NotNull Function2 operate, @NotNull Function1 result) {
   Intrinsics.checkParameterIsNotNull(operate, "operate");
   Intrinsics.checkParameterIsNotNull(result, "result");
   int c = ((Number)operate.invoke(a, b)).intValue();
   String var5 = "cal : " + a + " ," + b + " = " + c;
   boolean var6 = false;
   System.out.println(var5);
   result.invoke(c);
}

public static final int test() {
   final IntRef product = new IntRef();
   product.element = 0;
   int i = 1;

   for(byte var2 = 10; i < var2; ++i) {
      cal(product.element, i, (Function2)null.INSTANCE, (Function1)(new Function1() {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke(Object var1) {
            this.invoke(((Number)var1).intValue());
            return Unit.INSTANCE;
         }

         public final void invoke(int it) {
            IntRef var10000 = product;
            var10000.element += it;
            String var2 = "product -> " + product.element;
            boolean var3 = false;
            System.out.println(var2);
         }
      }));
      String var3 = "for -> " + i;
      boolean var4 = false;
      System.out.println(var3);
   }

   String var5 = " 是不是到了这里,没有到这里,说明直接跳出 test 函数了 product ->" + product.element;
   boolean var6 = false;
   System.out.println(var5);
   return product.element;
}

在使用 inline修饰高阶函数后,就把代码内联到调用处了,就不会编译为 FunctionX 类,此举减少了运行时的对象创建开销。

 inline fun cal(a: Int, b: Int, operate: (Int, Int) -> Int, result: (Int) -> Unit) {
    var c = operate(a, b)
    println("cal : $a ,$b = $c")
    result(c)
}

看使用了 inline 后的调用处代码,是把代码复制过来了,不会再创建对象去使用了。虽然声明处并没有什么改变。

public static final void cal(int a, int b, @NotNull Function2 operate, @NotNull Function1 result) {
   int $i$f$cal = 0;
   Intrinsics.checkParameterIsNotNull(operate, "operate");
   Intrinsics.checkParameterIsNotNull(result, "result");
   int c = ((Number)operate.invoke(a, b)).intValue();
   String var6 = "cal : " + a + " ," + b + " = " + c;
   boolean var7 = false;
   System.out.println(var6);
   result.invoke(c);
}
public static final int test() {
   int product = 0;
   int i = 1;

   for(byte var2 = 10; i < var2; ++i) {
      int $i$f$cal = false;
      int var7 = false;
      int c = product + i;
      String var9 = product + " + " + i + " = " + c;
      boolean var10 = false;
      System.out.println(var9);
      String var6 = "cal : " + product + " ," + i + " = " + c;
      boolean var5 = false;
      System.out.println(var6);
      int var15 = false;
      product += c;
      var9 = "product -> " + product;
      var10 = false;
      System.out.println(var9);
      String var3 = "for -> " + i;
      $i$f$cal = false;
      System.out.println(var3);
   }

   String var12 = " 是不是到了这里,没有到这里,说明直接跳出 test 函数了 product ->" + product;
   boolean var13 = false;
   System.out.println(var12);
   return product;
}

虽然这样避免了创建对象的内存开销,但是也出现了一个问题,那就是代码被内联到调用处了,lambda内部就可以使用 return 语句了,并且会直接作用于调用处函数。 (本身 lambda 内部是不能使用 return 的)

fun test(): Int {
    var product = 0
    for (i in 1 until 10) {
        cal(product, i, { a, b ->
            var c = a + b
            println("$a + $b = $c")
            c
        }, {
            if (it > 50) { //如果使用了 inline 修饰 cal 函数会直接跳出 test 函数。因为在编译时已经被内联到这里了,所以 return 就是直接在 test() 函数里 return 了。如果不想这样,可以用 crossinline 修饰这个型参
                println("$it  > 50 了,就在这停了 ")
                return 50
            }
            product += it
            println("product -> $product")
        })
        println("for -> $i")
    }
    println(" 是不是到了这里,没有到这里,说明直接跳出 test 函数了 product ->$product")
    return product
}

这里 就可以使用 crossinline 了。它需要配合 inline 使用,就是告诉编译器,不能使用 return,还是得使用 return@label 来返回到指定层面。 crossinline

inline fun cal(a: Int, b: Int, operate: (Int, Int) -> Int,crossinline result: (Int) -> Unit) {
    var c = operate(a, b)
    println("cal : $a ,$b = $c")
    result(c)
}

fun test(): Int {
    var product = 0
    for (i in 1 until 10) {
        cal(product, i, { a, b ->
            var c = a + b
            println("$a + $b = $c")
            c
        }, {
            if (it > 50) { //如果使用了 inline 修饰 cal 函数会直接跳出 test 函数。因为在编译时已经被内联到这里了,所以 return 就是直接在 test() 函数里 return 了。如果不想这样,可以用 crossinline 修饰这个型参
                println("$it  > 50 了,就在这停了 ")
                return@cal
            }
            product += it
            println("product -> $product")
        })
        println("for -> $i")
    }
    println(" 是不是到了这里,没有到这里,说明直接跳出 test 函数了 product ->$product")
    return product
}

编译后如下,已经很清晰了,return@label 语句被编译为了 if 控制语句,并不会跳出 test() 方法

public static final int test() {
   int product = 0;
   int i = 1;

   for(byte var2 = 10; i < var2; ++i) {
      int $i$f$cal = false;
      int var7 = false;
      int c = product + i;
      String var9 = product + " + " + i + " = " + c;
      boolean var10 = false;
      System.out.println(var9);
      String var6 = "cal : " + product + " ," + i + " = " + c;
      boolean var5 = false;
      System.out.println(var6);
      int var15 = false;
      if (c > 50) {
         var9 = c + "  > 50 了,就在这停了 ";
         var10 = false;
         System.out.println(var9);
      } else {
         product += c;
         var9 = "product -> " + product;
         var10 = false;
         System.out.println(var9);
      }

      String var3 = "for -> " + i;
      $i$f$cal = false;
      System.out.println(var3);
   }

   String var12 = " 是不是到了这里,没有到这里,说明直接跳出 test 函数了 product ->" + product;
   boolean var13 = false;
   System.out.println(var12);
   return product;
}

有时候并不想全部的函数型参都被内联,怎么办呢,noinline 就上场了。

它会告诉编译器,这个参数不能内联。

 inline fun cal(a: Int, b: Int,noinline operate: (Int, Int) -> Int,crossinline result: (Int) -> Unit) {
    var c = operate(a, b)
    println("cal : $a ,$b = $c")
    result(c)
}

调用处代码编译后如下,很清晰的看到又创建了 Function2 对象。这个参数并没有内联。

public static final int test() {
   int product = 0;
   int i = 1;

   for(byte var2 = 10; i < var2; ++i) {
      Function2 operate$iv = (Function2)null.INSTANCE;
      int $i$f$cal = false;
      int c$iv = ((Number)operate$iv.invoke(product, i)).intValue();
      String var7 = "cal : " + product + " ," + i + " = " + c$iv;
      boolean var8 = false;
      System.out.println(var7);
      int var10 = false;
      String var11;
      boolean var12;
      if (c$iv > 50) {
         var11 = c$iv + "  > 50 了,就在这停了 ";
         var12 = false;
         System.out.println(var11);
      } else {
         product += c$iv;
         var11 = "product -> " + product;
         var12 = false;
         System.out.println(var11);
      }

      String var3 = "for -> " + i;
      boolean var15 = false;
      System.out.println(var3);
   }

   String var13 = " 是不是到了这里,没有到这里,说明直接跳出 test 函数了 product ->" + product;
   boolean var14 = false;
   System.out.println(var13);
   return product;
}

最后附上标准库 Functions.kt 的代码,其实每个 lambda 都会被编译成对应的类型,目前默认是最多 22个参数。 我使用的版本 1.4.0

/*
 * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

// Auto-generated file. DO NOT EDIT!

package kotlin.jvm.functions

/** A function that takes 0 arguments. */
public interface Function0<out R> : Function<R> {
    /** Invokes the function. */
    public operator fun invoke(): R
}
/** A function that takes 1 argument. */
public interface Function1<in P1, out R> : Function<R> {
    /** Invokes the function with the specified argument. */
    public operator fun invoke(p1: P1): R
}
/** A function that takes 2 arguments. */
public interface Function2<in P1, in P2, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2): R
}
/** A function that takes 3 arguments. */
public interface Function3<in P1, in P2, in P3, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3): R
}
/** A function that takes 4 arguments. */
public interface Function4<in P1, in P2, in P3, in P4, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4): R
}
/** A function that takes 5 arguments. */
public interface Function5<in P1, in P2, in P3, in P4, in P5, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5): R
}
/** A function that takes 6 arguments. */
public interface Function6<in P1, in P2, in P3, in P4, in P5, in P6, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6): R
}
/** A function that takes 7 arguments. */
public interface Function7<in P1, in P2, in P3, in P4, in P5, in P6, in P7, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7): R
}
/** A function that takes 8 arguments. */
public interface Function8<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8): R
}
/** A function that takes 9 arguments. */
public interface Function9<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9): R
}
/** A function that takes 10 arguments. */
public interface Function10<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10): R
}
/** A function that takes 11 arguments. */
public interface Function11<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11): R
}
/** A function that takes 12 arguments. */
public interface Function12<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12): R
}
/** A function that takes 13 arguments. */
public interface Function13<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13): R
}
/** A function that takes 14 arguments. */
public interface Function14<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14): R
}
/** A function that takes 15 arguments. */
public interface Function15<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15): R
}
/** A function that takes 16 arguments. */
public interface Function16<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16): R
}
/** A function that takes 17 arguments. */
public interface Function17<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17): R
}
/** A function that takes 18 arguments. */
public interface Function18<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18): R
}
/** A function that takes 19 arguments. */
public interface Function19<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19): R
}
/** A function that takes 20 arguments. */
public interface Function20<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20): R
}
/** A function that takes 21 arguments. */
public interface Function21<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21): R
}
/** A function that takes 22 arguments. */
public interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
}