我知道内联函数可能会提高性能
lock(l) { foo() }
编译器可以发出以下代码,而不是为参数创建函数对象并生成调用。(来源)
l.lock()
try {
foo()
}
finally {
l.unlock()
}
但是我发现没有kotlin为一个非内联函数创建的函数对象。为什么?
/**non-inline function**/
fun lock(lock: Lock, block: () -> Unit) {
lock.lock();
try {
block();
} finally {
lock.unlock();
}
}
lambda转换为类
在Kotlin/JVM中,函数类型(lambda)被转换为匿名/常规类,这些类扩展了接口函数。考虑以下功能:
fun doSomethingElse(lambda: () -> Unit) {
println("Doing something else")
lambda()
}
编译后,上面的函数如下所示:
public static final void doSomethingElse(Function0 lambda) {
System.out.println("Doing something else");
lambda.invoke();
}
函数类型()-
现在让我们看看当我们从其他函数调用此函数时会发生什么:
fun doSomething() {
println("Before lambda")
doSomethingElse {
println("Inside lambda")
}
println("After lambda")
}
问题:对象
编译器将lambda替换为Function
类型的匿名对象:
public static final void doSomething() {
System.out.println("Before lambda");
doSomethingElse(new Function() {
public final void invoke() {
System.out.println("Inside lambda");
}
});
System.out.println("After lambda");
}
这里的问题是,如果您在循环中调用这个函数数千次,将创建数千个对象并收集垃圾。这会影响性能。
解决方案:<代码>内联
通过在函数之前添加内联关键字,我们可以告诉编译器在调用站点复制该函数的代码,而无需创建对象:
inline fun doSomethingElse(lambda: () -> Unit) {
println("Doing something else")
lambda()
}
这会导致在调用站点复制内联函数的代码以及lambda()的代码:
public static final void doSomething() {
System.out.println("Before lambda");
System.out.println("Doing something else");
System.out.println("Inside lambda");
System.out.println("After lambda");
}
如果在for
循环中使用/不使用inline
关键字,重复次数达到一百万次,则执行速度会加倍。因此,将其他函数作为参数的函数在内联时速度更快。
当您在lambda中使用局部变量时,它被称为变量捕获(闭包):
fun doSomething() {
val greetings = "Hello" // Local variable
doSomethingElse {
println("$greetings from lambda") // Variable capture
}
}
如果此处的函数不是内联函数,则在创建前面看到的匿名对象时,捕获的变量将通过构造函数传递给lambda:
public static final void doSomething() {
String greetings = "Hello";
doSomethingElse(new Function(greetings) {
public final void invoke() {
System.out.println(this.$greetings + " from lambda");
}
});
}
如果您在lambda内部使用了许多局部变量或在循环中调用lambda,则通过构造函数传递每个局部变量会导致额外的内存开销。在这种情况下使用inline
函数很有帮助,因为该变量直接用于调用站点。
因此,正如您从上面的两个示例中看到的,当函数将其他函数作为参数时,内联函数的大部分性能优势就实现了。此时,内联函数最为有用,也最值得使用。不需要内联其他通用函数,因为JIT编译器已经在必要时将它们内联。
由于非内联函数类型被转换为类,因此我们无法在lambda中编写return语句:
fun doSomething() {
doSomethingElse {
return // Error: return is not allowed here
}
}
这被称为非本地返回
,因为它不是调用函数do万物()
的本地。不允许非本地返回
的原因是返回
语句存在于另一个类中(在前面显示的匿名类中)。使doThingElse()
函数inline
解决了这个问题,我们被允许使用非本地返回,因为然后返回
语句被复制到调用函数中。
在静态编程语言中使用泛型时,我们可以使用T
类型的值。但是我们不能直接使用类型,我们得到错误不能使用'T'作为具体化类型参数。改用类
:
fun <T> doSomething(someValue: T) {
println("Doing something with value: $someValue") // OK
println("Doing something with type: ${T::class.simpleName}") // Error
}
这是因为我们传递给函数的类型参数在运行时被擦除。因此,我们不可能确切知道我们正在处理哪种类型。
使用内联函数和具体化类型参数可以解决此问题:
inline fun <reified T> doSomething(someValue: T) {
println("Doing something with value: $someValue") // OK
println("Doing something with type: ${T::class.simpleName}") // OK
}
内联导致复制实际的类型参数来代替T。例如,T::类。simpleName变为String::class。simpleName,当您调用像doSomething(“Some String”)这样的函数时。具体化关键字只能与内联函数一起使用。
假设我们有以下在不同抽象级别重复调用的函数:
inline fun doSomething() {
println("Doing something")
}
第一抽象层
inline fun doSomethingAgain() {
doSomething()
doSomething()
}
结果:
public static final void doSomethingAgain() {
System.out.println("Doing something");
System.out.println("Doing something");
}
在第一个抽象级别,代码在以下位置增长:21=2行。
第二抽象层
inline fun doSomethingAgainAndAgain() {
doSomethingAgain()
doSomethingAgain()
}
结果:
public static final void doSomethingAgainAndAgain() {
System.out.println("Doing something");
System.out.println("Doing something");
System.out.println("Doing something");
System.out.println("Doing something");
}
在第二个抽象级别,代码在以下位置增长:22=4行。
第三抽象层
inline fun doSomethingAgainAndAgainAndAgain() {
doSomethingAgainAndAgain()
doSomethingAgainAndAgain()
}
结果:
public static final void doSomethingAgainAndAgainAndAgain() {
System.out.println("Doing something");
System.out.println("Doing something");
System.out.println("Doing something");
System.out.println("Doing something");
System.out.println("Doing something");
System.out.println("Doing something");
System.out.println("Doing something");
System.out.println("Doing something");
}
在第三个抽象级别,代码在以下位置增长:23=8行。
类似地,在第四个抽象级别,代码增长为2行4行,以此类推。
数字2是函数在每个抽象级别被调用的次数。正如您所看到的,代码不仅在最后一个级别而且在每个级别都呈指数级增长,所以这是16 8 4 2行。为了保持简洁,我在这里只显示了2个调用和3个抽象级别,但想象一下为更多调用和更多抽象级别会生成多少代码。这会增加您的应用程序的大小。这是您不应该inline
应用程序中的每个函数的另一个原因。
避免使用内联函数进行函数调用的递归循环,如以下代码所示:
// Don't use inline for such recursive cycles
inline fun doFirstThing() { doSecondThing() }
inline fun doSecondThing() { doThirdThing() }
inline fun doThirdThing() { doFirstThing() }
这将导致函数无休止地复制代码。编译器会给您一个错误:调用“yourFunction()”是内联循环的一部分。
公共内联函数无法访问私有函数,因此不能用于实现隐藏:
inline fun doSomething() {
doItPrivately() // Error
}
private fun doItPrivately() { }
在上面显示的内联函数中,访问私有函数doitprivaty()会出现错误:
公共API内联函数无法访问非公共API fun。
现在,关于你问题的第二部分:
但是我发现没有kotlin为一个非内联函数创建的函数对象。为什么?
Function
对象确实已创建。要查看创建的Function
对象,您需要在main()
函数中实际调用您的lock()
函数,如下所示:
fun main() {
lock { println("Inside the block()") }
}
生成的类
生成的函数类不会反映在反编译的Java代码中。您需要直接查看字节码。查找以以下开头的行:
final class your/package/YourFilenameKt$main$1 extends Lambda implements Function0 { }
这是编译器为传递给
lock()
函数的函数类型生成的类。main 1美元
是为您的block()
函数创建的类的名称。有时类是匿名的,如第一节中的示例所示。
生成的对象
在字节码中,查找以以下开头的行:
GETSTATIC your/package/YourFilenameKt$main$1.INSTANCE
实例是为上述类创建的对象。创建的对象是单例对象,因此命名为实例。
就是这样!希望这能对内联函数提供有用的见解。
让我补充一下:当不使用内联时:
>
如果您有一个不接受其他函数作为参数的简单函数,那么内联它们是没有意义的。IntelliJ会警告您:
内联“…”的预期性能影响是无关紧要的。内联最适用于具有函数类型参数的函数
即使您有一个“具有函数类型参数”的函数,您也可能会遇到编译器告诉您内联不起作用。考虑这个例子:
inline fun calculateNoInline(param: Int, operation: IntMapper): Int {
val o = operation //compiler does not like this
return o(param)
}
此代码无法编译,产生错误:
在“…”中非法使用内联参数“operation”。在参数声明中添加“noinline”修饰符。
原因是编译器无法内联此代码,尤其是操作参数。如果操作未包装在对象中(这将是应用内联操作的结果),如何将其分配给变量?在这种情况下,编译器建议将参数设置为noinline。将一个内联函数与一个非内联函数结合使用没有任何意义,请不要这样做。但是,如果有多个函数类型的参数,请考虑在需要时内联其中一些参数。
以下是一些建议规则:
假设您创建一个高阶函数,它采用()类型的lambda-
fun nonInlined(block: () -> Unit) {
println("before")
block()
println("after")
}
用Java的说法,这将转化为如下内容(简化!):
public void nonInlined(Function block) {
System.out.println("before");
block.invoke();
System.out.println("after");
}
当你从科特林打电话过来时。。。
nonInlined {
println("do something here")
}
在引擎盖下,此处将创建一个
函数的实例,该实例将代码包装在lambda中(再次简化):
nonInlined(new Function() {
@Override
public void invoke() {
System.out.println("do something here");
}
});
因此,基本上,调用此函数并向其传递lambda将始终创建函数对象的实例。
另一方面,如果使用内联关键字:
inline fun inlined(block: () -> Unit) {
println("before")
block()
println("after")
}
当你这样称呼它的时候:
inlined {
println("do something here")
}
不会创建
Function
实例,而是将内联函数内block
调用周围的代码复制到调用站点,因此您将在字节码中获得类似的内容:
System.out.println("before");
System.out.println("do something here");
System.out.println("after");
在这种情况下,不会创建新实例。
我注意到Kotlin为var创建了setter,并通过setter设置值,而不是直接设置值。我们可以让setter内联吗?或者在默认情况下直接设置值而不创建私有setter方法?
这个例子来自我正在学习的一门Kotlin课程: 如果我喜欢使用这样的主构造函数: 在这种情况下,我必须如何编写getter/setter?
我试图用OkHttp和Cucumber在静态编程语言中设置一个Spring启动项目,并且在运行Cucumber任务时遇到以下错误。如何修复? 还有build gradle kts片段 我看到了这个错误https://github.com/square/okio/issues/647看起来可能是它,并修复了这个build.gradle,我如何将其翻译为kotlinbuild.gradle.kts?
我试图转换一些使用Jackson的@JsonSubTypes注释来管理多态性的Java代码。 以下是可用的Java代码: 以下是我认为等效的Kotlin代码: 但我在三行“JsonSubTypes.Type”中的每一行都会出现以下错误: 知道吗?
我希望函数位于类中(不污染全局名称空间),但可以静态访问(从不创建它们所在的对象)。提议的解决办法: 这是一个好的解决方案,还是不可避免地会创建一个对象?我应该使用哪种图案?
我想为我的游戏创建一个简单的倒计时,当游戏开始时,我想每秒调用这个函数: 我试过这个: 但应用程序不幸停止,第二次调用run函数 3周前,我刚刚开始使用android开发和静态编程语言,到目前为止,我对它了解最多。 在Xcode中使用swift时,我使用了这一行,我认为类似的东西也适用于Kotlin