在Kotlin中,crossinline和noinline有什么区别?

5us2dqdw  于 2023-03-19  发布在  Kotlin
关注(0)|答案(4)|浏览(234)

1.此代码编译时出现警告(* 对性能的影响微不足道 *):

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

1.此代码未编译(* 非法使用内联参数 *):

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

1.此代码编译时出现警告(* 对性能的影响微不足道 *):

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

1.此代码编译时没有警告或错误

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

以下是我的问题:

  • 为什么(2)不能编译,而(4)可以?
  • noinlinecrossinline之间到底有什么区别?
  • 如果(3)没有产生性能改进,为什么(4)会产生性能改进?
z18hc3ub

z18hc3ub1#

inline functions reference
请注意,一些内联函数可能不直接从函数体调用作为参数传递给它们的lambda,而是从另一个执行上下文,如本地对象或嵌套函数调用。在这种情况下,lambda中也不允许非本地控制流。为了指示这一点,lambda参数需要用交叉修饰符标记
因此,示例2.没有编译,因为crossinline只强制执行局部控制流,而表达式block = f违反了这一点。示例1编译,因为noinline不需要这样的行为(显然,因为它是一个普通的函数参数)。
示例1和3没有产生任何性能改进,因为唯一的lambda参数被标记为noinline,使得函数的inline修饰符无用且多余-编译器想要内联一些东西,但所有可以内联的东西都被标记为不内联。
考虑两个函数AB

A

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

B

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

函数A的行为类似于函数B,因为参数f不会被内联(B函数不会内联test的主体,而在A函数中,主体:thread { f() }仍然被内联)。
但是,在示例4中,情况并非如此,因为crossinline f: () -> Unit参数**可以内联,所以它不能违反前面提到的非本地控制流规则(比如给全局变量赋值),如果可以内联,编译器就认为性能得到了提高,并且不会像示例3那样发出警告。

jtjikinw

jtjikinw2#

让我试着用例子来解释这一点:我将逐一介绍您的每个示例,并描述它命令编译器执行的操作。首先,下面是一些使用您的函数的代码:

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

现在让我们来看看你的变体,我将调用例子中的函数test1..test4,我将用伪代码展示上面的main函数编译成什么。

1.一米三英寸一英寸,一米四英寸一英寸

  • test1()编译时出现警告(对性能的影响微不足道)*
inline fun test1(noinline f: () -> Unit) {
    thread(block = f)
}

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

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

2.一米十二分一秒,一米十三分一秒

  • test2()未编译(非法使用内联参数)*
inline fun test2(crossinline f: () -> Unit) {
    thread(block = f)
}

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

我希望我能想象出这里发生了什么:您请求将块的代码复制粘贴到需要值的位置。这只是语法垃圾。原因:不管有没有crossinline,你都要求将块复制粘贴到它被使用的地方。这个修饰符只是限制你可以在块中写入什么(没有return等)。

3. x一米十七英寸x一米十八英寸

  • test3()编译时出现警告(性能影响微不足道)*
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.一米二十四英寸,一米二十五英寸

  • test4()编译时没有警告或错误 *
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时,内联本身就失去了作用,编译器会警告你。

vfhzx4xs

vfhzx4xs3#

Q1:为什么(2)不能编译,而(4)可以?
来自他们的文档:
可内联的lambda只能在内联函数内部调用或作为可内联参数传递...
答:
方法thread(...)不是inline方法,因此不能将f作为参数传递。
Q2:无线和交叉线的区别到底是什么?
答:
noinline会阻止lambda的内联,当你有多个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
    }

}

Q3:如果(3)没有产生性能改进,为什么(4)会产生性能改进?
答:
这是因为你在(3)中唯一的lambda被标记为noinline,这意味着你需要创建一个Function对象来存放你的lambda的主体。对于(4),lambda仍然是内联的(性能提高),只是它不允许非本地返回。

fjnneemd

fjnneemd4#

关于第一及第二项问题
为什么(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
inline修饰符既影响函数本身,也影响传递给它的lambda:所有这些都将被内联到调用点中。
因此编译器被指示放置代码(内联),而不是为f构造和调用函数对象。crossinline修饰符仅用于内联的内容:它只是说传递的lambda(在f参数中)不应该有非本地返回(“正常的”内联lambda可能有)。crossinline可以被认为是这样的(编译器的指令):“执行内联,但有一个限制,即它会跨越调用者上下文,因此请确保lambda没有非本地返回。
顺便说一句,thread看起来像是crossinline的一个概念性说明示例,因为显然稍后在不同线程上从某些代码(传入f)返回不可能影响从test返回,test继续在调用者线程上独立于其派生的代码执行(f继续独立执行)。
在案例#4中,有一个lambda(大括号)调用f()。在案例#2中,f作为参数直接传递给thread
因此在#4中,调用f()可以被内联,编译器可以保证没有非本地返回。更详细地说,编译器会用它的定义替换f(),然后代码被“ Package ”在封闭的lambda中,换句话说,{ //code for f() }是另一种( Package 器)lambda,并且它本身进一步作为函数对象引用(到thread)传递。
在案例#2中,编译器错误只是简单地说它不能内联f,因为它作为引用被传递到一个“未知”(非内联)位置。crossinline在这种情况下变得不合适和不相关,因为它只有在f被内联时才能应用。
总而言之,通过与Kotlin参考文献中的示例进行比较,情况2和4是不同的(参见“高阶函数和λ”):以下调用是等效的,其中大括号(lambda表达式)“替换” Package 器函数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的“ Package 器

相关问题