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

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

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

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

下面通過幾個場景來分析

1.Lambda不訪問外部

    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自帶工具查看對應的字節(jié)碼,為方便查看直接再講該字節(jié)碼反編譯成java代碼累驮,對應的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代碼可能會出現(xiàn)錯誤谤专,那我們直接分析字節(jié)碼躁锡。
對應onCreate()中testInline()方法調(diào)用時節(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é)碼顯示:

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

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

接下來測試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);
   }

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

2.Lambda訪問外部類的成員方法

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

同樣方法得到對應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);
         }
      }));
   }

可見Lambda以匿名內(nèi)部類的形式傳入testInline()方法中据忘。每次testInline()調(diào)用都會new一個Function1對象鹦牛。此時由于Lambda引用了外部對象的方法,導致其編譯后難以像第一個例子中優(yōu)化成一個靜態(tài)內(nèi)部對象勇吊。
加入onCreate()方法被循環(huán)調(diào)用曼追,每次調(diào)用都會new出一個Function1對象,這會造成一些性能問題:不斷創(chuàng)建對象會造成內(nèi)存抖動汉规,增加gc負擔礼殊,頻繁gc也會造成卡頓。所以此時需要將函數(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);
   }

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

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

和上面的例子一樣,我們對下面進行測試:

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

對應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);
         }
      }));
   }

和第二個例子一樣坝辫,也是會在每次調(diào)用時new出對象傳入testInline()中篷就,同樣有性能問題。
加inline后近忙,結(jié)果和前面兩個例子一樣竭业,就不贅述了。

結(jié)論

  1. 從實質(zhì)上及舍,kotlin中使用高階函數(shù)時每一個函數(shù)都對應個對象傳遞給調(diào)用方未辆。
  2. 使用高階函數(shù)時當函數(shù)/Lambda不訪問外部的變量/方法(即不捕獲外部)時,編譯器會將函數(shù)對應的對象優(yōu)化成類的靜態(tài)成員變量锯玛,反復調(diào)用時不會有性能問題咐柜。此時也不需要使用inline。
  3. 當函數(shù)/Lambda捕獲外部時攘残,比如訪問閉包內(nèi)的參數(shù)拙友、訪問外部方法時,閉包會一new 內(nèi)部類對象的方式進行傳遞歼郭,此時如果方法被頻繁調(diào)用(如在循環(huán)中被調(diào)用)會造成性能問題:對象被持續(xù)創(chuàng)建遗契,造成內(nèi)存抖動,增加gc負擔病曾,頻繁gc也可能造成卡頓牍蜂。
  4. 使用inline,方法會被平鋪到調(diào)用處泰涂,不存在上面說的性能問題鲫竞。
  5. inline的使用不當也會有負面作用:由于inline是將函數(shù)平鋪到調(diào)用處,所以要避免內(nèi)聯(lián)函數(shù)過大逼蒙。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末从绘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子是牢,更是在濱河造成了極大的恐慌顶考,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妖泄,死亡現(xiàn)場離奇詭異,居然都是意外死亡艘策,警方通過查閱死者的電腦和手機蹈胡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人罚渐,你說我怎么就攤上這事却汉。” “怎么了荷并?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵合砂,是天一觀的道長。 經(jīng)常有香客問我源织,道長翩伪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任谈息,我火速辦了婚禮缘屹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘侠仇。我一直安慰自己轻姿,他們只是感情好,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布逻炊。 她就那樣靜靜地躺著互亮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪余素。 梳的紋絲不亂的頭發(fā)上豹休,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音溺森,去河邊找鬼慕爬。 笑死,一個胖子當著我的面吹牛屏积,可吹牛的內(nèi)容都是我干的医窿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼炊林,長吁一口氣:“原來是場噩夢啊……” “哼姥卢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起渣聚,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤独榴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后奕枝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棺榔,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年隘道,在試婚紗的時候發(fā)現(xiàn)自己被綠了症歇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郎笆。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖忘晤,靈堂內(nèi)的尸體忽然破棺而出宛蚓,到底是詐尸還是另有隱情,我是刑警寧澤设塔,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布凄吏,位于F島的核電站,受9級特大地震影響闰蛔,放射性物質(zhì)發(fā)生泄漏痕钢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一钞护、第九天 我趴在偏房一處隱蔽的房頂上張望盖喷。 院中可真熱鬧,春花似錦难咕、人聲如沸课梳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽暮刃。三九已至,卻和暖如春爆土,著一層夾襖步出監(jiān)牢的瞬間椭懊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工步势, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留氧猬,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓坏瘩,卻偏偏與公主長得像盅抚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子倔矾,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容

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