Kotlin的crossinline和noinline有什么区别?

时间:2016-08-08 10:45:38

标签: kotlin

  1. 此代码编译时出现警告无关紧要的效果影响):

    inline fun test(noinline f: () -> Unit) {
        thread(block = f)
    }
    
  2. 此代码无法编译非法使用内联参数):

    inline fun test(crossinline f: () -> Unit) {
        thread(block = f)
    }
    
  3. 此代码编译时出现警告无关紧要的效果影响):

    inline fun test(noinline f: () -> Unit) {
        thread { f() }
    }
    
  4. 此代码编译时没有任何警告或错误

    inline fun test(crossinline f: () -> Unit) {
        thread { f() }
    }
    
  5. 以下是我的问题:

    • 怎么来(2)不编译而(4)呢?
    • noinlinecrossinline之间究竟有什么区别?
    • 如果(3)没有产生无性能改善,为什么(4)会这样做?

4 个答案:

答案 0 :(得分:31)

来自inline functions reference

  

请注意,某些内联函数可能会将传递给它们的lambda作为参数调用,而不是直接来自函数体,而是来自另一个执行上下文,例如本地对象或嵌套函数。在这种情况下,lambda中也不允许非本地控制流。为了表明这一点,lambda参数需要用crossinline修饰符

标记

因此,示例2.不编译,因为crossinline仅强制执行本地控制流,而block = f表达式违反了该表达式。示例1编译,因为noinline不需要这样的行为(显然,因为它是一个普通的函数参数)。

示例1和3不会产生任何性能改进,因为唯一的lambda参数标记为noinline,导致函数的inline修饰符无用且冗余 - 编译器想要内联,但是所有可能的内容都被标记为不被内联。

考虑两个功能, A B

A

inline fun test(noinline f: () -> Unit) {
    thread { f() }
}

fun test(f: () -> Unit) {
    thread { f() }
}

函数 A 的行为类似于函数 B ,因为参数f不会被内联( B 函数不会内联test的正文,而在 A 函数中,正文:thread { f() }仍然内联。)

现在,在示例4中并非如此,因为crossinline f: () -> Unit参数可以内联,它不能违反上述非本地控制流规则(如分配新值到全局变量)。如果可以内联,则编译器会假设性能得到改进,并且不会像示例3中那样发出警告。

答案 1 :(得分:7)

  

Q1:为什么(2)不编译但是(4)呢?

来自他们的文件:

  

Inlinable lambdas只能在内联函数内部调用或作为无限参数传递...

答案:

方法thread(...)不是inline方法,因此您无法将f作为参数传递。

  

Q2:noinline和crossinline之间究竟有什么区别?

答案:

noinline将阻止lambdas的内联。当你有多个lambda参数并且你只希望传递给内联函数的一些lambda被内联时,这就变得很有用了。

crossinline用于标记不允许非本地返回的lambda,特别是当这样的lambda传递给另一个执行上下文时。换句话说,你不能在这样的lambda中使用return。使用您的示例:

inline fun test(crossinline f: () -> Unit) {
    thread { f() }
}

//another method in the class
fun foo() {

    test{ 

       //Error! return is not allowed here.
       return
    }

}
  

问题3:如果(3)没有产生无性能改善,为什么(4)会这样做?

答案:

那是因为你在(3)中唯一的lambda标有noinline,这意味着你需要花费很少的成本来创建Function对象以容纳你的身体LAMDA。对于(4),lambda仍然内联(性能改进),只是它不允许非本地返回。

答案 2 :(得分:7)

让我向您展示每个示例命令编译器执行的操作,而不是冗长的解释。让我们首先编写一些使用您的函数的代码:

fun main(args: Array<String>) {
    test { 
        println("start")
        println("stop")
    }
}

现在让我们看一下您的变体。我将从您的示例test1 .. test4中调用这些函数,并以伪代码显示以上main函数将被编译成什么。

1。 noinlineblock = f

inline fun test1(noinline f: () -> Unit) {
    thread(block = f)
}

fun compiledMain1() {
    val myBlock = {
        println("start")
        println("stop")
    }
    thread(block = myBlock)
}

首先,请注意甚至没有inline fun test1的证据。内联函数并没有真正被“调用”:好像test1的代码是写在main()内的。另一方面,noinline lambda参数的行为与没有内联的行为相同:创建一个lambda对象并将其传递给thread函数。

2。 crossinlineblock = f

inline fun test2(crossinline f: () -> Unit) {
    thread(block = f)
}

fun compiledMain2() {
    thread(block =
        println("start")
        println("stop")
    )
}

我希望我能想到这里发生的事情:您请求将块的代码复制粘贴到需要值的位置。这只是语法垃圾。原因:无论是否使用crossinline,您都要求将块复制粘贴到使用的位置。此修饰符仅限制您可以在块中写入的内容(无return等)

3。 noinline{ f() }

inline fun test3(noinline f: () -> Unit) {
    thread { f() }
}

fun compiledMain3() {
    val myBlock = {
        println("start")
        println("stop")
    }
    thread { myBlock() }
}

我们在这里回到noinline,因此事情再简单不过了。创建一个常规的lambda对象myBlock,然后创建另一个委托给它的常规lambda对象:{ myBlock() },然后将其传递给thread()

4。 crossinline{ f() }

inline fun test4(crossinline f: () -> Unit) {
    thread { f() }
}

fun compiledMain4() {
    thread {
        println("start")
        println("stop")
    }
}

最后,该示例演示了crossinline的用途。 test4的代码内联到main中,块的代码内联到使用它的地方。但是,由于它是在常规lambda对象的定义中使用的,因此它不能包含非本地控制流。

关于性能影响

Kotlin团队希望您明智地使用内联功能。内联后,已编译代码的大小会急剧爆炸,甚至达到JVM限制(每个方法最多64K字节码指令)。主要用例是高阶函数,这些函数避免了创建实际的lambda对象的开销,而只是在立即发生单个函数调用之后立即将其丢弃。

每当您声明没有内联lambda的inline fun时,内联本身就会失去其目的。编译器会警告您。

答案 3 :(得分:1)

第一和第二个问题

  

怎么来(2)不编译但(4)呢?.. noinlinecrossinline之间的差异

2. inline fun test(crossinline f: () -> Unit) {
    thread(block = f)
}

4. inline fun test(crossinline f: () -> Unit) {
    thread { f() }
}

两种情况都有inline修饰符指示内联函数test及其参数lambda f。来自kotlin参考:

  

内联修饰符会影响函数本身和lambdas   传递给它:所有这些都将被内联到呼叫站点。

因此指示编译器放置代码(内联),而不是构造和调用f的函数对象。 crossinline修饰符仅用于内联事物:它只是说传递的lambda(在f参数中)不应该具有非本地返回(“正常”内联lambda可能具有)。 crossinline可以被认为是这样的东西(对编译器的指令):“内联但是有一个限制,它正在越过调用者上下文,所以确保lambda没有非本地返回。< / p>

在旁注中,thread似乎是crossinline的概念说明性示例,因为显然从某些代码(稍后在f中传递)返回不同的线程可能不会影响返回来自test,它继续在调用者线程上独立于它生成的内容执行(f继续独立执行)..

在#4的情况下,有一个lambda(花括号)调用f()。在#2的情况下,f作为参数直接传递给thread

因此在#4中,可以内联调用f(),编译器可以保证不存在非本地返回。详细说明,编译器会用它的定义替换f(),然后代码被“包装”在封闭的lambda中,换句话说,{ //code for f() }是另一种(包装器)lambda,它本身就是进一步的作为函数对象引用传递给thread)。

在#2的情况下,编译器错误只是说它不能内联f,因为它作为引用传递到“未知”(非内联)位置。 crossinline在这种情况下变得不合适并且无关紧要,因为只有在f内联时才能应用它。

总而言之,通过与kotlin引用中的示例(参见“高阶函数和Lambdas”)进行比较,情况2和4不同:下面的调用是等效的,其中花括号(lambda表达式)“替换“包装函数toBeSynchronized

//want to pass `sharedResource.operation()` to lock body
fun <T> lock(lock: Lock, body: () -> T): T {...}
//pass a function
fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized) 
//or pass a lambda expression
val result = lock(lock, { sharedResource.operation() })

问题中的案例#2和#4不等同,因为在#2中没有“包装”调用f