Kotlin的crossinline和noinline有什么区别?

Sal*_*RYS 55 kotlin

  1. 此代码编译时出现警告(对性能影响不显着):

    inline fun test(noinline f: () -> Unit) {
        thread(block = f)
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 此代码无法编译(非法使用inline-parameter):

    inline fun test(crossinline f: () -> Unit) {
        thread(block = f)
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 此代码编译时出现警告(对性能影响不显着):

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

    inline fun test(crossinline f: () -> Unit) {
        thread { f() }
    }
    
    Run Code Online (Sandbox Code Playgroud)

这是我的问题:

  • 怎么会(2)不编译,但(4)呢?
  • noinline和之间究竟有什么区别crossinline
  • 如果(3)没有产生没有性能改进,为什么(4)会这样做?

mac*_*usz 37

内联函数参考:

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

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

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

考虑两个函数,AB.

一个

inline fun test(noinline f: () -> Unit) {
    thread { f() }
}
Run Code Online (Sandbox Code Playgroud)

fun test(f: () -> Unit) {
    thread { f() }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • `inline fun test(noinline f)`将内联函数`test`的主体,但不会内联`f`.所以上面的片段不完全相同.请更正 (4认同)
  • 如果我理解正确的话:crossinline 会将 lambda 内联到它被调用的地方(但会阻止非本地控制流),而 noinline 根本不会内联它(因此将 lambda 封装到 Function 对象中)。 (2认同)
  • @ YaiYu-Hsuan在“ block = f”表达式中,“ block”变量不是“ test”函数的局部变量,它存在于方法范围之外,因此是“非局部控制流”。坦白地说,这只是意味着“访问非本地值”。 (2认同)

Mar*_*nik 11

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

fun main(args: Array<String>) {
    test { 
        println("start")
        println("stop")
    }
}
Run Code Online (Sandbox Code Playgroud)

现在让我们来看看你的变种.

1. test1,test4

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

fun compiledMain1() {
    val myBlock = {
        println("start")
        println("stop")
    }
    thread(block = myBlock)
}
Run Code Online (Sandbox Code Playgroud)

首先,请注意,没有证据证明main存在.内联函数并没有真正"被调用":就好像代码noinline是在里面写的block = f.另一方面,inline fun test1lambda参数的行为与没有内联的行为相同:创建一个lambda对象并将其传递给test1函数.

2. main(),noinline

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

fun compiledMain2() {
    thread(block =
        println("start")
        println("stop")
    )
}
Run Code Online (Sandbox Code Playgroud)

我希望我能想到这里发生的事情:你要求将块的代码复制粘贴到一个需要值的地方.这只是语法垃圾.原因:无论thread是否请求将块复制粘贴到使用它的位置.这个修饰符只限制你在块内写的东西(没有crossinline等等)

3. block = f,crossinline

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

fun compiledMain3() {
    val myBlock = {
        println("start")
        println("stop")
    }
    thread { myBlock() }
}
Run Code Online (Sandbox Code Playgroud)

我们回到return这里,所以事情再次简单明了.您创建一个常规的lambda对象noinline,然后创建另一个委托给它的常规lambda对象:{ f() }然后将其传递给noinline.

4. myBlock,{ myBlock() }

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

fun compiledMain4() {
    thread {
        println("start")
        println("stop")
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,这个例子演示了什么thread().的代码crossinline被内联到{ f() },块的代码被内联到使用它的地方.但是,由于它在常规lambda对象的定义中使用,因此它不能包含非本地控制流.

关于绩效影响

Kotlin团队希望您明智地使用内联功能.通过内联,编译代码的大小可以显着爆炸,甚至可以达到每个方法高达64K字节码指令的JVM限制.主要用例是高阶函数,它避免了创建实际lambda对象的成本,只是在一次立即发生的单个函数调用之后立即丢弃它.

每当你声明一个crossinline没有内联lambda时,内联本身就失去了它的目的.编译器警告你.


Ben*_*iel 8

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

从他们的文件:

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

回答:

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

Q2:noinline和crossinline有什么区别?

回答:

noinline将阻止lambdas的内联.当您有多个lambda参数并且只希望传递给内联函数的一些lambda内联时,这会很有用.

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

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

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

    test{ 

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

}
Run Code Online (Sandbox Code Playgroud)

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

回答:

那是因为你在(3)中唯一的lambda已被标记,noinline这意味着你将有创建Function对象来承担你的lamda体的开销.对于(4),lambda仍然内联(性能改进),只是它不允许非本地返回.