Utilisez inline
pour empêcher la création d'objets
Les lambdas sont converties en classes
Dans Kotlin/JVM, les types de fonctions (lambdas) sont convertis en classes anonymes/régulières qui étendent l'interface Function
. Considérons la fonction suivante :
fun doSomethingElse(lambda: () -> Unit) {
println("Doing something else")
lambda()
}
La fonction ci-dessus, après compilation, aura l'aspect suivant :
public static final void doSomethingElse(Function0 lambda) {
System.out.println("Doing something else");
lambda.invoke();
}
Le type de fonction () -> Unit
est converti en interface Function0
.
Voyons maintenant ce qui se passe lorsque nous appelons cette fonction depuis une autre fonction :
fun doSomething() {
println("Before lambda")
doSomethingElse {
println("Inside lambda")
}
println("After lambda")
}
Problème : objets
Le compilateur remplace le lambda par un objet anonyme de type Function
type :
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");
}
Le problème ici est que, si vous appelez cette fonction dans une boucle des milliers de fois, des milliers d'objets seront créés et ramassés. Cela affecte les performances.
Solution : inline
En ajoutant le inline
avant la fonction, nous pouvons demander au compilateur de copier le code de cette fonction au niveau de l'appel, sans créer les objets :
inline fun doSomethingElse(lambda: () -> Unit) {
println("Doing something else")
lambda()
}
Cela a pour conséquence de copier le code du inline
ainsi que le code de la fonction lambda()
à l'emplacement de l'appel :
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");
}
Cela double la vitesse d'exécution, si vous comparez avec/sans inline
avec un million de répétitions dans un for
boucle. Ainsi, les fonctions qui prennent d'autres fonctions comme arguments sont plus rapides lorsqu'elles sont inlined.
Utilisez inline
pour empêcher la capture de variables
Lorsque vous utilisez les variables locales à l'intérieur du lambda, cela s'appelle la capture de variable (closure) :
fun doSomething() {
val greetings = "Hello" // Local variable
doSomethingElse {
println("$greetings from lambda") // Variable capture
}
}
Si notre doSomethingElse()
La fonction ici n'est pas inline
les variables capturées sont passées à la lambda via le constructeur lors de la création de l'objet anonyme que nous avons vu précédemment :
public static final void doSomething() {
String greetings = "Hello";
doSomethingElse(new Function(greetings) {
public final void invoke() {
System.out.println(this.$greetings + " from lambda");
}
});
}
Si vous avez de nombreuses variables locales utilisées à l'intérieur du lambda ou si vous appelez le lambda dans une boucle, le passage de chaque variable locale par le constructeur entraîne une surcharge de mémoire supplémentaire. En utilisant la méthode inline
dans ce cas, aide beaucoup, puisque la variable est directement utilisée au niveau de l'appel.
Ainsi, comme vous pouvez le voir dans les deux exemples ci-dessus, la majeure partie de l'avantage en termes de performances de l'option inline
est réalisée lorsque les fonctions prennent d'autres fonctions comme arguments. C'est lorsque les inline
sont les plus bénéfiques et valent la peine d'être utilisées. Il n'est pas nécessaire de inline
d'autres fonctions générales, car le compilateur JIT les met déjà en ligne sous le capot, chaque fois qu'il le juge nécessaire.
Utilisez inline
pour un meilleur flux de contrôle
Puisque le type de fonction non-inline est converti en classe, nous ne pouvons pas écrire l'instruction return
à l'intérieur du lambda :
fun doSomething() {
doSomethingElse {
return // Error: return is not allowed here
}
}
C'est ce qu'on appelle non local return
parce qu'il n'est pas local à la fonction appelante doSomething()
. La raison pour laquelle on n'autorise pas l'utilisation non locale return
est que le return
existe dans une autre classe (dans la classe anonyme présentée précédemment). En rendant l'instruction doSomethingElse()
fonction inline
résout ce problème et nous sommes autorisés à utiliser des retours non-locaux car alors la return
est copiée dans la fonction appelante.
Utilisez inline
pour reified
paramètres de type
En utilisant les génériques en Kotlin, nous pouvons travailler avec la valeur de type T
. Mais nous ne pouvons pas travailler avec le type directement, nous obtenons l'erreur suivante Cannot use 'T' as reified type parameter. Use a class instead
:
fun <T> doSomething(someValue: T) {
println("Doing something with value: $someValue") // OK
println("Doing something with type: ${T::class.simpleName}") // Error
}
En effet, l'argument de type que nous passons à la fonction est effacé au moment de l'exécution. Nous ne pouvons donc pas savoir exactement à quel type nous avons affaire.
En utilisant un inline
ainsi que la fonction reified
résout ce problème :
inline fun <reified T> doSomething(someValue: T) {
println("Doing something with value: $someValue") // OK
println("Doing something with type: ${T::class.simpleName}") // OK
}
L'alignement fait que l'argument de type réel est copié à la place de T
. Ainsi, par exemple, le T::class.simpleName
devient String::class.simpleName
lorsque vous appelez la fonction comme doSomething("Some String")
. Le site reified
Le mot-clé ne peut être utilisé qu'avec inline
fonctions.
Évitez inline
lorsque les appels sont répétitifs
Disons que nous avons la fonction suivante qui est appelée de manière répétitive à différents niveaux d'abstraction :
inline fun doSomething() {
println("Doing something")
}
Premier niveau d'abstraction
inline fun doSomethingAgain() {
doSomething()
doSomething()
}
Résultats dans :
public static final void doSomethingAgain() {
System.out.println("Doing something");
System.out.println("Doing something");
}
Au premier niveau d'abstraction, le code se développe à : 2 1 \= 2 lignes.
Deuxième niveau d'abstraction
inline fun doSomethingAgainAndAgain() {
doSomethingAgain()
doSomethingAgain()
}
Résultats dans :
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");
}
Au deuxième niveau d'abstraction, le code se développe à : 2 2 \= 4 lignes.
Troisième niveau d'abstraction
inline fun doSomethingAgainAndAgainAndAgain() {
doSomethingAgainAndAgain()
doSomethingAgainAndAgain()
}
Résultats dans :
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");
}
Au troisième niveau d'abstraction, le code se développe à : 2 3 \= 8 lignes.
De même, au quatrième niveau d'abstraction, le code s'accroît de 2 %. 4 \= 16 lignes et ainsi de suite.
Le nombre 2 est le nombre de fois que la fonction est appelée à chaque niveau d'abstraction. Comme vous pouvez le constater, le code croît de manière exponentielle non seulement au dernier niveau mais aussi à chaque niveau, soit 16 + 8 + 4 + 2 lignes. J'ai montré seulement 2 appels et 3 niveaux d'abstraction ici pour rester concis mais imaginez combien de code sera généré pour plus d'appels et plus de niveaux d'abstraction. Cela augmente la taille de votre application. C'est une autre raison pour laquelle vous ne devriez pas inline
chaque fonction de votre application.
Évitez inline
en cycles récursifs
Évitez d'utiliser le inline
pour les cycles récursifs d'appels de fonctions, comme le montre le code suivant :
// Don't use inline for such recursive cycles
inline fun doFirstThing() { doSecondThing() }
inline fun doSecondThing() { doThirdThing() }
inline fun doThirdThing() { doFirstThing() }
Il en résultera un cycle sans fin où les fonctions copieront le code. Le compilateur vous donne une erreur : The 'yourFunction()' invocation is a part of inline cycle
.
Ne peut pas utiliser inline
lors du masquage de la mise en œuvre
Le public inline
ne peuvent pas accéder aux fonctions private
et ne peuvent donc pas être utilisés pour le masquage de l'implémentation :
inline fun doSomething() {
doItPrivately() // Error
}
private fun doItPrivately() { }
Dans le inline
présentée ci-dessus, en accédant à la fonction private
fonction doItPrivately()
donne une erreur : Public-API inline function cannot access non-public API fun
.
Vérification du code généré
Maintenant, concernant la deuxième partie de votre question :
mais j'ai constaté qu'il n'y a pas d'objet fonction créé par kotlin pour une fonction non-inline. Pourquoi ?
El Function
est bien créé. Pour voir l'objet créé Function
vous devez en fait appeler votre lock()
dans la fonction main()
comme suit :
fun main() {
lock { println("Inside the block()") }
}
Classe générée
Le produit Function
ne se reflète pas dans le code Java décompilé. Vous devez regarder directement dans le bytecode. Cherchez la ligne qui commence par :
final class your/package/YourFilenameKt$main$1 extends Lambda implements Function0 { }
Il s'agit de la classe qui est générée par le compilateur pour le type de fonction qui est passé à la fonction lock()
fonction. Le site main$1
est le nom de la classe qui est créé pour votre block()
fonction. Parfois, la classe est anonyme, comme le montre l'exemple de la première section.
Objet généré
Dans le bytecode, recherchez la ligne commençant par :
GETSTATIC your/package/YourFilenameKt$main$1.INSTANCE
INSTANCE
est l'objet qui est créé pour la classe mentionnée ci-dessus. L'objet créé est un singleton, d'où le nom de INSTANCE
.
C'est ça ! J'espère que vous avez trouvé des informations utiles sur inline
fonctions.
8 votes
Il existe deux cas d'utilisation principaux pour cela, l'un concerne certains types de fonctions d'ordre supérieur, et l'autre les paramètres de type réifié. La documentation des fonctions en ligne couvre ces cas : kotlinlang.org/docs/reference/inline-functions.html
5 votes
@zsmb13 merci, monsieur. mais je ne comprends pas que : "Au lieu de créer un objet fonction pour le paramètre et de générer un appel, le compilateur pourrait émettre le code suivant".
5 votes
Je ne comprends pas non plus cet exemple.
0 votes
Qu'est-ce qui vous fait dire qu'un
inline
peut améliorer les performances, et que cela serait perceptible par l'utilisateur ?