vendredi 4 décembre 2015

Pourquoi un mot clef inline ?

En Kotlin, on peut forcer à "inliner" (quelqu'un a t'il une jolie traduction en français ?) une fonction.

Ca parait surprenant au début quand on sait que le JIT sait le faire automatiquement pour nous depuis des années. Ce concept ne devrait plus avoir sa place dans un langage de haut niveau d'abstraction.

Il y a malheureusement un cas où le JIT ne peut rien pour nous, et il faut donc le faire au niveau de la compilation Kotlin vers le bytecode. C'est le cas d'une fonction qui reçoit une autre fonction en paramètre, lorsqu'on l'appelle en lui donnant une expression lambda et non une fonction nommée. Exemple : 
fun f(a: (String) -> String) = println(a("coucou"))
f { a -> a.toUpperCase() }

Le code de f pourra être inliné si le JIT le décide, mais pas l'expression lambda { a -> a.toUppserCase () }. Cette expression si petite soit-elle, sera transformée en un petit objet qui sera passée en paramètre à f. Chaque appel sera couteux.

Kotlin nous permet de résoudre le problème en forçant le compilateur Kotlin à inliner à la fois f et le petit bout de lambda expression. C'est à dire que les deux bouts de code suivants auront la même performance :

inline fun f(a: (String) -> String) = println(a("coucou"))
f { a -> a.toUpperCase() }

println ("coucou".toUpperCase())

Cette astuce n'existe pas en Java 8, le code ne sera jamais inliné et l'usage des expressions lambda est donc coûteuse du point de vue des performances. Ce qui est bien dommage car le code est indéniablement plus concis et plus lisible.

J'ai vérifié avec quelques exemples : le gain de performance avec cette option est réel. Non seulement le code Kotlin avec inline est plus rapide que sans, mais il est sensiblement plus rapide que le code Java 8. Outre le gain, l'absence d'objets intermédiaires garantie un temps d'exécution plus constant avec l'inline. Je suppose que c'est lié au travail du garbage collector, moins sollicité du coup.

On peut quand même se demander pourquoi le compilateur Kotlin ne décide pas tout seul la meilleure stratégie. Je suppose que l'objectif est de pouvoir par défaut re-compiler individuellement chaque fichier Kotlin, comme on le fait en Java. L'inconvénient du bytecode inliné est que les classes appelantes doivent être re-compilées après une modification de la fonction appelée même sans changement de signature.

Aucun commentaire:

Enregistrer un commentaire