Kotlin 中的 inline ,noinline 和 crossinline

2021-03-02 佛系編碼

佛系編碼  ·  寫點編碼的日常

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 來返回到指定層面。

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;
}

其實每個 lambda 都會被編譯成對應的 Function 類型,目前默認是最多 22個參數。我使用的版本 1.4.0 。

點擊 閱讀原文 拉到最下面可看到 Functions.kt 的源碼


相關焦點

  • Kotlin最佳實踐:在高階函數中使用inline - 碼農登陸
    前言最近,無意中看到一篇文章,是聊inline在高階函數中的性能提升,說實話之前沒有認真關注過這個特性,所以藉此機會好好學習了一番。高階函數:入參中含有lambda的函數(方法)。>if (predicate(element))destination.add(element)return destination}既然官方標準庫中如果使用,我們則需要驗證一下inline是不是真能有更好的性能:inlinefunrepeat
  • 新手入門:關於C++中的內聯函數(inline)
    正在閱讀:新手入門:關於C++中的內聯函數(inline)新手入門:關於C++中的內聯函數(inline)2005-03-01 10:10出處:PConline作者:管寧>   在c++中,為了解決一些頻繁調用的小函數大量消耗棧空間或者是叫棧內存的問題,特別的引入了inline修飾符,表示為內聯函數。
  • CSS中block級和inline級對象區別
    CSS中block級和inline級對象區別 你對CSS中block級和inline級對象的區別是否了解,Block級對象會自然地水平充滿其父容器,而Inline級對象會忽略其寬度和高度設置。
  • C++中inline, extern, static潛在的陷阱
    相信inline, extern, static這三個關鍵字對於C++程式設計師是非常熟悉的,但有些時候,其中隱藏的陷阱,可能會給你的程序帶來一些很難診斷的問題。1. inline我們先聚焦於inline函數(內聯函數)。inline可以與名稱空間一起使用,但這種用法並不常見。
  • Kotlin入門教程,快使用Kotlin吧
    有通配符和邊界的概念比如Class,表示上界通配符,它代表T以及T的子類,上限是T;在kotlin中可以使用out來替代例如clazz: Class<out T>in:同樣也有下屆通配符比如,它表示T以及T的超類,下限是T;在kotlin中可以使用in來代替例如clazz: Class<in T>靜態類和靜態方法 object(全局):使用object
  • CSS中flex和inline-flex的區別
    兩者的區別描述:flex: 將對象作為彈性伸縮盒顯示inline-flex:將對象作為內聯塊級彈性伸縮盒顯示一句話來描述就是 當Flex Box 容器沒有設置寬度大小限制時,當display 指定為 flex 時,FlexBox 的寬度會填充父容器,當display指定為 inline-flex
  • Kotlin 高階函數、內聯
    在 println 加了個 return ,這樣就會導致下面的列印 「運行完成2」和「結束」都無法運行,這很明顯不是我們想要的,解決方法也很簡單,我們在 return 處加個 @label 不過 Kotlin 也有另一種方式來限制在 lambda 中直接 return,那就是使用 noinline 或 crossinline 。
  • C語言陷阱與技巧第2節,使用inline函數可以提升程序效率,但是讓...
    inline 關鍵字的作用在C語言程序開發中,inline 一般用於定義函數,inline 函數也被稱作「內聯函數」,C99 和 GNU C 均支持內聯函數。那麼在C語言中,內聯函數和普通函數有什麼不同呢?
  • 關於CSS屬性display:inline-block的深入理解
    但很遺憾,最流行的 IE 和 Firefox 卻不支持這個屬性(在 Firefox3 版本中將會支持 display:inline-block)。不過 Firefox 下卻有私有屬性 -moz-inline-box 和inline-block 形似,為什麼是「形似」而不是「神似」呢?
  • Kotlin Vocabulary | 內聯類 inline class
    inline class DoggoId(val id: Long)data class Doggo(val id: DoggoId, … ) val goodDoggo = Doggo(DoggoId(doggoId), …)fun pet(id: DoggoId) { … }內聯類的唯一作用是成為某種類型的包裝,因此 Kotlin
  • 去除inline-block元素間間距的N種方法
    二、方法之移除空格元素間留白間距出現的原因就是標籤段之間的空格,因此,去掉HTML中的空格,自然間距就木有了。三、使用margin負值.space a { display: inline-block; margin-right: -3px;}margin負值的大小與上下文的字體和文字大小相關,其中,間距對應大小值可以參見我之前「基於display:inline-block的列表布局」一文part 6的統計表格:
  • 當li設置為inline-block之後元素之間空隙的產生原因和解決辦法
    這不是li的問題,而是display:inline-block本身的問題。display:inline-block是一種布局方法,它相比於與浮動、定位最大的不同就是其沒有父元素的匿名包裹特性,這使得display:inline-block屬性的使用非常自由,可與文字,圖片混排,既可內嵌block屬性元素,也可以置身於inline水平的元素中。
  • 張鑫旭說:去除inline-block元素間間距的N種方法
    元素間留白間距出現的原因就是標籤段之間的空格,因此,去掉HTML中的空格,自然間距就木有了。.space a { display: inline-block; margin-right: -3px;}margin負值的大小與上下文的字體和文字大小相關,其中,間距對應大小值可以參見我之前「基於display:inline-block的列表布局」一文part 6的統計表格:
  • C語言關鍵字 inline 講解?一定要防止踩到這幾個屎坑!
    這時候我們就可以使用inline關鍵字,來將這個抹掉,提升效率。這裡說一個重點,inline是修飾實現體的,聲明是沒啥意義。比如inline int add(int a,int b);  這個修飾了,但是實現地方沒有,那麼就是無效的,具體就是如下:add.c這個就是沒有用的,還是默認的方式,只有改成:
  • 為數不多的人知道的 Kotlin 技巧及解析(一)
    文章中沒有奇淫技巧,都是一些在實際開發中常用,但很容易被我們忽略的一些常見問題,源於平時的總結,這篇文章主要對這些常見問題進行分析。這篇文章主要分析一些常見問題的解決方案,如果使用不當會對 性能 和 內存 造成的那些影響以及如何規避這些問題,文章中涉及的案例來自 Kotlin 官方、Stackoverflow、Medium 等等網站。
  • css文字塊-display行內元素塊 inline-block 只給文字加背景
    gt;<style> body{ padding: 500px;}.mian{ font-size: 30px;/* 文字大小 */ background-color: #FF0000;/* 背景顏色 */ }.mian_uu{ font-size: 30px;/* 文字大小 */ background-color: #337AB7;/* 背景顏色 */ display: inline-block
  • 程式語言 Kotlin 1.4 將推新的編譯器:今年春季發布
    它解析代碼和命名、執行類型檢查等。此編譯器的這一部分也可以在 IDE 中使用,來高亮顯示語法錯誤、導航到定義並搜索項目中的符號用法。這是 kotlinc 如今花費最多時間的步驟,因此開發團隊希望使其更快。當前的實現尚未完成,並且不會在 1.4 中到來。但是,大多耗時的工作都是由它完成,因此我們可以預期提速的效果。
  • python-docx設置圖片大小和對齊方式
    在WORD軟體中,圖片大小一般可以採用點擊圖片,在「格式」菜單中「大小」裡設置圖片的高度和寬度,也可以通過滑鼠右鍵菜單「大小」菜單調出設置圖片大小的界面,見下圖。而在python-docx包中主要使用inline_shape對象的height和width屬性設置,筆記將分圖像大小設置和圖片對齊方式設置等2個方面進行敘述並製作了思維導圖。
  • 「cross the line」是什麼意思?
    cross the line:衝刺;穿過那條線, 過線,做得太過分了。She definitely cross the line.她的確過分了一點。We shouldn't cross the line anddo bad things.我們不應該越界做壞事。Who do you think you are?
  • 2月第25題:inline-block 元素間間距是怎麼產生的?該如何去除呢?
    <div class="space"> <a href="##">惆悵</a> <a href="##">淡定</a> <a href="##">熱血</a></div>.space a { display: inline-block