kotlin中高階函數(shù)/Lambda的性能問(wèn)題分析及inline的作用

在Kotlin中,使用高階函數(shù)(函數(shù)/Lambda作為參數(shù)傳遞)時(shí)不良使用會(huì)造成性能問(wèn)題。官方文檔表述如下:

kotlin中每一個(gè)函數(shù)都是一個(gè)對(duì)象,并且會(huì)捕獲一個(gè)閉包。 即那些在函數(shù)體內(nèi)會(huì)訪問(wèn)到的變量。 
內(nèi)存分配(對(duì)于函數(shù)對(duì)象和類)和虛擬調(diào)用會(huì)引入運(yùn)行時(shí)間開(kāi)銷。

那在什么情況下函數(shù)會(huì)捕獲閉包,性能隱患是怎么產(chǎn)生的,又是什么時(shí)候需要使用內(nèi)聯(lián)inline呢?

下面通過(guò)幾個(gè)場(chǎng)景來(lái)分析

1.Lambda不訪問(wèn)外部

    val k = 1
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        testInline {
            Logger.d("qintong", "funn33 $it")
        }
    }

    private fun testInline(func : (i : Int) -> Unit) {
        func(k)
    }

用AndroidStudio自帶工具查看對(duì)應(yīng)的字節(jié)碼,為方便查看直接再講該字節(jié)碼反編譯成java代碼,對(duì)應(yīng)的onCreate()部分:

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.testInline((Function1)null.INSTANCE);
   }

這里為什么是(Function1)null.INSTANCE?stackoverflow一下:
https://stackoverflow.com/questions/53384931/why-kotlin-decompiler-generates-null-instance


kotlin代碼轉(zhuǎn)成字節(jié)碼,字節(jié)碼再轉(zhuǎn)成java代碼可能會(huì)出現(xiàn)錯(cuò)誤,那我們直接分析字節(jié)碼。
對(duì)應(yīng)onCreate()中testInline()方法調(diào)用時(shí)節(jié)碼如下:

    GETSTATIC com/xxx/xxx/xxx/xxx/x/xxxx/XxxActivity$onCreate$1.INSTANCE : Lcom/xxx/xxx/xxx/xxx/xxx/xxx/XxxActivity$onCreate$1;
    CHECKCAST kotlin/jvm/functions/Function1
    INVOKESPECIAL com/xxx/xxx/xxx/xxx/x/xxxx/XxxActivity.testInline (Lkotlin/jvm/functions/Function1;)V
   L3

可見(jiàn)在編譯成字節(jié)碼顯示:

  1. GETSTATIC指令取出的靜態(tài)變量值onCreate$1.INSTANCE,然后推入操作數(shù)棧頂
  2. CHECKCAST 檢查類型
  3. INVOKESPECIAL 調(diào)用testInline(),彈出棧頂參數(shù)onCreate$1.INSTANCE,傳入testInline()

由此可見(jiàn)編譯成字節(jié)碼后對(duì)應(yīng)傳入testInline()方法的Lambda以Lkotlin/jvm/functions/Function1類型的靜態(tài)對(duì)象onCreate$1.INSTANCE存在。假如onCreate()被反復(fù)調(diào)用,由于Lambda對(duì)應(yīng)的方法對(duì)象為靜態(tài)對(duì)象,應(yīng)該不存在明顯的性能問(wèn)題。

接下來(lái)測(cè)試testInline()加上inline后:
用同樣方法,kotlin > 字節(jié)碼 > java代碼:

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      int $i$f$testInline = false;
      int it = this.getK();
      int var5 = false;
      Logger.d("qintong", "funn33 " + it);
   }

由此可見(jiàn)testInline()已平鋪到onCreate()中,不生成內(nèi)部對(duì)象了。

2.Lambda訪問(wèn)外部類的成員方法

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       val x = 1
        testInline{
            log(it)
        }
    }

   fun log(it: Int) {
        Logger.d("qintong", "funn22 $it")
    }
     private fun testInline(func : (i : Int) -> Unit) {
        func(k)
    }

同樣方法得到對(duì)應(yīng)java代碼:

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      int x = true;
      this.testInline((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) {
            VersionActivity.this.log(it);
         }
      }));
   }

可見(jiàn)Lambda以匿名內(nèi)部類的形式傳入testInline()方法中。每次testInline()調(diào)用都會(huì)new一個(gè)Function1對(duì)象。此時(shí)由于Lambda引用了外部對(duì)象的方法,導(dǎo)致其編譯后難以像第一個(gè)例子中優(yōu)化成一個(gè)靜態(tài)內(nèi)部對(duì)象。
加入onCreate()方法被循環(huán)調(diào)用,每次調(diào)用都會(huì)new出一個(gè)Function1對(duì)象,這會(huì)造成一些性能問(wèn)題:不斷創(chuàng)建對(duì)象會(huì)造成內(nèi)存抖動(dòng),增加gc負(fù)擔(dān),頻繁gc也會(huì)造成卡頓。所以此時(shí)需要將函數(shù)內(nèi)聯(lián):
testInline()加上inline后:

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      int x = true;
      Function1 funxx = (Function1)null.INSTANCE;
      int $i$f$testInline = false;
      int it = this.getK();
      int var7 = false;
      int $i$f$log = false;
      Logger.d("qintong", "funn22 " + it);
   }

和第一個(gè)例子一樣,內(nèi)聯(lián)后testInlint()方法平鋪到了onCreate()中,不存在性能問(wèn)題了。

3.Lambda內(nèi)訪問(wèn)外部的變量

和上面的例子一樣,我們對(duì)下面進(jìn)行測(cè)試:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       val x = 1
        testInline{
            Logger.d("qintong", "funn11 $it + $x")
        }
    }

    private fun testInline(func : (i : Int) -> Unit) {
        func(k)
    }

對(duì)應(yīng)java代碼:

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      final int x = 1;
      this.testInline((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) {
            Logger.d("qintong", "funn11 " + it + " + " + x);
         }
      }));
   }

和第二個(gè)例子一樣,也是會(huì)在每次調(diào)用時(shí)new出對(duì)象傳入testInline()中,同樣有性能問(wèn)題。
加inline后,結(jié)果和前面兩個(gè)例子一樣,就不贅述了。

結(jié)論

  1. 從實(shí)質(zhì)上,kotlin中使用高階函數(shù)時(shí)每一個(gè)函數(shù)都對(duì)應(yīng)個(gè)對(duì)象傳遞給調(diào)用方。
  2. 使用高階函數(shù)時(shí)當(dāng)函數(shù)/Lambda不訪問(wèn)外部的變量/方法(即不捕獲外部)時(shí),編譯器會(huì)將函數(shù)對(duì)應(yīng)的對(duì)象優(yōu)化成類的靜態(tài)成員變量,反復(fù)調(diào)用時(shí)不會(huì)有性能問(wèn)題。此時(shí)也不需要使用inline。
  3. 當(dāng)函數(shù)/Lambda捕獲外部時(shí),比如訪問(wèn)閉包內(nèi)的參數(shù)、訪問(wèn)外部方法時(shí),閉包會(huì)一new 內(nèi)部類對(duì)象的方式進(jìn)行傳遞,此時(shí)如果方法被頻繁調(diào)用(如在循環(huán)中被調(diào)用)會(huì)造成性能問(wèn)題:對(duì)象被持續(xù)創(chuàng)建,造成內(nèi)存抖動(dòng),增加gc負(fù)擔(dān),頻繁gc也可能造成卡頓。
  4. 使用inline,方法會(huì)被平鋪到調(diào)用處,不存在上面說(shuō)的性能問(wèn)題。
  5. inline的使用不當(dāng)也會(huì)有負(fù)面作用:由于inline是將函數(shù)平鋪到調(diào)用處,所以要避免內(nèi)聯(lián)函數(shù)過(guò)大。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 寫在開(kāi)頭:本人打算開(kāi)始寫一個(gè)Kotlin系列的教程,一是使自己記憶和理解的更加深刻,二是可以分享給同樣想學(xué)習(xí)Kot...
    胡奚冰閱讀 1,379評(píng)論 1 5
  • 本文是在學(xué)習(xí)和使用kotlin時(shí)的一些總結(jié)與體會(huì),一些代碼示例來(lái)自于網(wǎng)絡(luò)或Kotlin官方文檔,持續(xù)更新... 對(duì)...
    竹塵居士閱讀 3,461評(píng)論 0 8
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,621評(píng)論 1 32
  • Inline Basics Inline or Inlining,我們更經(jīng)常聽(tīng)到的詞是方法內(nèi)聯(lián)或者內(nèi)聯(lián)函數(shù)。在大多...
    wusp閱讀 1,525評(píng)論 2 4
  • 過(guò)去總算漸漸都還過(guò)得去 ,未來(lái)就等來(lái)了再?zèng)Q定 ,回憶多少還留一點(diǎn)點(diǎn)余地 ,還不至于回不去,誰(shuí)的青春沒(méi)有淺淺的瘀...
    浮塘蘆葦閱讀 292評(píng)論 2 1

友情鏈接更多精彩內(nèi)容