Je sais qu'une fonction en ligne améliorera peut-être les performances et entraînera la croissance du code généré, mais je ne suis pas sûr qu'il soit utilisé correctement.
lock(l) { foo() }
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. ( Source )
l.lock()
try {
foo()
}
finally {
l.unlock()
}
mais j'ai trouvé qu'il n'y a pas d'objet de fonction créé par kotlin pour une fonction non inline. Pourquoi?
/**non-inline function**/
fun lock(lock: Lock, block: () -> Unit) {
lock.lock();
try {
block();
} finally {
lock.unlock();
}
}
Supposons que vous créez une fonction d'ordre supérieur qui prend un lambda de type () -> Unit
(pas de paramètre, pas de valeur de retour), et l’exécute comme ceci:
fun nonInlined(block: () -> Unit) {
println("before")
block()
println("after")
}
Dans Java jargon, cela se traduira par quelque chose comme ceci (simplifié!):
public void nonInlined(Function block) {
System.out.println("before");
block.invoke();
System.out.println("after");
}
Et quand tu appelles ça de Kotlin ...
nonInlined {
println("do something here")
}
Sous le capot, une instance de Function
sera créée ici, elle enveloppe le code à l'intérieur du lambda (là encore, c'est simplifié):
nonInlined(new Function() {
@Override
public void invoke() {
System.out.println("do something here");
}
});
Donc, en gros, appeler cette fonction et lui passer un lambda créera toujours une instance d'un objet Function
.
Par contre, si vous utilisez le mot clé inline
:
inline fun inlined(block: () -> Unit) {
println("before")
block()
println("after")
}
Quand tu appelles ça comme ça:
inlined {
println("do something here")
}
Aucune instance de Function
ne sera créée. Le code autour de l'appel de block
à l'intérieur de la fonction intégrée sera copié sur le site de l'appel. Vous obtiendrez ainsi le même nom dans le bytecode:
System.out.println("before");
System.out.println("do something here");
System.out.println("after");
Dans ce cas, aucune nouvelle instance n'est créée.
Laissez-moi ajouter: "Quand ne pas utiliser inline
" :
1) Si vous avez une fonction simple qui n'accepte pas les autres fonctions en tant qu'argument, cela n'a aucun sens de les aligner. IntelliJ vous préviendra:
L'impact attendu de l'inline '...' sur les performances est insignifiant. L'inligne fonctionne mieux pour les fonctions avec des paramètres de types fonctionnels
2) Même si vous avez une fonction "avec des paramètres de types fonctionnels", le compilateur peut vous dire que l'inline ne fonctionne pas. Considérons cet exemple:
inline fun calculateNoInline(param: Int, operation: IntMapper): Int {
val o = operation //compiler does not like this
return o(param)
}
Ce code ne compilera pas avec l'erreur:
Utilisation illégale du paramètre en ligne 'operation' dans '...'. Ajoutez le modificateur 'noinline' à la déclaration de paramètre.
La raison en est que le compilateur est incapable d'insérer ce code. Si operation
n'est pas encapsulé dans un objet (ce qui est impliqué par inline
puisque vous voulez éviter cela), comment peut-il être affecté à une variable? Dans ce cas, le compilateur suggère de créer l'argument noinline
. Avoir une fonction inline
avec une seule fonction noinline
n'a pas de sens, ne le faites pas. Cependant, s'il existe plusieurs paramètres de types fonctionnels, envisagez d'en aligner certains si nécessaire.
Alors voici quelques règles suggérées:
noinline
pour le reste.reified
, qui vous obligent à utiliser inline
. Lire ici .Le cas le plus important lorsque nous utilisons le modificateur inline est lorsque nous définissons des fonctions de type util avec des fonctions de paramètre. Le traitement des collections ou des chaînes (comme filter
, ma
p ou joinToString
) ou simplement des fonctions autonomes en sont un exemple parfait.
C'est pourquoi le modificateur en ligne est principalement une optimisation importante pour les développeurs de bibliothèques. Ils devraient savoir comment cela fonctionne et quels sont ses coûts et ses améliorations. Nous allons utiliser le modificateur inline dans nos projets lorsque nous définissons nos propres fonctions util avec des paramètres de type de fonction.
Lorsque nous n’avons ni paramètre de type de fonction, ni paramètre de type reifié, ni l’affichage non local, nous ne devrions probablement pas utiliser le modificateur inline. C'est pourquoi nous aurons un avertissement sur Android Studio ou IDEA IntelliJ.
Il y a aussi le problème de la taille du code. L'inclusion d'une fonction volumineuse peut considérablement augmenter la taille du bytecode, car il est copié sur tous les sites d'appels. Dans ce cas, vous pouvez refactoriser la fonction et extraire le code en fonctions normales.
Un cas simple où vous pourriez en vouloir un, c'est lorsque vous créez une fonction util qui prend un bloc de suspension. Considère ceci.
fun timer(block: () -> Unit) {
// stuff
block()
//stuff
}
fun logic() { }
suspend fun asyncLogic() { }
fun main() {
timer { logic() }
// This is an error
timer { asyncLogic() }
}
Dans ce cas, notre minuterie n'acceptera pas les fonctions de suspension. Pour le résoudre, vous pourriez être tenté de le suspendre également
suspend fun timer(block: suspend () -> Unit) {
// stuff
block()
// stuff
}
Mais alors, il ne peut être utilisé qu'à partir de coroutines/fonctions suspendues. Vous allez ensuite créer une version asynchrone et une version non asynchrone de ces utilitaires. Le problème disparaît si vous le faites en ligne.
inline fun timer(block: () -> Unit) {
// stuff
block()
// stuff
}
fun main() {
// timer can be used from anywhere now
timer { logic() }
launch {
timer { asyncLogic() }
}
}
Voici un terrain de jeu de kotlin avec l'état d'erreur. Faites la minuterie en ligne pour le résoudre.