Je suis impatient de me pencher sur Scala et j'ai une question fondamentale à laquelle je n'arrive pas à trouver de réponse: en général, y a-t-il une différence de performances et d'utilisation de la mémoire entre Scala et Java?
Je suis impatient de me pencher sur Scala et j'ai une question fondamentale à laquelle je n'arrive pas à trouver de réponse: en général, y a-t-il une différence de performances et d'utilisation de la mémoire entre Scala et Java?
Réponses:
Scala facilite l'utilisation d'énormes quantités de mémoire sans s'en rendre compte. C'est généralement très puissant, mais parfois ennuyeux. Par exemple, supposons que vous ayez un tableau de chaînes (appelées array
) et une carte de ces chaînes vers des fichiers (appelées mapping
). Supposons que vous souhaitiez obtenir tous les fichiers qui se trouvent dans la carte et proviennent de chaînes de longueur supérieure à deux. En Java, vous pourriez
int n = 0;
for (String s: array) {
if (s.length > 2 && mapping.containsKey(s)) n++;
}
String[] bigEnough = new String[n];
n = 0;
for (String s: array) {
if (s.length <= 2) continue;
bigEnough[n++] = map.get(s);
}
Ouf! Un dur travail. Dans Scala, la façon la plus compacte de faire la même chose est:
val bigEnough = array.filter(_.length > 2).flatMap(mapping.get)
Facile! Mais, à moins que vous ne soyez assez familier avec le fonctionnement des collections, ce que vous ne réalisez peut-être pas, c'est que cette façon de faire a créé un tableau intermédiaire supplémentaire (avec filter
), et un objet supplémentaire pour chaque élément du tableau (avec mapping.get
, qui retourne une option). Il crée également deux objets de fonction (un pour le filtre et un pour le flatMap), bien que ce soit rarement un problème majeur car les objets de fonction sont petits.
Donc, fondamentalement, l'utilisation de la mémoire est, à un niveau primitif, la même. Mais les bibliothèques de Scala ont de nombreuses méthodes puissantes qui vous permettent de créer très facilement un nombre énorme d'objets (généralement de courte durée). Le garbage collector est généralement assez bon avec ce genre de déchets, mais si vous oubliez complètement la mémoire utilisée, vous rencontrerez probablement des problèmes plus tôt dans Scala que Java.
Notez que le code Scala de Computer Languages Benchmark Game est écrit dans un style plutôt Java afin d'obtenir des performances de type Java, et a donc une utilisation de la mémoire de type Java. Vous pouvez le faire dans Scala: si vous écrivez votre code pour ressembler à du code Java haute performance, ce sera du code Scala haute performance. (Vous pourrez peut- être l'écrire dans un style Scala plus idiomatique tout en obtenant de bonnes performances, mais cela dépend des spécificités.)
Je devrais ajouter que par temps passé à programmer, mon code Scala est généralement plus rapide que mon code Java car dans Scala, je peux faire les parties fastidieuses non critiques pour les performances avec moins d'effort, et consacrer plus de mon attention à l'optimisation des algorithmes et code pour les pièces critiques pour les performances.
Je suis un nouvel utilisateur, donc je ne peux pas ajouter de commentaire à la réponse de Rex Kerr ci-dessus (autoriser les nouveaux utilisateurs à "répondre" mais pas à "commenter" est une règle très étrange btw).
Je me suis inscrit simplement pour répondre à l'insinuation "ouf, Java est si verbeux et si dur" de la réponse populaire de Rex ci-dessus. Bien que vous puissiez bien sûr écrire du code Scala plus concis, l'exemple Java donné est clairement gonflé. La plupart des développeurs Java coderaient quelque chose comme ceci:
List<String> bigEnough = new ArrayList<String>();
for(String s : array) {
if(s.length() > 2 && mapping.get(s) != null) {
bigEnough.add(mapping.get(s));
}
}
Et bien sûr, si nous prétendons qu'Eclipse ne fait pas l'essentiel de la saisie à votre place et que chaque caractère enregistré fait vraiment de vous un meilleur programmeur, alors vous pouvez coder ceci:
List b=new ArrayList();
for(String s:array)
if(s.length()>2 && mapping.get(s) != null) b.add(mapping.get(s));
Maintenant, non seulement j'ai économisé le temps qu'il m'a fallu pour taper des noms de variables complets et des accolades (me permettant de passer 5 secondes de plus à réfléchir à des pensées algorithmiques profondes), mais je peux également entrer mon code dans des concours d'obscurcissement et potentiellement gagner de l'argent supplémentaire pour les vacances.
Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
Écrivez votre Scala comme Java, et vous pouvez vous attendre à ce qu'un bytecode presque identique soit émis - avec des métriques presque identiques.
Écrivez-le plus «idiomatiquement», avec des objets immuables et des fonctions d'ordre supérieur, et ce sera un peu plus lent et un peu plus grand. La seule exception à cette règle empirique est que lorsque vous utilisez des objets génériques dans lesquels les paramètres de type utilisent l' @specialised
annotation, cela créera un bytecode encore plus grand qui peut dépasser les performances de Java en évitant le boxing / unboxing.
Il convient également de mentionner le fait que plus de mémoire / moins de vitesse est un compromis inévitable lors de l'écriture de code pouvant être exécuté en parallèle. Le code Scala idiomatique est de nature beaucoup plus déclarative que le code Java typique, et est souvent à seulement 4 caractères ( .par
) pour être entièrement parallèle.
Donc si
Diriez-vous alors que le code Scala est maintenant comparativement 25% plus lent, ou 3x plus rapide?
La bonne réponse dépend exactement de la façon dont vous définissez la «performance» :)
.par
c'est dans la version 2.9.
.par
.
map
méthode sera extrêmement petit.
Jeu de benchmarks de langage informatique:
Test de vitesse java / scala 1.71 / 2.25
Test de mémoire java / scala 66.55 / 80.81
Ainsi, ces tests indiquent que java est 24% plus rapide et que scala utilise 21% de mémoire en plus.
Dans l'ensemble, ce n'est pas un problème et ne devrait pas avoir d'importance dans les applications du monde réel, où la plupart du temps est consommé par la base de données et le réseau.
Bottom line: Si Scala vous rend, vous et votre équipe (et les personnes qui prennent en charge le projet lorsque vous partez) plus productifs, alors vous devriez y aller.
D'autres ont répondu à cette question en ce qui concerne les boucles serrées, bien qu'il semble y avoir une différence de performance évidente entre les exemples de Rex Kerr que j'ai commentés.
Cette réponse est vraiment destinée aux personnes qui pourraient rechercher un besoin d'optimisation en boucle serrée en tant que défaut de conception.
Je suis relativement nouveau à Scala (environ un an environ) mais la sensation, jusqu'à présent, est que cela vous permet de différer nombreux aspects de la conception, de la mise en œuvre et de l'exécution relativement facilement (avec suffisamment de lecture et d'expérimentation en arrière-plan :)
Caractéristiques de conception différées:
Fonctionnalités de mise en œuvre différée:
Fonctionnalités d'exécution différée: (désolé, pas de liens)
Ces fonctionnalités, pour moi, sont celles qui nous aident à tracer la voie vers des applications rapides et serrées.
Les exemples de Rex Kerr diffèrent quant aux aspects de l'exécution différés. Dans l'exemple Java, l'allocation de mémoire est différée jusqu'à ce que sa taille soit calculée où l'exemple Scala diffère la recherche de mappage. Pour moi, ils semblent être des algorithmes complètement différents.
Voici ce que je pense être plus un équivalent de pommes à pommes pour son exemple Java:
val bigEnough = array.collect({
case k: String if k.length > 2 && mapping.contains(k) => mapping(k)
})
Pas de collections intermédiaires, pas d' Option
instances, etc. Cela préserve également le type de collection, donc bigEnough
le type de Array[File]
- Array
l' collect
implémentation de - fera probablement quelque chose dans le sens de ce que fait le code Java de M. Kerr.
Les fonctionnalités de conception différées que j'ai répertoriées ci-dessus permettraient également aux développeurs d'API de collecte de Scala d'implémenter cette implémentation de collecte rapide spécifique à Array dans les versions futures sans casser l'API. C'est ce à quoi je fais référence en parcourant la voie de la vitesse.
Aussi:
val bigEnough = array.withFilter(_.length > 2).flatMap(mapping.get)
La withFilter
méthode que j'ai utilisée ici au lieu de filter
résoudre le problème de collecte intermédiaire, mais il y a toujours le problème d'instance Option.
Un exemple de vitesse d'exécution simple dans Scala est la journalisation.
En Java, nous pourrions écrire quelque chose comme:
if (logger.isDebugEnabled())
logger.debug("trace");
Dans Scala, c'est juste:
logger.debug("trace")
car le paramètre de message à déboguer dans Scala a le type " => String
" que je considère comme une fonction sans paramètre qui s'exécute quand il est évalué, mais que la documentation appelle pass-by-name.
EDIT {Les fonctions dans Scala sont des objets donc il y a un objet supplémentaire ici. Pour mon travail, le poids d'un objet trivial vaut la peine de supprimer la possibilité qu'un message de journal soit évalué inutilement. }
Cela ne rend pas le code plus rapide, mais cela le rend plus susceptible d'être plus rapide et nous sommes moins susceptibles d'avoir l'expérience de parcourir et de nettoyer le code d'autres personnes en masse.
Pour moi, c'est un thème cohérent au sein de Scala.
Le code dur ne parvient pas à comprendre pourquoi Scala est plus rapide, bien qu'il laisse un peu de vue.
Je pense que c'est une combinaison de réutilisation du code et du plafond de la qualité du code dans Scala.
En Java, un code génial est souvent obligé de devenir un gâchis incompréhensible et n'est donc pas vraiment viable dans les API de qualité de production, car la plupart des programmeurs ne pourraient pas l'utiliser.
J'ai de grands espoirs que Scala pourrait permettre aux einstein parmi nous de mettre en œuvre des API beaucoup plus compétentes, potentiellement exprimées par le biais de DSL. Les API de base de Scala sont déjà loin sur cette voie.
Présentation de @higherkinded sur le sujet - Considérations sur les performances de Scala qui effectue des comparaisons Java / Scala.
Outils:
Grand article de blog:
Java et Scala se compilent tous deux en bytecode JVM, donc la différence n'est pas si grande. La meilleure comparaison que vous puissiez obtenir est probablement sur le jeu des benchmarks de langage informatique , qui dit essentiellement que Java et Scala ont tous deux la même utilisation de la mémoire. Scala n'est que légèrement plus lent que Java sur certains des benchmarks répertoriés, mais cela pourrait simplement être dû au fait que la mise en œuvre des programmes est différente.
Vraiment cependant, ils sont tous les deux si proches que cela ne vaut pas la peine de s'inquiéter. L'augmentation de productivité que vous obtenez en utilisant un langage plus expressif comme Scala vaut bien plus qu'une performance minimale (le cas échéant).
Java and Scala both compile down to JVM bytecode,
qui a été combinée avec une so
à la déclaration en question que diffence isn't that big.
je voulais montrer, que le so
n'est qu'un truc rhétorique, et non une conclusion argumentative.
L'exemple Java n'est vraiment pas un idiome pour les programmes d'application typiques. Un tel code optimisé peut être trouvé dans une méthode de bibliothèque système. Mais alors il utiliserait un tableau du bon type, c'est-à-dire File [] et ne lèverait pas une IndexOutOfBoundsException. (Différentes conditions de filtre pour le comptage et l'addition). Ma version serait (toujours (!) Avec des accolades car je n'aime pas passer une heure à chercher un bogue qui a été introduit en économisant les 2 secondes pour appuyer sur une seule touche dans Eclipse):
List<File> bigEnough = new ArrayList<File>();
for(String s : array) {
if(s.length() > 2) {
File file = mapping.get(s);
if (file != null) {
bigEnough.add(file);
}
}
}
Mais je pourrais vous apporter beaucoup d'autres exemples de code Java laids de mon projet actuel. J'ai essayé d'éviter le style de codage copié et modifié commun en prenant en compte les structures et les comportements communs.
Dans ma classe de base DAO abstraite, j'ai une classe interne abstraite pour le mécanisme de mise en cache commun. Pour chaque type d'objet de modèle concret, il existe une sous-classe de la classe de base abstraite DAO, dans laquelle la classe interne est sous-classée pour fournir une implémentation de la méthode qui crée l'objet métier lorsqu'il est chargé à partir de la base de données. (Nous ne pouvons pas utiliser un outil ORM car nous accédons à un autre système via une API propriétaire.)
Ce code de sous-classification et d'instanciation n'est pas du tout clair en Java et serait très lisible dans Scala.