Un email d'un employé de Yammer, Coda Hale, et adressé aux directeurs commerciaux de Typesafe a été récemment dévoilé sur YCombinator et publié sur GitHub sous la forme d'un Gist. Cet email confirme que Yammer est en train de migrer tout son code d'infrastructure de Scala vers Java, en raison de problèmes de complexité et de performance.
Shelley Risk, directeur des relations publiques de Yammer, a précisé à InfoQ que l'email ne représentait que les opinions personnelles de Coda Hale, et pas nécessairement celles de Yammer; suite à quoi l'auteur de l'email original a souhaité apporter des précisions supplémentaires, visibles ici : http://codahale.com/the-rest-of-the-story/. Coda y explique que son message était à l'origine sa réponse à une demande d'explications de la part de Donald Fischer (directeur de Typesafe), faisant suite à un message sur Twitter annonçant l'abandon de Scala pour Java.
Mise à jour: Coda a également publié la position officielle de Yammer sur le sujet, qui confirme les points énoncés dans son mail. Il rappelle également que tous les langages ont leurs faiblesses (pas seulement Scala), et que son email devait plutôt être perçu comme un ensemble de pistes à explorer pour améliorer (entre autres) les performances de Scala. Pour finir, il conclut qu'avant d'envisager l'utilisation de Scala sur un projet à haute performance, il restait un certain nombre de problèmes à corriger; l'email était donc destiné à améliorer Scala.
L'email original n'était pas destiné à une diffusion publique; Coda l'avait publié sur GitHub sous la forme d'un Gist uniquement pour recueillir l'avis de ses collègues. Mais il a été largement partagé, et a donc fini par être divulgué au grand public.
En août 2010, Coda expliquait sur le blog technique de Yammer qu'ils étaient en train de migrer vers Scala pour bénéficier de ses fonctionnalités temps-réel. Le but était de conserver la JVM (pour des raisons de performances), et la migration a résulté en une diminution de 50% de la quantité de code.
Notre prototype original pour Artie était écrit en Java. Un weekend, j'ai tenté de le réimplémenter en Scala 2.8. Au bout d'une journée, j'avais éliminé environ la moitié des lignes de code et ajouté plusieurs fonctionnalités non-triviales. J'était conquis. Il est certainement plus facile de trouver des développeurs Java, mais une équipe de développeurs Scala est bien plus productive.
Un an et quart plus tard, il revient sur sa décision :
En ce moment même, chez Yammer, nous re-migrons tout notre code d'infrastructure de Scala vers Java; nous conservons Scala uniquement pour certaines interfaces et pour certaines bibliothèques legacy. Nous n'avons pas pris la décision à la légère, et nous débutons à peine la migration, mais cela fasait longtemps que nous y pensions. Pour résumer, nous n'avons pas constaté de gains de productivité ou de maintenance suffisants pour justifier la complexité et la pénibilité du développement en Scala; nous n'en ferons donc pas notre langage par défaut. Nous avons toujours du Scala en production, et nous serons obligés de le maintenir, mais notre plateforme principale de développement sera désormais Java.
Stephen Colebourne, qui a récemment publié un billet intitulé "Scala est-il le nouvel EJB2 ?", a souhaité ajouter des remarques au mail initial :
Scala, en tant que langage, propose certains concepts très intéressants. Mais c'est également un langage très complexe.
En plus des nouveaux concepts introduits par Scala, et de leur implémentation particulière, il faut également comprendre la philosophie générale du langage pour l'utiliser de la façon prévue par ses auteurs. Une bonne pratique a également émergé : ignorer complètement la communauté qui l'entoure.
Rétrospectivement, je m'aperçois que j'ai largement sous-estimé la difficulté et l'importance de l'apprentissage de Scala. Parce qu'il est tout simplement impossible de trouver des développeurs sur le marché possédant une expérience significative de Scala, c'est un point qui prend une importance critique.
En plus de la complexité du langage même, se pose la question de la qualité des outils de développement. (...) Le fait de vouloir à tout prix imposer SBT comme seul et unique outil de build a conduit à marginaliser Maven et Ant - pourtant les deux principaux outils de build dans le monde Java.
Chaque version majeure de Scala est incompatible avec les versions précédentes. Les développeurs sont donc fortement incités à migrer vers les dernières librairies à la mode et à réinventer la roue en permanence.
L'utilisation d'un profileur et l'analyse du bytecode nous ont permis de dégager un ensemble de règles de codage, et d'améliorer nos performances d'un facteur 100 :
- Ne jamais utiliser une boucle "for"
- Ne jamais utiliser les collections de scala.collection.mutable
- Ne jamais utiliser les collections de scala.collection.immmutable
- Toujours utiliser private[this]
- Eviter les closures
J'ai discuté du sujet (la migration vers Java) avec mon équipe, je leur montré le code dans les deux langages, et j'ai été assez surpris du consensus immédiat en faveur du retour à Java. Certains aspects de Scala nous manqueront, mais certainement pas assez pour nous retenir.
Certains de ces problèmes sont problablement anecdotiques (par exemple, la facilité d'embauche de développeurs expérimentés augmente avec le temps), mais certains peuvent être facilement testés. Par exemple, prenons mon conseil d'éviter les boucles "for" ; le code suivant permet d'en démontrer facilement le bien-fondé :
scala>
var start = System.currentTimeMillis();
var total = 0;for(i <- 0 until 100000) { total += i };
var end = System.currentTimeMillis();
println(end-start);
println(total);
114
scala>
scala<
var start = System.currentTimeMillis();
var total = 0;var i=0;while(i < 100000) { i=i+1;total += i };
var end = System.currentTimeMillis();
println(end-start);
println(total);
8
Utiliser une boucle "for" avec une clause "until" (ce que beaucoup de programmeurs Scala considèrent comme une pratique standard) est significativement moins performant qu'une boucle "while" équivalente, bien que cette dernière soit moins lisible. Le même code en Java offre des performances de l'ordre de 2ms quel que soit le type de boucle.
Nous pouvons également tester la performance des Maps mutables en la remplissant avec un jeu de données composé exclusivement d'Integers (la comparaison est facile entre Scala et Java, et le coût du boxing devrait être équivalent).
scala>
val m = new scala.collection.mutable.HashMap[Int,Int];
var i = 0;
var start = System.currentTimeMillis();
while(i<100000) { i=i+1;m.put(i,i);};
var end = System.currentTimeMillis();
println(end-start);
println(m.size)
101
scala>
val m = new java.util.HashMap[Int,Int];
var i = 0;
var start = System.currentTimeMillis();
while(i<100000) { i=i+1;m.put(i,i);};
var end = System.currentTimeMillis();
println(end-start);
println(m.size)
28
scala>
val m = new java.util.concurrent.ConcurrentHashMap[Int,Int];
var i = 0;
var start = System.currentTimeMillis();
while(i<100000) { i=i+1;m.put(i,i);};
var end = System.currentTimeMillis();
println(end-start);
println(m.size)
55
Comparé au code équivalent en Java, la performance est la même lorsqu'on utilise une java.util.HashMap, et deux fois meilleure en Java lorsqu'on utilise une java.util.concurrent.ConcurrentHashMap. Et en toutes circonstances, ces deux Maps offrent des performances bien meilleures que leurs équivalents en Scala. (Les mesures ont été réalisées sur OSX, JVM 1.6.0_29, Scala 2.9.1 - la dernière version disponible actuellement)
Malheureusement, les collections Scala sont utilisées partout dans les API et librairies Scala, et les collections Java sont souvent transformées automatiquement en collections Scala par le biais des "implicits". En conséquence, de nombreuses librairies Scala ont dû être réécrites pour des raisons de performance, comme indiqué dans l'email de Yammer.
La performance des closures (lambdas) serait sans doute meilleure si le compilateur utilisait "invokedynamic"; il est possible que cette fonctionnalité soit implémentée dans les prochaines versions. Le JDK8 (qui introduira le support natif des expressions lambda et des pointeurs de fonctions dans le langage Java) proposera également des améliorations en termes de performances, dont Scala pourrait tirer parti.
Pour terminer, le problème de la compatibilité entre les versions majeures (plutôt qu'entre les versions mineures uniquement, par exemple entre 2.9.2 et 2.9.3) se fait de plus en plus pressant. Typesafe n'a toujours communiqué aucune roadmap officielle des futures versions de Scala, et ne s'est pas exprimé sur une éventuelle stabilisation du format du bytecode qui garantirait la rétro-compatibilité du code entre les versions majeures. Un format rétro-compatible favoriserait pourtant la pérennisation des librairies et la constitution d'un repository standard, qui aiderait la communauté à développer encore davantage l'écosystème Scala.