My conference at USI 2014 (http://www.usievents.com/en). Explaining how we arrive to reactive programming, why we should care and what to do about it.
In French
5. www.usievents.com #USI2014
Un peu d’histoire
Lisp (1958) Clojure (2007)
Parallel.forEach
(2010)
Async IO
(2006)
Loi d’Amdahl
(1967)
Real-time
(1965)
Reactive
(2012)
43 ans
49 ans
47 ans
10. www.usievents.com #USI2014
Cooperative Multitasking
UNIX Mainframe
Multiprogramming
Je n’ai plus
besoin du CPU.
Je le tend à qui
veut bien
Je le prend.
MerciProcessus 1
Processus 2
Je fais un IO, à
qui le tour?
Moi!
Processus 1
Processus 2
43. www.usievents.com #USI2014
NIO
Avantage
Améliore sensiblement la scalabilité d’un serveur
d’application
Désavantage
Doit être supporté par les frameworks
Recommandation: Allez-y
Je vais faire un IO, rappelle-moi
quand c’est fini. En attendant je
fais d’autre chose Processus 1
D’accord
44. www.usievents.com #USI2014
Parallèle
Avantage
Réduit les temps de réponse d’un système peu sollicité
Maximise l’utilisation CPU
Désavantage
Algorithmie plus complexe
Recommandation: Allez-y… au besoin Yep!
Les gars, voici chacun un peu de
boulot. Dites-moi quand c’est fini.
Je vous attend Processus 1
45. www.usievents.com #USI2014
Fonctionnel
Avantage
Implicitement thread-safe
Diminue la quantité de code
Facilement parallélisable
Évaluation paresseuse
Désavantage
Rapidement illisible si surutilisé
Lecture du code plus complexe pour les non initiés
Recommandation: Allez-y. Avec retenu
Bien sûr
Tu pourrais effectuer cette
opération sur toutes ces données
et me donner le résultat?
Méthode 1 Méthode 2
46. www.usievents.com #USI2014
Immutable
Avantage
Élimine les risques de contention
Facilite les traitements parallèles
Désavantage
Augmente le travail du GC
Les structures de données doivent être compatible
Recommandation: Allez-y, sans vous forcer
Tout de suite
Voici des données. Tu m’en
donnes une copie en appliquant
cette transformation?
Méthode 1
Méthode 2
47. www.usievents.com #USI2014
Asynchrone
Avantage
Réduit les temps de réponse
Meilleure exploitation des CPUs
Désavantage
Code plus complexe si utilisé extensivement
Mais ça s’améliore par l’évolution des langages
Recommandation: Allez-y en opportuniste
Yep!
Hop, toi fais ça, et hop toi fais ça.
Dites-moi quand c’est fini Processus 1
Aujourd’hui je vais vous faire parcourir l’histoire de l’informatique pour nous permettre de mieux comprendre les nouveaux paradigmes de programmation que nous voyons émerger depuis quelques années.
Ce qui est étonnant c’est que certains concepts proviennent du fin fond de l’aube de l’informatique, sont tombés partiellement dans l’oublie.
Loi d’Amdahl
Par exemple, …
Mais pourquoi maintenant, pourquoi rien pendant 40 ans et soudain on se découvre plein de nouveaux besoins
Presque tous les développements d’aujourd’hui sont sur x86
Au début du x86, tout était simple
On lance un processus à la fois, il prend des données en entrées et en fait quelquechose
La vie était belle et simple
Évidemment, hors du x86, on utilisait depuis déjà 20 ans des paradigmes plus compliqués
Ken Thompson et Dennis Ritchie
Multiprogramming:
Un programme tourne et on passe au suivant lors d’un IO
Cooperative multitasking (CICS par exemple):
Un programme tourne et laisse la main des fois pour permettre aux autres de s’exécuter. S’il ne la laisse pas, personne ne peut lui enlever
Windows 3.1
Le premier OS maintenant améliorant la situation fut Windows 95 et Windows NT 3.1.
Il faisait du preemptive multitasking.
Évidemment, les systèmes Unix utilisaient eux aussi ce système sur une architecture SMP
Par exemple, cgi-bin. Un processus reçoit un appel HTTP et fork un processus correspondant à l’URL en passant des variables.
Le problème c’est qu’il y a évidemment beaucoup d’overhead à faire des fork. Donc on a eu une autre idée
Pourquoi ne pas faire des threads. Le thread, c’est plein de traitements parallèles mais au sein d’un même processus et partagent la même mémoire. Il n’est plus nécessaire de créer un processus complet, uniquement une nouvelle stack d’appel.
Le problème c’est que des OS comme Solaris n’implémentait pas de threads natifs. Donc, par exemple, lors de la sortie de Java, la JVM sur Solaris utilisait un concept de green threads. C’est la JVM qui gérait le time sharing et non l’OS. Évidemment, ça causait des problèmes. Par exemple, il fallait traiter les IO de façon asynchrone pour ne pas gelé un processus.
Donc sommes donc rapidement passé au native threads.
C’est l’OS qui s’occupe du time sharing des threads. Cela ralentit la synchronisation et la création mais améliore sensiblement la gestion des IO et le context switching.
On a donc plusieurs processus sur lesquels l’OS distribue les threads des différents processus en tentant de maximiser l’utilisation CPU.
Par exemple, si un thread est sur un I/O, pas de soucis, un autre thread prend le relai.
Il suffit d’ajouter des threads pour avoir une scalabilité infinie. Pour le reste, la loi de Moore devrait nous assurer que dans le futur, l’augmentation des besoins CPUs sera couverts
SMP = Symmetric multiprocessing
Le nombre de transistors double tous les 2 ans et par extension la puissance des ordinateurs
D’ailleurs, l’architecture de Java en général comptait beaucoup sur la loi de Moore.
Plusieurs décisions étaient sciemment lentes pour couvrir un maximum de besoins en se disant que rapidement, cette lenteur se fondrait dans la loi de Moore
Et c’est là que le couperet est tombé.
On a atteint les limites de fréquences possibles pour un ordinateur. Et dans un ordinateur, ce n’est pas vraiment le nombre de transistors qui est important, c’est la fréquence. La fréquence donne la vitesse de passage des portes. Si on ne peut pas augmenter la vitesse de passage des portes, ça ne sert à rien d’ajouter des portes.
Donc, les fondeurs n’ont pas trop eu le choix de changer de stratégie.
Le nombre de transistor continue d’augmenter mais plus la fréquence. À la place, les transistors sont déplacés sur plusieurs cœurs.
D’où l’apparition du Dual Core d’Intel en 2007
Le traitement parallèle n’était plus limité aux serveurs, il était maintenant pour tous
À noter, ce n’est pas une problème de scalabilité.
Par exemple, pour un serveur web, chaque requête est traitée par un thread qui lui-même est assigné à un CPU.
Donc, il suffit d’ajouter des CPUs, peu importe leurs puissance. Mais ça n’améliore pas mon temps de réponse.
Si on zoom, un requête c’est ça
Évidemment, je simplifie un peu. Les fabricants trouvent des « trucs ». Ils augmentent le cache, les algorithmes de prédiction.
Mais ce n’est que du patch. Rien de vraiment révolutionnaire
Une deuxième fois pour être bien sûr que c’est intégré.
On a surfé plus de 30 ans sur la vague de la loi de Moore et c’est maintenant terminé.
Donc, quand les développeurs s’en sont rendus compte, ils se sont mis à la recherche de solution
Et ça tombe bien, la passé est plein de solutions
Mais tombé, jetons un coup d’œil sur ce qu’est vraiment une requête
Un requête vu à la loupe ça ressemble à ça.
On remarque deux choses:
1- On attend sur des IOs
2- La fréquence étant désormais constante, il n’y a pas de raison pour que les opérations CPU d’une requête soient plus rapides dans le futur
Et si on zoom encore, on voit la chose suivante.
En pratique, l’opération 1 effectue un IO, l’OS réquisitionne le CPU pour le donner à quelqu’un d’autre, l’IO termine et éventuellement, l’OS redonne la main au CPU.
Deux autres constatations:
1- Le context switching a un coût CPU
2- Le temps de récupération après IO a un coût
Et si on zoom encore, on voit la chose suivante.
En pratique, l’opération 1 effectue un IO, l’OS réquisitionne le CPU pour le donner à quelqu’un d’autre, l’IO termine et éventuellement, l’OS redonne la main au CPU.
Deux autres constatations:
1- Le context switching a un coût CPU
2- Le temps de récupération après IO a un coût
Il est assez facile de réaliser que plus on a de threads, plus on a d’overhead dû au context switching.
Il y a donc une limite
On voit maintenant un peu mieux comment ça marche,
Il nous faut maintenant des solutions, des outils pour améliorer nos performances
Les différentes opérations d’une requête serveur sont traités en parallèles pour améliorer les temps de réponse
Évidemment, ça ne marche que si le serveur n’est pas trop chargé. Si le CPU d’à côté est en train de traiter la requête de quelqu’un d’autre, il n’aura pas le temps de traiter la notre.
Non-blocking IO ou asynchronous IO
Au lieu d’attendre bêtement les fins d’un IO, on fait d’autre chose en attendant d’être notifié de la fin de l’IO
Évidemment, pour arriver à faire ça, on doit avoir la collaboration de la libraire effectuant l’IO et de l’OS.
On utilise 1 thread par CPU. Chaque thread effectue du traitement de tâches à partir des tâches disponibles dans une file de tâche
La gestion du multithreading et le partage de données augmente trop la complexité
D’autres, moins extrême n’étaient pas contre le multithreading mais contre la mutabilité des données
Car on peut partager des données immutables sans danger
Et les ingénieurs, quand ils ont des problèmes, ils ont des solutions
Et évidemment, quand il y a un problème, il y a toujours un outil disponible pour le résoudre
PPR: Stratégie différente, intégration dans les OS, langages, frameworks, etc
Aussi dans les navigateurs
Un exemple plus raisonnable
Un exemple plus raisonnable
Slide sur le fait que les langages évoluent
En conclusion
One-liner
On parle ici d’immutabilité des données en mémoire. Pas des données persistées
Si vous voulez améliorer vos performances ou diminuer vos coûts d’infrastructure, c’est la seule solution.
Par contre, ça reste relativement compliqué, les technologies et outils ne sont pas encore tous prêts.
Mais ça vaut quand même la peine de tester sur un petit projet avec des problématiques de performance.
C’est ce qui a amené le concept de programmation réactive.
C’est une énorme buzzword qui constitue en fait un pieu vieux.
Il consiste à appliquer toutes les nouvelles solutions provenant du passer afin d’atteindre les objectifs suivants
Réactive:
1- Tout en async (orienté évènement)
2- Pas de points de contention (immutable)
3- Un seul thread par hard-thread (moins d’overhead)
4- Scalabilité horizontale infinie
C’est donc bien évidemment un gros buzzword mais derrière lequel se cachent de vraies valeurs à respecter pour obtenir de meilleures applications.
De plus en plus de devices connectés et donc de sollicitation serveur. La granularité des requêtes diminues.
- Petits applications bureautiques: Ne pas faire
- Propension à augmenter en puissance: Go, avec aide et outillage