JAVA 8 – Parallel Stream – Performance : les types de données

En Java, pour savoir ce qui peut avoir un impact sur les performances, il est nécessaire de comprendre ce qui se passe lorsqu’on lance un traitement en parallèle avec les streams. C’est l’objet de cet article.
Pierre LISERONMis à jour le 13 Mai 2014

Lutilisation des streams en mode parallèle est très simple dutilisation. En revanche, il nest pas forcément évident de savoir si un traitement particulier va bénéficier, ou non, dune amélioration de performance lors du passage en parallèle. Pour savoir ce qui peut avoir un impact sur les performances, il est nécessaire de comprendre ce qui se passe lorsquon lance un traitement en parallèle avec les streams. Cest lobjet de cet article.

Pourquoi utiliser les STREAMS en parallèle?

La vitesse des processeurs actuels ne saméliore plus de manière aussi rapide que par le passé. Les nouvelles architectures processeur se basent davantage sur la multiplication des cœurs CPU que sur la fréquence. Avant JAVA 7 puis 8 il était très difficile de bénéficier de ces nouvelles architectures. La création de threads étant un processus très complexe et assez dangereux car difficile à débugger, elle était souvent bannie des développements habituels. Grâce à la nouvelle APIUne API est un programme permettant à deux applications distinctes de communiquer entre elles et d’échanger des données. Stream de JAVA 8 il est devenu presque « trivial » de bénéficier des architectures multicœurs. Il est donc temps dutiliser nos puissantes machines!

Le fonctionnement de lAPI Stream

Le fonctionnement de lAPI Stream est un traitement de type « fork / join ». Disponible depuis JAVA 7, ce mode de programmation a été intégré de manière transparente dans lAPI STREAM. Il faut bien comprendre ce mécanisme, même sans devenir expert, pour en tirer pleinement parti !

Fork / Join: Fonctionnement général

La complexité dun problème dépend de deux facteurs : la « difficulté » pour résoudre le problème et la taille de lentrée. La combinaison des deux sexprime souvent par une fonction de la forme O(n) où est n est la taille de lentrée du problème.

Lobjectif des algorithmes de type fork / join est de diviser le problème en sous-problèmes de taille inférieure qui sont traités par des processus différents puis de fusionner les résultats pour obtenir le résultat final.

En clair, pour réaliser du multi-threading, il faut diviser le problème en sous-problèmes qui sont eux même répartis sur les différents cœurs de la machine. Plus que le problème, cest lensemble dentrée du problème qui est divisé. Par exemple, si on souhaite traiter un tableau déléments, ce tableau donné en entrée de lalgorithme va être divisé en petits tableaux qui seront donnés aux différents cœurs (CPU) de la machine (phase de fork). Une fois que chaque cœur (CPU) a réalisé son traitement sur sa partie (sous-ensemble du problème de départ) vient la phase de join pour « fusionner » les résultats issus des traitements.  Nous allons voir ici que cette phase de fork joue un rôle déterminant dans les performances des streams parallèles.

Phase de fork: Importance du type de données

Le rôle de la phase de fork est primordial dans les performances. En effet, découper lespace dentrée du problème dépend fortement de la structure de données. Par exemple, découper une ArrayList (qui est représentée dans un tableau) peut être réalisé en temps constant. En effet, chaque élément peut être accédé de manière unitaire et en temps constant avec une complexité de O(1).

En revanche, un TreeSet ou un HashSet ne peuvent pas être découpés de manière simple et rapide, ce qui peut impliquer un défaut de performance important. Lexemple le plus marquant est la LinkedList. Une liste chaînée est très complexe à découper car il faut la parcourir pour la découper aux bons endroits.

Ceci peut paraître un peu contre-intuitif mais plus on réfléchit à la structure de données, plus il devient clair que la phase de fork dépend fortement de la capacité à diviser facilement lensemble de départ.

En conclusion sur la phase de fork, une ArrayList savère plus performante car facilement découpable en sous ArrayList. Ceci nimplique pas que les traitements seront toujours plus rapides avec des ArrayList car comme nous lavons dit dans larticle précédent, lutilisation dArrayList oblige les streams à garder lordre ce qui est très préjudiciable en terme de performance, par opposition à un Set (qui lui na pas dordre). Il est donc important de prendre tous ces facteurs en compte avant de choisir le type de données en entrée et en sortie de notre Stream.