Tout d'abord, je voudrais préciser que ce n'est pas une question de langue X contre langue Y pour déterminer laquelle est la meilleure.
J'utilise Java depuis longtemps et j'ai l'intention de continuer à l'utiliser. Parallèlement à cela, j'apprends actuellement Scala avec un grand intérêt: à part des choses mineures qui prennent un peu l'habitude de mon impression est que je peux vraiment très bien travailler dans cette langue.
Ma question est: comment les logiciels écrits en Scala se comparent-ils aux logiciels écrits en Java en termes de vitesse d'exécution et de consommation de mémoire? Bien sûr, c'est un question à répondre en général, mais je m'attendrais à ce que les constructions de niveau supérieur telles que la correspondance de motifs, les fonctions d'ordre supérieur, etc., introduisent une surcharge.
Cependant, mon expérience actuelle dans Scala est limitée à de petits exemples sous 50 lignes de code et je n'ai pas exécuté de benchmarks jusqu'à présent. Donc, je n'ai pas de données réelles.
S'il s'est avéré que Scala a du temps système par rapport à Java, est-il logique d'avoir mélangé Scala/Java, où l'on code les parties les plus complexes dans Scala et les parties critiques pour les performances en Java? Est-ce un pratique courante?
MODIFIER 1
J'ai exécuté un petit benchmark: construire une liste d'entiers, multiplier chaque entier par deux et le mettre dans une nouvelle liste, imprimer la liste résultante. J'ai écrit une implémentation Java (Java 6) et Scala (Scala 2.9). J'ai exécuté les deux sur Eclipse Indigo sous Ubuntu 10.04.
Les résultats sont comparables: 480 ms pour Java et 493 ms pour Scala (en moyenne sur 100 itérations). Voici les extraits de code que j'ai utilisés.
// Java
public static void main(String[] args)
{
long total = 0;
final int maxCount = 100;
for (int count = 0; count < maxCount; count++)
{
final long t1 = System.currentTimeMillis();
final int max = 20000;
final List<Integer> list = new ArrayList<Integer>();
for (int index = 1; index <= max; index++)
{
list.add(index);
}
final List<Integer> doub = new ArrayList<Integer>();
for (Integer value : list)
{
doub.add(value * 2);
}
for (Integer value : doub)
{
System.out.println(value);
}
final long t2 = System.currentTimeMillis();
System.out.println("Elapsed milliseconds: " + (t2 - t1));
total += t2 - t1;
}
System.out.println("Average milliseconds: " + (total / maxCount));
}
// Scala
def main(args: Array[String])
{
var total: Long = 0
val maxCount = 100
for (i <- 1 to maxCount)
{
val t1 = System.currentTimeMillis()
val list = (1 to 20000) toList
val doub = list map { n: Int => 2 * n }
doub foreach ( println )
val t2 = System.currentTimeMillis()
println("Elapsed milliseconds: " + (t2 - t1))
total = total + (t2 - t1)
}
println("Average milliseconds: " + (total / maxCount))
}
Donc, dans ce cas, il semble que le Scala overhead (en utilisant range, map, lambda) est vraiment minime, ce qui n'est pas loin des informations fournies par World Engineer.
Peut-être y a-t-il d'autres constructions Scala qui devraient être utilisées avec précaution car elles sont particulièrement lourdes à exécuter?
MODIFIER 2
Certains d'entre vous ont souligné que l'impression dans les boucles internes prend la plupart du temps d'exécution. Je les ai supprimés et défini la taille des listes à 100000 au lieu de 20000. La moyenne résultante était de 88 ms pour Java et 49 ms pour Scala.
Il y a une chose que vous pouvez faire de manière concise et efficace dans Java que vous ne pouvez pas dans Scala: les énumérations. Pour tout le reste, même pour les constructions qui sont lentes dans la bibliothèque de Scala, vous pouvez obtenir des versions efficaces fonctionnant dans Scala.
Donc, pour la plupart, vous n'avez pas besoin d'ajouter Java à votre code. Même pour le code qui utilise l'énumération en Java, il y a souvent une solution dans Scala qui est adéquate ou bonne - je place l'exception sur les énumérations qui ont des méthodes supplémentaires et dont les valeurs constantes int sont utilisées.
Quant à ce à quoi faire attention, voici quelques choses.
Si vous utilisez le modèle d'enrichir ma bibliothèque, convertissez toujours en classe. Par exemple:
// WRONG -- the implementation uses reflection when calling "isWord"
implicit def toIsWord(s: String) = new { def isWord = s matches "[A-Za-z]+" }
// RIGHT
class IsWord(s: String) { def isWord = s matches "[A-Za-z]+" }
implicit def toIsWord(s: String): IsWord = new IsWord(s)
Méfiez-vous des méthodes de collecte - car elles sont polymorphes pour la plupart, JVM ne les optimise pas. Vous n'avez pas besoin de les éviter, mais faites attention aux sections critiques. Sachez que for
dans Scala est implémenté via des appels de méthode et des classes anonymes.
Si vous utilisez une classe Java, telle que les classes String
, Array
ou AnyVal
qui correspondent aux primitives Java, préférez les méthodes fourni par Java lorsqu'il existe des alternatives. Par exemple, utilisez length
sur String
et Array
au lieu de size
.
Évitez l'utilisation imprudente des conversions implicites, car vous pouvez vous retrouver à utiliser des conversions par erreur plutôt que par conception.
Étendez les classes au lieu des traits. Par exemple, si vous étendez Function1
, Étendez plutôt AbstractFunction1
.
Utilisez -optimise
Et la spécialisation pour obtenir la plupart de Scala.
Comprenez ce qui se passe: javap
est votre ami, tout comme un tas de drapeaux Scala qui montrent ce qui se passe.
Les idiomes Scala sont conçus pour améliorer l'exactitude et rendre le code plus concis et plus facile à gérer. Ils ne sont pas conçus pour la vitesse, donc si vous devez utiliser null
au lieu de Option
dans un chemin critique, faites-le! Il y a une raison pour laquelle Scala est multi-paradigme.
N'oubliez pas que la véritable mesure des performances est l'exécution de code. Voir cette question pour un exemple de ce qui peut arriver si vous ignorez cette règle.
Selon le Benchmarks Game pour un seul système 32 bits, Scala est à une médiane 80% aussi rapide que Java. Les performances sont approximativement les mêmes pour un Ordinateur Quad Core x64. Même tilisation de la mémoire et densité du code sont très similaires dans la plupart des cas. Je dirais que d'après ces analyses (plutôt non scientifiques), vous avez raison d'affirmer que Scala ajoute des frais généraux à Java. Il ne semble pas ajouter des tonnes de frais généraux, donc je soupçonne que le diagnostic d'articles d'ordre supérieur prenant plus d'espace/temps est le plus correct.
Int
, Char
, etc. quand il le pourra. Alors que les boucles sont tout aussi efficaces dans Scala.Function
. Si vous passez un lambda à map
, la classe anonyme doit être instanciée (et certaines sections locales peuvent avoir besoin d'être passées), puis chaque itération a une surcharge supplémentaire d'appel de fonction (avec un passage de paramètre) à partir du apply
appels.scala.util.Random
Ne sont que des wrappers autour de classes JRE équivalentes. L'appel de fonction supplémentaire est légèrement inutile.Java.lang.Math.signum(x)
est beaucoup plus direct que x.signum()
, qui se convertit en RichInt
et vice-versa.(1 to 20000).toList.map (_ * 2).max
réduit le temps de 800 ms à 20 sur mon système.