内联函数什么时候展开
You know all of those Util files you create with all sorts of small functions that you end up using a lot throughout your app? If your utility functions get other functions as parameters, chances are you can improve the performance of your app by saving some extra object allocations, that you might not even know you’re making, with one keyword: inline
. Let’s see what happens when you pass these short functions around, what inline does under the hood and what you should be aware of when working with inline functions.
您知道使用各种小功能创建的所有Util文件,最终在整个应用程序中大量使用这些文件吗? 如果您的实用程序函数将其他函数用作参数,则可以通过使用一个关键字inline
来保存一些甚至可能不知道自己正在做的对象分配,从而提高应用程序的性能。 让我们看看当您传递这些短函数时会发生什么,内联在幕后做什么,以及在使用内联函数时应注意的事项。
函数调用-内幕 (Function call — under the hood)
Let’s say that you use SharedPreferences
a lot in your app so you create this utility function to reduce the boilerplate every time you write something in your SharedPreferences
:
假设您在应用程序中使用了很多SharedPreferences
,因此您创建此实用程序函数可以在每次在SharedPreferences
编写内容时减少样板:
fun SharedPreferences.edit(
commit: Boolean = false,
action: SharedPreferences.Editor.() -> Unit
) {
val editor = edit()
action(editor)
if (commit) {
editor.commit()
} else {
editor.apply()
}
}
Then, you can use it to save a String
token:
然后,您可以使用它保存String
令牌:
private const val KEY_TOKEN = “token”class PreferencesManager(private val preferences: SharedPreferences){
fun saveToken(token: String) {
preferences.edit { putString(KEY_TOKEN, token) }
}
}
Now let’s see what’s going on under the hood when preferences.edit
is called. If we look at the Kotlin bytecode (Tools > Kotlin > Decompiled Kotlin to Java) we see that there’s a NEW
called, so a new object is being created, even if in our code we didn’t call any object constructor:
现在,让我们看看调用preferences.edit
情况。 如果查看Kotlin字节码(“工具”>“ Kotlin”>“将Kotlin编译为Java”),我们会看到有一个NEW
调用,因此正在创建一个新对象,即使在我们的代码中我们没有调用任何对象构造函数:
NEW com/example/inlinefun/PreferencesManager$saveToken$1
Let’s check the decompiled code to make this a bit friendlier. Our saveToken
decompiled function is as follows (comments and formatting mine):
让我们检查一下反编译的代码,以使其更加友好。 我们的saveToken
反编译功能如下(注释和格式化我的代码):
Each high-order function we create leads to a
Function
object creation and memory allocation that introduces runtime overhead.我们创建的每个高阶函数都会导致
Function
对象的创建和内存分配,从而引入运行时开销。
内联功能-引擎盖下 (Inline function — under the hood)
To improve the performance of our app we can avoid the new function object creation, by using the inline keyword:
为了提高应用程序的性能,我们可以使用inline关键字来避免创建新的函数对象:
inline fun SharedPreferences.edit(
commit: Boolean = false,
action: SharedPreferences.Editor.() -> Unit
) { … }
Now the Kotlin bytecode doesn’t contain any NEW
calls and here’s how the decompiled java code looks like for our saveToken
method (comments and formatting mine):
现在Kotlin字节码不包含任何NEW
调用,这是我们saveToken
方法的反编译Java代码的样子(注释和格式化我的代码):
Because of the inline
keyword, the compiler copies the content of the inline function to the call site, avoiding creating a new Function
object.
由于使用inline
关键字,编译器将inline函数的内容复制到调用站点,从而避免了创建新的Function
对象。
标记为内联的内容 (What to mark as inline)
⚠️ If you’re trying to mark as inline a function that doesn’t accept another function as a parameter, you won’t get significant performance benefits and the IDE will even tell you that, suggesting you to remove it:
If️如果您尝试将不接受另一个函数作为参数的函数标记为内联函数,则不会获得明显的性能优势,IDE甚至会告诉您,建议您删除它:
⚠️ Because inlining may cause the generated code to grow, make sure that you avoid inlining large functions. For example, if you check the Kotlin Standard Library, you’ll see that most of the inlined functions have only 1–3 lines.
⚠️ 因为内联可能导致生成的代码增长,所以请确保您 避免内联大型函数 。 例如,如果您查看Kotlin标准库,您会看到大多数内联函数只有1-3行。
⚠️ Avoid inlining large functions!
Avoid️ 避免内联大型函数!
⚠️ When using inline functions, you’re not allowed to keep a reference to the functions passed as parameter or pass it to a different function — you’ll get a compiler error saying Illegal usage of inline-parameter
.
using️使用内联函数时, 不允许保留对作为参数传递的函数的引用或将其传递给其他函数—会出现编译器错误,指出Illegal usage of inline-parameter
。
So, for example, let’s modify the edit
method and the saveToken
method. edit
method gets another parameter that is then passed to a different function. saveToken
uses a dummy variable that gets updated in the new function:
因此,例如,让我们修改edit
方法和saveToken
方法。 edit
方法获取另一个参数,然后将其传递给另一个函数。 saveToken
使用一个虚拟变量,该变量在新函数中进行更新:
fun myFunction(importantAction: Int.() -> Unit) {
importantAction(-1)
}inline fun SharedPreferences.edit(
commit: Boolean = false,importantAction: Int.() -> Unit = { },
action: SharedPreferences.Editor.() -> Unit
) {myFunction(importantAction)
...
}
...
fun saveToken(token: String) {
var dummy = 3
preferences.edit(importantAction = { dummy = this}) {
putString(KEY_TOKEN, token)
}
}
We can see that myFunction(importantAction)
produces an error:
我们可以看到myFunction(importantAction)
产生一个错误:
Here’s how you can solve this, depending on how your function looks like:
根据函数的外观,可以按照以下方法解决此问题:
Case 1: If you have multiple functions as parameters and you only need to keep a reference to one of them, then you can mark it as noinline
.
情况1 :如果您有多个函数作为参数,而只需要保留对其中一个的引用,则可以将其标记为noinline
。
By using noinline
, the compiler will create a new Function
object only for that specific function, but the rest will be inlined.
通过使用noinline
,编译器将仅为该特定函数创建一个新的Function
对象,而其余的将被内联。
Our edit
function will now be:
现在,我们的edit
功能将是:
inline fun SharedPreferences.edit(
commit: Boolean = false,noinline importantAction: Int.() -> Unit = { },
action: SharedPreferences.Editor.() -> Unit
) {
myFunction(importantAction)
...
}
If we check the bytecode, we see that a NEW
call appeared:
如果我们检查字节码,我们会看到出现了一个NEW
调用:
NEW com/example/inlinefun/PreferencesManager$saveToken$1
In the decompiled code we can see the following (comments mine):
在反编译的代码中,我们可以看到以下内容(我的评论):
Case 2: If your function only has one function as a parameter, just prefer not using inline
at all. If you do want to use inline, you’d have to mark your parameter with noinline
, but like this you’ll have low performance benefits by inlining the method.
情况2 :如果您的函数只有一个函数作为参数,则只希望根本不使用inline
。 如果确实要使用内联,则必须用noinline
标记参数,但是这样,通过内联该方法将降低性能。
To decrease the memory allocations caused by lambda expressions, use the inline
keyword! Make sure you apply it to small functions that take a lambda as a parameter. If you need to keep a reference to a lambda or pass it as an argument to another function use the noinline
keyword. Start inlining to start saving!
要减少由lambda表达式引起的内存分配,请使用inline
关键字! 确保将其应用于以lambda作为参数的 小函数 。 如果您需要保留对lambda的引用或将其作为参数传递给另一个函数,请使用noinline
关键字。 开始内联以开始保存!
翻译自: https://medium.com/androiddevelopers/inline-functions-under-the-hood-12ddcc0b3a56
内联函数什么时候展开