scala vs java, performances et mémoire? [fermé]


161

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?


3
J'ai entendu dire que la performance peut être très proche. Je soupçonne que cela dépend fortement de ce que vous faites. (comme pour Java vs C)
Peter Lawrey

La réponse à ce genre de questions est "cela dépend" - pour pratiquement toute comparaison entre le système X et le système Y. De plus, il s'agit d'un double de stackoverflow.com/questions/2479819/...
James Moore

Réponses:


262

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.


172
+1 pour ce dernier paragraphe. Il est un point essentiel qui reste hors de considération beaucoup trop souvent.
Kevin Wright

2
Je pensais que les points de vue pourraient beaucoup aider avec les problèmes que vous mentionnez. Ou n'est-ce pas vrai avec les tableaux, en particulier?
Nicolas Payette

1
@Kevin Wright - "C'est un point essentiel qui est trop souvent négligé" - C'est quelque chose de facile à dire et difficile à démontrer, et nous disons quelque chose sur les compétences de Rex Kerr et non sur ce que d'autres moins qualifiés accomplissent.
igouy

1
>> dans un style idiomatique avec des performances superlatives << Il y a de la place dans le jeu des benchmarks pour les programmes Scala idiomatiques qui n'atteignent pas des performances "superlatives".
igouy

2
@RexKerr - votre exemple Java ne recherche-t-il pas la clé de mappage deux fois pour chaque chaîne possible où votre exemple Scala ne le fait-il qu'une fois après que les chaînes ont été sélectionnées? C'est-à-dire qu'ils sont optimisés de différentes manières pour différents ensembles de données?
Seth

103

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.


7
Comment se fait-il que vous ne soyez pas membre du club «langue branchée du mois»? Bons commentaires. J'ai particulièrement apprécié la lecture du dernier paragraphe.
stepanian

21
Superbement mis! Je me lasse des exemples artificiels où le code Java gonflé est suivi d'un exemple laconique et soigneusement construit de Scala (ou d'un autre langage FP), puis d'une conclusion tirée à la hâte que Scala doit être meilleure que Java à cause de cela. Qui a jamais écrit quelque chose d'important dans Scala de toute façon! ;-) Et ne dites pas Twitter ...
chrisjleu

2
Eh bien, la solution de Rex préalloue la mémoire pour le tableau, ce qui accélérera l'exécution du code compilé (car avec votre approche, vous laissez la JVM réallouer périodiquement votre tableau à mesure qu'il grandit). Même s'il y avait plus de frappe impliquée, en termes de performances, cela pourrait être un gagnant.
Ashalynd

5
pendant que nous y sommes, en java8 ce sera:Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
bennyl

2
Ce qui rend Scala "meilleur" à certains égards que Java, ce sont les capacités étendues du système de types qui facilitent l'expression de modèles plus génériques en tant que types (tels que Monades, Functors, etc.). Cela vous permet de créer des types qui ne vous gênent pas en raison de contrats trop stricts, comme cela se produit souvent en Java. Des contrats stricts non basés sur des modèles réels dans le code sont la raison pour laquelle les modèles d'inversion de responsabilité sont nécessaires juste pour tester correctement votre code (l'injection de dépendance vient à l'esprit en premier et l'enfer XML qu'elle apporte). Le addl. la concision qu'apporte la flexibilité n'est qu'un bonus.
josiah

67

É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' @specialisedannotation, 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

  • Le code Scala prend 1,25 fois plus de temps que le code Java dans un seul thread
  • Il peut être facilement divisé en 4 cœurs (maintenant courant même sur les ordinateurs portables)
  • pour un temps d'exécution en parallèle de (1,24 / 4 =) 0,3125x le Java d'origine

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» :)


4
Incidemment, vous voudrez peut-être mentionner que .parc'est dans la version 2.9.
Rex Kerr

26
>> Diriez-vous alors que le code Scala est maintenant comparativement 25% plus lent, ou 3x plus rapide? << Je dirais pourquoi votre comparaison hypothétique avec le code Java multithread?
igouy

17
@igouy - Le fait est que ledit code hypothétique n'existe pas, la nature impérative du code Java "plus rapide" rend la parallélisation beaucoup plus difficile, de sorte que le rapport coût / bénéfice signifie qu'il est peu probable que cela se produise. Idiomatic Scala, d'autre part, étant de nature beaucoup plus déclarative, peut souvent être rendue concurrente avec rien de plus qu'un changement insignifiant.
Kevin Wright

7
L'existence de programmes Java simultanés n'implique pas qu'un programme Java typique puisse être facilement adapté à la concurrence. Si quoi que ce soit, je dirais que le style de jointure en fourche particulier est particulièrement rare en Java et doit être explicitement codé, alors que des opérations simples telles que la recherche de la valeur minimale contenue ou la somme des valeurs dans une collection peuvent être effectuées de manière triviale en parallèle dans Scala en utilisant simplement .par.
Kevin Wright

5
Non, je pourrais pas. Ce genre de chose est un élément fondamental pour de nombreux algorithmes, et le voir présent à un niveau aussi bas dans le langage et les bibliothèques standard (les mêmes bibliothèques standard que tous les programmes utiliseront, pas seulement les classiques) est la preuve que vous ' sont déjà plus proches d'être simultanés en choisissant simplement la langue. Par exemple, le mappage sur une collection est intrinsèquement adapté à la parallélisation, et le nombre de programmes Scala qui n'utilisent pas la mapméthode sera extrêmement petit.
Kevin Wright

31

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.


34
Taille du code java / scala 3.39 / 2.21
hammar

22
Soyez prudent avec des nombres comme ceux-ci, ils semblent terriblement précis alors qu'en réalité ils ne signifient presque rien. Ce n'est pas comme si Scala était toujours 24% plus rapide que Java en moyenne, etc.
Jesper

3
Les chiffres cités par Afaik indiquent le contraire: Java est 24% plus rapide que scala. Mais comme vous le dites, ce sont des microbenchmarks, qui n'ont pas besoin de correspondre à ce qui se passe dans de vraies applications. Et la manière différente ou la solution du problème dans différentes langues pourrait conduire à des programmes moins comparables à la fin.
utilisateur inconnu

9
"Si Scala fait de vous et de votre équipe ..." Conclusion: vous le saurez après pas avant :-)
igouy

La page d'aide du jeu de référence fournit un exemple de la façon de «comparer la vitesse et la taille du programme pour 2 implémentations linguistiques». Pour Scala et Java, la page Web de comparaison appropriée est - shootout.alioth.debian.org/u64q/scala.php
igouy

20

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)

  • Valeurs paresseuses thread-safe
  • Pass-by-name
  • Trucs monadiques

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' Optioninstances, etc. Cela préserve également le type de collection, donc bigEnoughle type de Array[File]- Arrayl' collectimplé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 withFilterméthode que j'ai utilisée ici au lieu de filterré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.


Vos données de journalisation sont un bon exemple des pièges de performance de Scala: logger.debug ("trace") crée un nouvel objet pour la fonction sans paramètre.
jcsahnwaldt réintègre Monica

En effet - comment cela affecte-t-il mon point associé?
Seth

Les objets mentionnés ci-dessus peuvent également être utilisés pour créer des structures de contrôle IoC transparentes par souci d'efficacité. Oui, le même résultat est théoriquement possible en Java, mais ce serait quelque chose qui affecterait / obscurcirait considérablement la façon dont le code est écrit - d'où mon argument selon lequel le talent de Scala pour différer de nombreux éléments du développement logiciel nous aide à progresser vers un code plus rapide - plus susceptible d'être plus rapide en pratique vs avoir des performances de l'unité légèrement plus rapides.
Seth

Ok, j'ai relu ceci et j'ai écrit, "vitesse d'exécution simple" - je vais ajouter une note. Bon point :)
Seth

3
Instruction if prévisible (essentiellement gratuite sur un processeur superscalaire) vs allocation d'objets + garbage. Le code Java est évidemment plus rapide (notez qu'il n'évalue que la condition, l'exécution n'atteindra pas l'instruction de journal.) En réponse à "Pour mon travail, le poids d'un objet trivial vaut la peine de supprimer la possibilité qu'un message de journal soit évalué inutilement . "
Eloff


10

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).


7
Je vois une erreur logique ici: les deux langages se compilent en bytecode, mais un programmeur expérimenté et un débutant - leur code se compile également en bytecode - mais pas le même bytecode, donc la conclusion, que la différence ne peut pas être si grande , peut être faux. Et en fait, dans le passé, une boucle while pouvait être beaucoup, beaucoup plus rapide en scala qu'une boucle for sémantiquement équivalente (si je me souviens bien, c'est beaucoup mieux aujourd'hui). Et les deux ont été compilés en bytecode, bien sûr.
utilisateur inconnu

@user unknown - "une boucle while pourrait être beaucoup, beaucoup plus rapide dans scala qu'une boucle for sémantiquement équivalente" - remarquez que ces programmes de jeu de benchmarks Scala sont écrits avec des boucles while.
igouy

@igouy: Je n'ai pas parlé des résultats de ce microbenchmark, mais de l'argumentation. Une déclaration vraie 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 son'est qu'un truc rhétorique, et non une conclusion argumentative.
utilisateur inconnu

3
réponse étonnamment incorrecte avec des votes étonnamment élevés.
shabunc

4

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.