Je suis nouveau sur Java 8. Je ne connais toujours pas l'API en profondeur, mais j'ai fait un petit benchmark informel pour comparer les performances de la nouvelle API Streams avec les bonnes anciennes Collections.
Le test consiste à filtrer une liste de Integer
, et pour chaque nombre pair, calculer la racine carrée et la stocker dans un résultat List
deDouble
.
Voici le code:
public static void main(String[] args) {
//Calculating square root of even numbers from 1 to N
int min = 1;
int max = 1000000;
List<Integer> sourceList = new ArrayList<>();
for (int i = min; i < max; i++) {
sourceList.add(i);
}
List<Double> result = new LinkedList<>();
//Collections approach
long t0 = System.nanoTime();
long elapsed = 0;
for (Integer i : sourceList) {
if(i % 2 == 0){
result.add(Math.sqrt(i));
}
}
elapsed = System.nanoTime() - t0;
System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Stream approach
Stream<Integer> stream = sourceList.stream();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Parallel stream approach
stream = sourceList.stream().parallel();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
}.
Et voici les résultats pour une machine dual core:
Collections: Elapsed time: 94338247 ns (0,094338 seconds)
Streams: Elapsed time: 201112924 ns (0,201113 seconds)
Parallel streams: Elapsed time: 357243629 ns (0,357244 seconds)
Pour ce test particulier, les flux sont environ deux fois plus lents que les collections, et le parallélisme n'aide pas (ou je l'utilise de la mauvaise manière?).
Des questions:
- Ce test est-il juste? Ai-je fait une erreur?
- Les flux sont-ils plus lents que les collections? Quelqu'un a-t-il fait une bonne référence formelle à ce sujet?
- Quelle approche dois-je miser?
Résultats mis à jour.
J'ai exécuté le test 1k fois après le préchauffage de la JVM (1k itérations) comme conseillé par @pveentjer:
Collections: Average time: 206884437,000000 ns (0,206884 seconds)
Streams: Average time: 98366725,000000 ns (0,098367 seconds)
Parallel streams: Average time: 167703705,000000 ns (0,167704 seconds)
Dans ce cas, les flux sont plus performants. Je me demande ce que l'on observerait dans une application où la fonction de filtrage n'est appelée qu'une ou deux fois pendant l'exécution.
la source
IntStream
place?toList
devrait s'exécuter en parallèle même s'il est collecté dans une liste non thread-safe, car les différents threads seront collectés dans des listes intermédiaires confinées aux threads avant d'être fusionnés.Réponses:
Arrêtez d'utiliser
LinkedList
pour autre chose que la suppression lourde du milieu de la liste à l'aide d'itérateur.Arrêtez d'écrire du code d'analyse à la main, utilisez JMH .
Bonnes références:
Résultat:
Tout comme je m'attendais à ce que la mise en œuvre du flux soit assez lente. JIT est capable d'incorporer tous les éléments lambda mais ne produit pas de code aussi parfaitement concis que la version vanilla.
En général, les flux Java 8 ne sont pas magiques. Ils ne pouvaient pas accélérer les choses déjà bien implémentées (avec, probablement, des itérations simples ou des instructions for-each de Java 5 remplacées par des appels
Iterable.forEach()
etCollection.removeIf()
). Les flux concernent davantage la commodité et la sécurité du codage. Commodité - compromis de vitesse fonctionne ici.la source
@Benchmark
place de@GenerateMicroBenchmark
1) Vous voyez le temps inférieur à 1 seconde en utilisant votre benchmark. Cela signifie que les effets secondaires peuvent avoir une forte influence sur vos résultats. Alors, j'ai augmenté votre tâche 10 fois
et a exécuté votre benchmark. Mes résultats:
sans edit (
int max = 1_000_000
) les résultats étaientC'est comme vos résultats: le flux est plus lent que la collecte. Conclusion: beaucoup de temps a été consacré à l'initialisation du flux / à la transmission des valeurs.
2) Après avoir augmenté, le flux de tâches est devenu plus rapide (ce n'est pas grave), mais le flux parallèle est resté trop lent. Qu'est-ce qui ne va pas? Remarque: vous avez
collect(Collectors.toList())
dans votre commande. La collecte dans une seule collection introduit essentiellement un goulot d'étranglement et une surcharge de performances en cas d'exécution simultanée. Il est possible d'estimer le coût relatif des frais généraux en remplaçantPour les flux, cela peut être fait par
collect(Collectors.counting())
. J'ai obtenu des résultats:C'est pour une grosse tâche! (
int max = 10000000
) Conclusion: la collecte des articles à la collecte a pris la majorité du temps. La partie la plus lente est l'ajout à la liste. BTW, simpleArrayList
est utilisé pourCollectors.toList()
.la source
collect(Collectors.toList())
dans votre commande, c'est-à-dire qu'il peut y avoir une situation où vous devez adresser une seule collection par plusieurs threads. " Je suis presque sûr que celatoList
rassemble plusieurs instances de liste différentes en parallèle. Ce n'est qu'à la dernière étape de la collection que les éléments sont transférés dans une liste, puis renvoyés. Il ne devrait donc pas y avoir de surcharge de synchronisation. C'est pourquoi les collecteurs ont à la fois une fonction fournisseur, un accumulateur et une fonction combinateur. (Cela pourrait être lent pour d'autres raisons, bien sûr.)collect
mise en œuvre ici. Mais à la fin, plusieurs listes devraient être fusionnées en une seule, et il semble que la fusion soit l'opération la plus lourde dans l'exemple donné.Je change un peu le code, j'ai couru sur mon mac book pro qui a 8 cœurs, j'ai obtenu un résultat raisonnable:
Collectes: Temps écoulé: 1522036826 ns (1,522037 secondes)
Streams: Temps écoulé: 4315833719 ns (4,315834 secondes)
Flux parallèles: Temps écoulé: 261152901 ns (0,261153 secondes)
la source
Pour ce que vous essayez de faire, je n'utiliserais de toute façon pas les API Java classiques. Il y a une tonne de boxe / déballage en cours, donc il y a une énorme surcharge de performance.
Personnellement, je pense que beaucoup d'API conçues sont de la merde car elles créent beaucoup de déchets d'objets.
Essayez d'utiliser un tableau primitif de double / int et essayez de le faire avec un seul thread et voyez quelles sont les performances.
PS: Vous voudrez peut-être jeter un œil à JMH pour vous occuper de faire le benchmark. Il prend en charge certains des pièges typiques tels que le réchauffement de la JVM.
la source