57 votes

Quelle est la différence entre crossinline et noinline à Kotlin?

  1. Ce code compile avec un avertissement (insignifiant impact sur les performances):

    inline fun test(noinline f: () -> Unit) {
        thread(block = f)
    }
    
  2. Ce code ne compile pas (utilisation illégale de inline-paramètre):

    inline fun test(crossinline f: () -> Unit) {
        thread(block = f)
    }
    
  3. Ce code compile avec un avertissement (insignifiant impact sur les performances):

    inline fun test(noinline f: () -> Unit) {
        thread { f() }
    }
    
  4. Ce code compile sans erreur ou d'avertissement:

    inline fun test(crossinline f: () -> Unit) {
        thread { f() }
    }
    

Voici mes questions:

  • Comment venir (2) ne compile pas mais (4) n'?
  • Quelle est exactement la différence entre noinline et crossinline?
  • Si (3) ne génère pas d'améliorations de la performance, pourquoi (4) faire?

37voto

maciekjanusz Points 3392

À partir de l' inline fonctions de référence:

Notez que certaines fonctions inline peut appeler les lambdas passés comme paramètres non pas directement sur le corps de la fonction, mais d'un autre contexte d'exécution, comme un objet local ou une fonction imbriquée. Dans de tels cas, la non-locale de contrôle de flux n'est pas autorisé dans les lambdas. Pour indiquer que le paramètre lambda doit être marqué avec le modificateur crossinline

Par conséquent, l'exemple 2. ne compile pas, depuis crossinline applique uniquement locale de flux de contrôle, et l'expression block = f viole que. Exemple 1 compile, depuis noinline ne nécessite pas un tel comportement (évidemment, puisque c'est une fonction ordinaire de paramètre).

Des exemples 1 et 3 ne génèrent pas de toutes les améliorations de performances, puisque le seul paramètre lambda est marquée noinline, le rendu de l' inline modificateur de la fonction inutile et redondant - le compilateur voudrais inline quelque chose, mais tout ce qui pourrait être a été marquée pour ne pas être insérée.

Considérons deux fonctions A et B

Un

inline fun test(noinline f: () -> Unit) {
    thread { f() }
}

B

fun test(f: () -> Unit) {
    thread { f() }
}

Fonction Un se comporte comme la fonction B dans le sens que le paramètre f ne sera pas incorporé (le B de la fonction n'a pas d'insérer le corps de l' test alors que dans le Un la fonction, le corps: thread { f() } obtient toujours inline).

Maintenant, ce n'est pas vrai dans l'exemple 4, depuis l' crossinline f: () -> Unit paramètre peut être insérée, elle ne peut pas violer la précités non-locale de contrôle de la règle de flux (comme assigner une nouvelle valeur à une variable globale). Et si elle peut être insérée, le compilateur suppose des améliorations de performances et de ne pas avertir comme dans l'exemple 3.

11voto

Marko Topolnik Points 77257

Plutôt que de longues explications, laissez-moi vous montrer ce que chacun de vos exemples de commandes au compilateur de faire. Nous allons tout d'abord écrire du code qui utilise votre fonction:

fun main(args: Array<String>) {
    test { 
        println("start")
        println("stop")
    }
}

Maintenant, nous allons passer par vos variantes.

1. noinline, block = f

inline fun test1(noinline f: () -> Unit) {
    thread(block = f)
}

fun compiledMain1() {
    val myBlock = {
        println("start")
        println("stop")
    }
    thread(block = myBlock)
}

Tout d'abord, remarque il n'y a pas de preuve de l' inline fun test1 même en vigueur. Les fonctions Inline ne sont pas vraiment "appelé": c'est comme si le code d' test1 a été écrit à l'intérieur d' main(). D'autre part, l' noinline paramètre lambda se comporte de même que sans l'in-lining: vous créez un lambda de l'objet et de le passer à l' thread fonction.

2. crossinline, block = f

inline fun test2(crossinline f: () -> Unit) {
    thread(block = f)
}

fun compiledMain2() {
    thread(block =
        println("start")
        println("stop")
    )
}

J'espère que j'ai réussi à évoquer ce qui se passe ici: vous avez demandé de copier-coller le code de la bloquer dans un endroit qui attend une valeur. C'est juste syntaxique des ordures. Raison: avec ou sans crossinline vous demandez à ce que le bloc de copier-coller dans le lieu où il est utilisé. Ce modificateur juste limite ce que vous pouvez écrire à l'intérieur du bloc (pas de returns etc.)

3. noinline, { f() }

inline fun test3(noinline f: () -> Unit) {
    thread { f() }
}

fun compiledMain3() {
    val myBlock = {
        println("start")
        println("stop")
    }
    thread { myBlock() }
}

Nous sommes de retour à l' noinline ici donc, les choses sont simples encore. Vous créez régulièrement lambda objet myBlock, puis vous créez un autre habitué lambda objet que les délégués à elle: { myBlock() }, puis vous la transmettre à des thread().

4. crossinline, { f() }

inline fun test4(crossinline f: () -> Unit) {
    thread { f() }
}

fun compiledMain4() {
    thread {
        println("start")
        println("stop")
    }
}

Enfin, cet exemple montre à quel crossinline est pour. Le code de l' test4 est incorporé main, le code du bloc est incorporé dans le lieu où il est utilisé. Mais, puisqu'il est utilisé à l'intérieur de la définition d'un régulier lambda objet, il ne peut pas contenir de non-locale de flux de contrôle.

À propos de l'Impact sur les Performances

Le Kotlin équipe veut vous utiliser la fonction inline raisonnablement. Avec un alignement de la taille du code compilé peut exploser de façon spectaculaire et même frappé la JVM limites de jusqu'à 64 ko de bytecode d'instructions par méthode. Les principaux cas d'utilisation des fonctions d'ordre supérieur qui permet d'éviter le coût de la création d'un réel lambda objet, seuls les jeter juste après un seul appel de fonction qui arrive tout de suite.

Chaque fois que vous déclarez un inline fun sans inline lambdas), d'inlining lui-même a perdu sa raison d'être. Le compilateur vous met en garde à ce sujet.

8voto

Q1: Comment se fait - (2) ne compile pas mais (4) n'?

De leur doc:

Inlinable lambdas ne peut être appelée à l'intérieur de la ligne des fonctions ou des passé en tant que inlinable arguments...

Réponse:

La méthode thread(...) n'est pas un inline méthode de sorte que vous ne serez pas en mesure de transmettre f comme argument.

Q2: Quelle est exactement la différence entre noinline et crossinline?

Réponse:

noinline permettra d'éviter l'alignement des lambdas. Cela devient utile lorsque vous avez plusieurs lambda arguments et que vous voulez seulement certains des lambdas passé à une fonction inline pour être incorporé.

crossinline est utilisé pour marquer les lambdas qui ne doivent pas permettre à des non-local retourne, en particulier lorsque ces lambda est passé à un autre contexte d'exécution. En d'autres termes, vous ne serez pas en mesure de faire usage d'un return dans ces lambdas. À l'aide de votre exemple:

inline fun test(crossinline f: () -> Unit) {
    thread { f() }
}

//another method in the class
fun foo() {

    test{ 

       //Error! return is not allowed here.
       return
    }

}

Q3: Si (3) ne génère pas d'améliorations de la performance, pourquoi (4) faire?

Réponse:

C'est parce que la seule lambda que vous avez dans (3) a été marqué avec noinline ce qui signifie que vous aurez les frais généraux de la création de l' Function objet pour accueillir le corps de votre lamda. Pour (4) le lambda est toujours inline (amélioration du rendement) seulement qu'il ne permettra pas non local retourne.

1voto

minsk Points 187

À la première et à la deuxième question

Comment venir (2) ne compile pas mais (4) n'est?.. la différence entre noinline et crossinline

2. inline fun test(crossinline f: () -> Unit) {
    thread(block = f)
}

4. inline fun test(crossinline f: () -> Unit) {
    thread { f() }
}

Les deux cas ont inline modificateur demandant de l'inclure à la fois la fonction test et son argument lambda f. De kotlin de référence:

La ligne modificateur affecte à la fois la fonction elle-même et les lambdas passé: tous ceux qui seront intégrées dans le site d'appel.

Ainsi, le compilateur est chargé de placer le code (inline) plutôt que de construire et de faire appel à une fonction d'objet pour f. crossinline modificateur est seulement pour inline choses: il dit seulement que le passé lambda (en f paramètre) ne devrait pas avoir de non-local retourne (le "normal" inline lambdas peuvent avoir). crossinline peut être considéré comme quelque chose comme ceci (instruction pour le compilateur ): "ne inline, mais il y a une restriction qu'il est la traversée de l'invocateur contexte et donc, assurez-vous que le lambda n'a pas non local retourne.

Sur une note de côté, thread semble conceptuellement exemple illustratif de crossinline parce que, évidemment, de retour de code (adoptée en f) plus tard sur un autre thread ne peut pas avoir une incidence sur le retour d' test, qui continue à exécuter dans le thread appelant indépendamment de ce qu'il a engendré (f va à exécuter de façon indépendante)..

Dans le cas n ° 4, il y a un lambda (accolades), invoquant f(). Dans le cas n ° 2, f est directement transmise comme argument d' thread

Donc en #4, appelez - f() peut être insérée et le compilateur peut garantir il n'y a pas de non-retour locale. D'élaborer, le compilateur remplacera f() avec sa définition et que le code est "enveloppé" à l'intérieur de la enfermant lambda, en d'autres termes, { //code for f() } est en quelque sorte un autre (wrapper) lambda et lui-même est encore passé comme une fonction de l'objet de référence (à l' thread).

Dans le cas #2, l'erreur du compilateur simplement dit qu'il ne peut pas inline f car il est passé comme une référence à un "inconnu" (non-inline) place. crossinline devient hors de l'endroit et hors de propos dans ce cas, car il peut être appliqué que si f ont été inline.

Pour résumer, cas 2 et 4 ne sont pas les mêmes en les comparant à l'exemple de la kotlin de référence (voir "Fonctions d'Ordre Supérieur et les Lambdas"): ci-dessous les invocations sont équivalentes, où les accolades (lambda expression) "remplacer" la fonction wrapper toBeSynchronized

//want to pass `sharedResource.operation()` to lock body
fun <T> lock(lock: Lock, body: () -> T): T {...}
//pass a function
fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized) 
//or pass a lambda expression
val result = lock(lock, { sharedResource.operation() })

Cas n ° 2 et n ° 4 dans la question ne sont pas équivalentes car il n'y a pas de "wrapper", invoquant f #2

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X