10
votes

Question de performance Scala

dans le article écrit par Daniel Korzekwa , il a déclaré que la performance du code suivant: xxx

est bien pire que la solution itérative écrite à l'aide de Java.

Peut-on expliquer pourquoi? Et quelle est la meilleure solution pour ce code dans Scala (j'espère que ce n'est pas une version itérative Java qui est scala-fied)?


0 commentaires

7 Réponses :


13
votes

essentiellement, il traverse une liste deux fois. Une fois pour multiplier chaque élément avec deux. Et ensuite une autre fois pour filtrer les résultats.

Pensez-y comme une fois la boucle produisant une liste liée avec les résultats intermédiaires. Et puis une autre boucle en appliquant le filtre pour produire les résultats finaux. P>

Ceci devrait être plus rapide: P>

list.view.map(e => e * 2).filter(e => e > 10).force


2 commentaires

Cette réponse manque le point, bien que la solution soit correcte.


Je pourrais être intelligent maintenant et signaler que nulle part dans le code de l'échantillon, il indique qu'il s'agit d'INTS. Mais je dois admettre que la réponse aurait été bien meilleure si elle aurait mentionné qu'il y ait un peu de boxe et de transbordement.



15
votes

La raison que le code particulier est lent, c'est que cela fonctionne sur les primitives, mais il utilise des opérations génériques, les primitives doivent donc être en boîte. (Cela pourrait être amélioré si liste et ses ancêtres étaient spécialisés.) Cela va probablement ralentir les choses d'un facteur de 5 ou plus.

aussi, algorithmiquement, ces opérations sont un peu chères, car vous Effectuez une liste complète, puis effectuez une nouvelle liste qui jetait quelques composants de la liste intermédiaire. Si vous l'avez fait en un seul coup, alors vous seriez mieux. Vous pouvez faire quelque chose comme: xxx

mais si le calcul e * 2 est vraiment cher? Ensuite, vous pourriez xxx

sauf que cela apparaîtrait en arrière. (Vous pouvez l'inverser si besoin d'être, mais cela nécessite de créer une nouvelle liste, qui n'est à nouveau pas idéal algorithmique.)

Bien sûr, vous avez le même type de problèmes algorithmiques dans Java si vous êtes À l'aide d'une liste liée à des liens, votre nouvelle liste se retrouvera en arrière ou vous devez la créer deux fois, d'abord à l'envers, puis vous devez la construire avec une récursion (non-queue) (qui est facile à Scala, Mais invisible pour ce type de chose dans l'une ou l'autre langue puisque vous allez épuiser la pile), ou vous devez créer une liste mutable, puis prétendre ensuite que ce n'est pas mutable. (Qui, accessoirement, vous pouvez faire en scala également - voir mutable.Linkedlist .)


3 commentaires

Cette réponse obtient le problème, mais n'offre aucune solution agréable.


@RAPHAEL - Il n'y a pas vraiment que l'état actuel de la bibliothèque. Affichage / FORCE ne vous sauve pas lorsque vous travaillez avec Premi-informer.


Si E * 2 était coûteux, le coût de la mesure intermédiaire diminuerait. Peut-être que des problèmes de mémoire, si vous faites face à de grandes quantités de données.



2
votes

REX KERR indique correctement le problème majeur: Utilisation de listes immuables, la pièce de code indiquée crée des listes intermédiaires en mémoire. Notez que ce n'est pas nécessairement plus lent que le code Java équivalent; Vous n'utilisez jamais de données de données immuables dans Java.

Wilfried Springer a une belle solution Idomatic Scala. Utilisation de Voir , aucune copie (manipulé) de la liste complète est créée.

Notez que l'utilisation de la vue pourrait ne pas toujours être idéale. Par exemple, si votre premier appel est Filtre qui devrait jeter la majeure partie de la liste, il est peut-être intéressant de créer une version plus courte explicitement et d'utiliser voir seulement après cela. Afin d'améliorer la localité de mémoire pour les itérations ultérieures.


3 commentaires

Ce n'est pas une réponse. C'est un commentaire sur deux autres réponses réelles.


Oh, et peut-être que vous n'utilisez jamais de structures de données immuables en Java. En fait, je les utilise beaucoup le cas échéant.


1) J'ai trouvé les deux réponses référencées sans individuellement, j'ai donc décidé de poster une réponse commune. Le fait que je donne des références appropriées ne fait pas cela un commentaire. 2) la norme Cadre de collections Java semble Pour être assez courte sur des implémentations immuables, fournissant uniquement un wrapper non modifiable (! = immuable). Peut-être que c'est pourquoi.



4
votes

L'approche Scala est beaucoup plus abstraite et générique. Par conséquent, il est difficile d'optimiser chaque cas.

Je pouvais imaginer que le compilateur JIT Hotspot JIT pourrait appliquer des flux et une boucle-fusion au code à l'avenir s'il voit que les résultats immédiats ne sont pas utilisés. P> En outre, le code Java fait juste beaucoup plus. p>

Si vous voulez vraiment simplement muter sur une source de données, considérez transformer code>. Il semble un peu comme mappe code> mais ne crée pas une nouvelle collection, e. G.:

val array = Array(1,2,3,4,5,6,7,8,9,10).transform(_ * 2)
// array is now WrappedArray(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)


0 commentaires

3
votes

Pour éviter de traverser la liste deux fois, je pense que la syntaxe pour est une belle option ici: xxx


0 commentaires

6
votes

La solution réside principalement avec JVM. Bien que Scala ait une solution de contournement dans la figure de @Specialization , qui augmente énormément la taille de toute classe spécialisée et ne résout que la moitié du problème - l'autre moitié étant la création d'objets temporaires.

Le JVM fait un bon travail optimisant beaucoup d'autres, ou la performance serait encore plus terrible, mais Java n'exige pas les optimisations que Scala le fait, donc JVM ne les fournit pas. Je m'attends à ce que cela change dans une certaine mesure avec l'introduction de Sam Not-Real-fermetures de Java.

Mais, à la fin, il s'agit d'équilibrer les besoins. Le même tandis que boucle que Java et Scala font beaucoup plus vite que l'équivalent de fonction Scala puisse être fait plus rapidement dans C. Pourtant, malgré ce que les microbenchmans nous disent, les gens utilisent Java.


0 commentaires

1
votes

list.filter (E => E * 2> 10) .MAP (E => E * 2)

Cette tentative réduit la première liste. Donc, la deuxième traversée est sur moins d'éléments.


0 commentaires