web-dev-qa-db-fra.com

quand utiliser une fonction inline dans Kotlin?

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();
    }
}
75
holi-java

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.

212
zsmb13

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:

  • Vous pouvez en ligne lorsque le type fonctionnel param est appelé directement ou passé à autre en ligne fonction
  • Vous devriez en ligne lorsque ^ est le cas.
  • Vous ne pouvez pas en ligne lorsque le paramètre de fonction est affecté à une variable dans la fonction
  • Vous devriez envisager d'inliner si au moins un de vos paramètres fonctionnels typés peut être inséré, utilisez noinline pour le reste.
  • Vous ne devriez pas insérer d'énormes fonctions, pensez au code octet généré. Il sera copié à tous les endroits où la fonction est appelée.
  • Un autre cas d'utilisation concerne les paramètres de type reified, qui vous obligent à utiliser inline. Lire ici .
14
s1m0nw1

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, map 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.

2
0xAliHn

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.

0
Anthony Naddeo