Kotlin進階知識(七)——內聯函數:消除lambda帶來的運行時開銷

引用:使用**inline**修飾符標記一個函數,在函數被使用的時候編譯器並不會生成函數調用的代碼markdown

1、內聯函數如何運行

當一個函數被聲明爲inline時,它的函數體是內聯的——換句話說,函數體會被直接替換到函數被調用的地方,而不是被正常調用。函數

inline fun <T> synchronized(lock: Lock, action: () -> T): T {
    lock.lock()
    try {
        return action()
    }
    finally {
        lock.unlock()
    }
}

fun foo(l: Lock) {
    println("Before sync")

    synchronized(l) {
        println("Action")
    }

    println("After sync")
}
複製代碼

上述代碼在轉換成Java代碼時,inline修飾的方法會自動添加到對應的方法中性能

public static final void foo(@NotNull Lock l) {
      String var1 = "Before sync";
      System.out.println(var1);
      l.lock();

      try {
         String var3 = "Action";
         System.out.println(var3);
         Unit var8 = Unit.INSTANCE;
      } finally {
         l.unlock();
      }

      var1 = "After sync";
      System.out.println(var1);
   }
複製代碼

注意:lambda表達式和synchronized函數的實現都被內聯了。由lambda生成的字節碼稱爲了函數調用者定義的一部分,而不是被包含在一個實現了函數接口的匿名類中。ui

在調用內聯函數的時候也能夠傳遞函數類型的變量做爲參數:spa

class LockOwner(val lock: Lock) {
    fun runUnderLock(body: () -> Unit) {
        // 傳遞一個函數類型的變量做爲參數,而不是一個lambda
        synchronized(lock, body)
    }
}
複製代碼

這種狀況下,lambda的代碼在內聯函數被調用點是不可用的,所以並不會被內聯。 只有synchronized的函數體別內聯了,lambda纔會被正常調用。code

若是在兩個不一樣的位置使用同一個內聯函數,可是用的是不一樣的lambda,那麼內聯函數會在每個被調用的位置被分別內聯。orm

內聯函數的代碼會被拷貝到使用它的兩個不一樣爲孩子,並把不一樣的lambda替換到其中。對象

2、內聯集合操做

大部分標準庫中的集合函數都帶有lambda參數。相比於使用標準庫函數,直接實現這些操做不是更高效嗎?接口

  • 使用lambda過濾一個集合
data class PersonNew(val name: String = "", val age: Int = 0)

val people = listOf(PersonNew("Alice", 29), PersonNew("Bob", 31))

fun filterSetByLambdaTest() {
    println(people.filter { it.age < 30 })
}

// 輸出結果
[Person(name=Alice, age=29)]
複製代碼
  • 手動過濾一個集合
fun filterSetByManualTest() {
    val result = mutableListOf<PersonNew>()
    for (person in people) {
        if (person.age < 30) result.add(person)
    }
    println(result)
}
複製代碼

在Kotlin中,filter函數被聲明爲內聯函數。這意味着filter函數,以及傳遞給它的lambda的字節碼會被一塊兒關聯到filter被調用的地方。最終,第一種實現所產生的字節碼和第二種實現所產生的字節碼大體同樣的。編譯器

於是Kotlin對內聯函數的支持讓你沒必要擔憂性能問題

注意: 一、有大量元素須要處理,中間集合的運行開銷將成爲不可忽視的問題,於是使用asSequence調用,用序列來替代集合。 二、只在處理大量數據的集合時序列有用,小集合能夠用普通的集合操做處理便可。

3、決定什麼時候將函數聲明成內聯

使用inline關鍵字只能提升帶有lambda參數的函數的性能,其餘狀況須要額外的度量和研究。

對於普通的函數調用JVM已經提升了強大的內聯支持。它會分析代碼的執行,並在任何經過內聯可以帶來好處的時候將函數調用內聯。

lambda參數的函數內聯能帶來的好處

  • 經過內聯避免的運行時開銷更下明顯了。不只節約了函數調用的開銷,並且節約了爲lambda建立匿名類,以及建立lambda實例對象的開銷。
  • JVM目前並無老是將函數調用內聯。
  • 內聯可使用一些不可能被普通lambda使用的特性,好比非局部返回。
相關文章
相關標籤/搜索