Eric VerniéEric Vernié - relation technique développeurs

Un mot sur les technologies parallèles chez Microsoft


Document de référence
Révision 1.0

Table des matières


Introduction Introduction
Technologies d’implémentation Technologies d’implémentation
Les threads Les threads
Les pools de threads Les pools de threads
OpenMP (Open Multi-Processing) OpenMP (Open Multi-Processing)
MPI (Message Passing Interface) MPI (Message Passing Interface)
Extension Parallèle pour le Framework .NET Extension Parallèle pour le Framework .NET
Parallel Pattern Library/Agent Library/Data Structure Parallel Pattern Library/Agent Library/Data Structure
Threading Building Block© Intel Threading Building Block© Intel
CUDA© (NVIDIA) CUDA© (NVIDIA)
ATI Stream © ATI Stream ©
Microsoft Direct X 11 Compute Shader Microsoft Direct X 11 Compute Shader
Quelles Technologies pour quelles applications ? Quelles Technologies pour quelles applications ?
Système non parallèle, non distribuée Système non parallèle, non distribuée
Système Parallèle, non distribué Système Parallèle, non distribué
Système Parallèle, distribué Système Parallèle, distribué
Conclusion Conclusion


Introduction

Avec l’arrivée massive des multi-cœurs sur les PC de bureaux et sur les portables, le développement parallèle devient incontournable qu’on le veuille ou non.

C’est une réflexion qu’il faut désormais intégrer (au même titre que la sécurité) dans tous développements. Charge à vous ensuite de déterminer si dans l’application il y a des parties de code qui peuvent profiter de cette puissance.

Réservé jusqu’à présent à un certain nombre d’experts qui développaient, soient des jeux, des applications de manipulation vidéos, des applications très spécifiques pour du calcul scientifique dans le monde du HPC (High Performance Computing), ou pour des applications serveurs telles que des bases de données, des serveurs Web, ou de messageries, il apporte pour nous développeurs d’applications traditionnelles de gestion son lots de complexités auxquelles nous devront faire face.

Microsoft, travaillent sur un certain nombre de nouvelles technologies qui devraient pouvoir nous aider, dans notre quotidien, afin de simplifier le développement. Mais avant l’arrivée de ces nouvelles technologies, il existe aujourd’hui pléthore de technologies et librairie à utiliser.

Technologies d’implémentation

En tant que développeur novice dans ce domaine, vous pouvez à juste titre, en premier lieu, vous poser la question mais quelle technologie dois-je utiliser ?

Comme dans tous développements, c’est prendre le problème à l’envers, et ce problème sera d’autant plus exacerber avec le développement parallèle, car une mauvaise construction architecturale, un mauvais choix de modèle de conception peut vous amener dans le mur.

Il faut bien garder à l’esprit, qu’à la différence d’une application traditionnelle mal conçue qui profitait quand même de la puissance accrue des PCs (loi de Moore), une application parallèle mal conçue au départ, ne pourra sans doute pas profiter de l’ajout de processeurs donc de l’accroissement de puissance.

En un mot, une application qui a de bonnes performances sur un dual-coeurs, peut s’effondrer sur une machine possédant plus de cœurs. Et je ne parle que de mémoire partagée c'est-à-dire des applications fonctionnant sur un seul PC. Quand est-il lorsque vous devez alors distribuer le travail non plus entre des processeurs, mais entres des machines faisant partie d’un cluster de calcul ?

Sans en arriver à cette extrémité, il très important, avant de choisir quelle technologie utiliser de suivre un certain nombre d’étapes clés, pour que votre application soit capable de monter en charge. Ce sujet étant hors scope dans cet article, je ne peux que vous conseiller la lecture du livre.

"Patterns for Parallel Programming, Addison Wesley, Timothy G. Mattson ,Beverly A. Sanders ,Berna L. Massingill"

Les threads:

C’est la plus petite unité d’exécution sous Window. Chaque application Windows en possède au moins un par défaut. Le thread est assujetti à un ordonnanceur, qui sélectionne les threads par priorité. Lorsqu’un thread a été sélectionné, il est exécuté pendant une quantité de temps dite quantum. Ce quantum représente la durée pendant laquelle un thread peut être exécuté, avant de laisser la place à un autre thread de même priorité.

Cependant un thread peut ne pas terminer son quantum si un l’ordonnanceur donne la main à un autre thread de priorité plus haute. Lorsque l’ordonnanceur, donne la main à un autre thread, ont dit qu’il y a un basculement de contexte, procédure consistant à enregistrer l’état volatile de la machine associé au thread, et à charger l’état volatile d’un autre thread afin de l’exécuter.

Le développement au niveau "thread" reste compliqué et sujet à de nombreuses erreurs de programmation, qui ne seraient pas mis en exergue avec un développement dit "séquentiel".

Développer à ce niveau est tout indiqué pour les experts car, il permet de maitriser le système de bout en bout.

Pour en savoir plus : http://www.amazon.fr/Concurrent-Programming-Windows-Joe-Duffy/dp/032143482X

Les pools de threads

Comme le nom l’indique ce sont des constructions qui permettent de mettre en pool les threads.

Par rapport à l’utilisation brute du thread, les pools de threads sont intéressants, lorsqu’on a plusieurs tâches indépendantes les unes des autres, et qui s’exécutent sur une période de temps relativement restreinte.

En effet la création et la destruction d’un thread a un coût à la fois en temps, mais aussi en espace mémoire. Créer plus de threads par exemple que le nombre de processeurs, augmente le nombre de basculement de contexte ce qui peut avoir un coût sur les performances.

En effet, imaginez un thread T1 qui est encours d’exécution sur le processeur 0, n’a pas finie sont travail, mais son quantum est expiré et l’ordonnanceur décide de basculer son contexte car d’autres  threads demandent du travail (car trop de threads).

Jusqu’à présent lorsque le thread travaillait, ses données étaient dans le cache processeur, après le basculement de contexte, ce thread se retrouve sur un autre processeur et doit récupérer ses données dans la mémoire centrale, voir sur le disque, ce qui en temps cycle CPU, n’est pas la même chose que d’aller chercher les données dans le cache.

Le développement à base de pools de threads, ne peut se substituer complètement au thread, car dans certain cas lorsqu’on souhaite la maitrise complète du thread, le pool de thread ne nous le permet pas. Utilisez des pools de threads reste aussi compliqué que le développement à base de thread, car les mêmes concepts sont en jeu. C’est une abstraction, que l’on utilise en générale pour  minimiser l’utilisation des ressources du système. Par exemple, sur un serveur WEB, on imagine ne pas avoir une relation de 1->1, entre une demande d’un internaute et la réponse du serveur. J’ai 50000 demandes à un instant T, je ne vais pas créer 50000 threads, ni 50000 connexions à la base de données. On utilise dans ce cas la un pool de thread

Le pool de thread abstrait la gestion des threads au travers l’utilisation d’heuristiques sophistiquées.

Ces heuristiques déterminent le nombre optimal de thread à utiliser en analysant l’architecture de la machine, son taux d’utilisation de la CPU et le nombre de travaux encours.

La logique derrière (Thread injection et retirement algorithms), décide quand il faut créer des threads et quand il faut en détruire, en fonction du temps d’inoccupation ou si le CPU est pleinement occupé.

Sans ce type de logique (en utilisant le thread de base) ce serait aux développeurs de le faire et à tester sur plusieurs configurations bien sur.

Pour en savoir plus : http://www.amazon.fr/Concurrent-Programming-Windows-Joe-Duffy/dp/032143482X

Les fibres (Fiber)

La fibre représente une unité d’exécution très légère qui tourne dans le contexte d’un thread du système d’exploitation. Mais à la différence du thread, la fibre tourne en mode « User » c'est-à-dire qu’elle n’est pas assujettie à l’ordonnanceur de Windows (Le noyau de Windows ne connait alors rien de l’exécution des fibres) et ne crée pas de basculement de contexte. En d’autres termes, c’est au code utilisateur d’ordonnancer les fibres.

Contrairement au thread et au pool de thread, les fibres ne sont pas disponibles sur la plate-forme .NET

Pour en savoir plus : http://www.amazon.fr/Concurrent-Programming-Windows-Joe-Duffy/dp/032143482X

OpenMP (Open Multi-Processing)

Introduit depuis quelques années dans notre compilateur C et C++, c’est une interface de programmation qui permet de développer des applications parallèle assez proche du code séquentiel et utilisée essentiellement sur des architectures à mémoire partagée.

Elle fournit un ensemble de directives, une librairie et de variables d’environnement qui facilitent le développement parallèle et la portabilité du code.

Pour en savoir plus : http://www.openmp.org

MPI (Message Passing Interface)

"MPI conçue en 1993-94, est une norme définissant une bibliothèque de fonctions, utilisable avec les langages C et Fortran. Elle permet d'exploiter des ordinateurs distants ou multiprocesseurs par passage de messages. Il est devenu de facto un standard de communication pour des nœuds exécutant des programmes parallèles sur des systèmes à mémoire distribuée.

MPI a été écrit pour obtenir de bonnes performances aussi bien sur des machines massivement parallèles à mémoire partagée que sur des clusters d'ordinateurs hétérogènes à mémoire distribuée. Il est grandement disponible sur de très nombreux matériels et systèmes d'exploitation. Ainsi, MPI possède l'avantage par rapport au plus vieilles bibliothèques de passage de messages d'être grandement portable (car MPI a été implanté sur presque toutes les architectures de mémoires) et rapide (car chaque implantation a été optimisée pour le matériel sur lequel il s'exécute) ". (Wikipedia)

Microsoft à introduit sa propre librairie basée sur les spécifications de MPICH2 depuis la version 2005 de Visual Studio et l’apparition de notre cluster de calcul Windows.

Une version pour la plate-forme .NET MPI.NET est quand à elle disponible depuis l’arrivée de Windows HPC Server 2008.

Pour en savoir plus
http://www.mpi-forum.org/docs/
https://www.microsoft.com/france/hpc/default.mspx

Extension Parallèle pour le Framework .NET

Ces extensions seront disponibles avec la prochaine version de la plate-forme .NET, c'est-à-dire la version 4.

Elles ont pour but essentielle de réduire les nombres de concepts et la complexité du développement parallèle, et de permettre à tous les développeurs de développer des applications parallèles sans être des experts.

Une librairie plusieurs approches :
Ces extensions parallèles fournissent de nouvelles manières d’exprimer le parallélisme dans son code.

Une manière déclarative pour le parallélisme des données.
Parallel Language Integrated Query (ou Parallel LINQ) est une implémentation de LINQ-to-Objects et LINQ-to-XML qui exécute les requêtes en parallèle.

Exemple de requête LINQ
var query=from alien in Aliens where alien.X=100 select alien

En ajoutant le mot clé AsParallel() on exécute la requête en parallèle

var query=from alien in Aliens.AsParallel() where alien.X=100 select alien

Une manière impérative pour le parallélisme des données:
Ces extensions comprennent des mécanismes de boucle type foreach et for qui peuvent s’exécuter en parallèle.

Une manière impérative pour le parallélisme des tâches.
Plutôt que de paralléliser les données, le parallélisme des tâches, permet d’exprimer le parallélisme via des expressions qui prennent la forme de tâches légères. Ces tâches sont alors ordonnancées pour être exécutées sur les différents processeurs et fournissent des mécanismes simples d’arrêt et de mise en attente.

Pour en savoir plus.
https://msdn.microsoft.com/fr-fr/vbasic/cc678397.aspx
https://blogs.msdn.com/devpara
https://msdn.microsoft.com/concurrency

Parallel Pattern Library/Agent Library/Data Structure

Disponible prochainement avec VC++ 10

Parallel Pattern Library
Permet de simplifier le développement d’applications parallèles en élevant le niveau d’abstraction et en fournissant des alternatives aux partages d’états. Par exemple on ne parle plus de threads, mais de tâches (task_handle). L'exécution des tâches passe pas un groupe de tâches (task_group) permettant de les exécuter, de les attendre ou de les abandonner.

Data Structure (DS)
DS permet de gérer le partage d'états plus facilement en offrant des collections adaptées pour résister aux problèmes de concurrences:

Asynchronous Agents Library
La librairie Agent offre une alternative pour des applications ayants des difficultés à maitriser leurs états internes vis à vis d'un passage au mode parallèle. La classe agent (agent) permet d'isoler son code du monde extérieur en communiquant exclusivement en asynchrone via des messages de type queue (unbounded_buffer<T> ) ou bien valeurs (overwrite_buffer<T>) où la donnée transmise est clonée lorsqu'elle est consommée :

Concurrency Runtime
L'objectif du Concurrency runtime est de fournir une infrastructure de base solide, performante et ouverte afin de rendre accessible la programmation parallèle depuis de nombreuses librairies.

Il est composé d’un ordonnanceur d’équilibrage de charge, qui à pour but d’ajuster dynamique la planification des tâches en fonction de la charge et d’arbitrer les demandes de ressources à l’exécution.

Le Concurrency Runtime est également largement ouvert, permettant à d’autres éditeurs de librairies parallèles de s’appuyer dessus. En effet, on imagine que dans un avenir proche, toutes les applications seront massivement parallèle, pas toujours écrites avec les mêmes bibliothèques (mais bien écrites) et solliciterons le système d’exploitation et ses ressources de manière intensive.

Si ces applications tournent en même temps, le risque alors, serait de mettre à genoux le système lui-même. Passer par un arbitre des ressources sembles alors une bonne solution.

Pour en savoir plus :
https://msdn.microsoft.com/fr-fr/netframework/msdn.parallelisme.introduction.aspx
https://blogs.msdn.com/devpara
https://blogs.msdn.com/nativeconcurrency/

Threading Building Block© Intel

Au même titre que les parallèles extensions la TBB, offre pour les développeurs C++, une librairie riche et complète pour le développement parallèle. C’est une librairie qui permet de profiter au mieux des processeurs multi-cœurs sans pour cela être un expert. TBB n’est pas simplement un substitut aux threads, elle représente un niveau d’abstraction de haut niveau basé sur les tâches, qui cache les détails d’implémentation de la plate-forme et des mécanismes des threads. Intel à annoncée en Aout 2008, leur intention de s’appuyer sur le Concurrency Runtime de Microsoft.

Pour en savoir :
http://www.threadingbuildingblocks.org/
http://www.amazon.fr/Intel-Threading-Building-Blocks-Parallelism/dp/0596514808

CUDA© (NVIDIA)

"Compute Unified Device Architecture) est une technologie de GPGPU (General-Purpose Computing on Graphics Processing Units), c'est à dire qu'on utilise un processeur graphique (GPU) pour exécuter des calculs généraux habituellement exécutés par le processeur central (CPU). CUDA permet de programmer des GPU en C. Cette technologie a été développée par NVIDIA pour leurs cartes graphiques GeForce 8 Series, et utilise un pilote unifié utilisant une technique de streaming (flux continu). NVIDIA s'engage à ce que ses futures cartes graphiques restent compatibles avec CUDA. " (Wikipedia)

Pour en savoir plus : http://www.nvidia.com/object/cuda_home.html#

ATI Stream ©

Est la technologie d’ATI pour le GPGPU sur carte graphique ATI, en réponse à CUDA de NVIDIA ©.

Pour en savoir plus : http://ati.amd.com/technology/streamcomputing/index.html

Microsoft Direct X 11 Compute Shader

Microsoft prépare dans sa prochaine mouture de DirectX, une technologie les Computes Shader qui permettront de faire du GPGPU sur les cartes graphiques qui le supporteront.

En savoir plus : https://www.microsoft.com/downloads/details.aspx?FamilyID=9F943B2B-53EA-4F80-84B2-F05A360BFC6A&displaylang=en

Quelles Technologies pour quelles applications ?

Pour bien comprendre le type de technologie à utiliser, il faut comprendre l’architecture Matérielle, le type d’application que l’on souhaite développer, mais également l’évolution de l’application (prévoir un développement parallèle qui monte en charge, c‘est prévoir un partage des ressources, qui peut s’adapter du poste client vers des postes distribués par exemple).

L’utilisation de l’une ou l’autre des technologies, dépend,

  1. Du type d’application que l’on souhaite développer
  2. De l’architecture matérielle
  3. Mais également des modèles de conceptions parallèles qui sont en place dans l’application

Système non parallèle, non distribuée

Architecture Matériel :

PC de bureau ou station de travail qui ne communique pas entre eux et qui ne possède qu’un processeur.

  • Un socket, Mono-coeur

Type d’application :

Une application qui fonctionne en locale, sur un seul Micro-Processeur, peux se connecter aux réseaux mais ne communique pas avec des clones sur ce réseau.

Il est bien évidement possible de développer en mode multi-threads, mais on parle alors de développement concurrentiel et non pas parallèle. En effet le système d’exploitation, alloue un quota de temps à chaque unité d’exécution qu’est le thread qui partage donc le temps processeur en concurrence avec un autre thread. L’effet pour l’utilisateur est qu’il pense que deux unités s’exécutent en même temps, alors qu’en réalité, l’exécution se fait séquentiellement.

C’est l’application que l’on développe tous les jours, application de gestion traditionnelle.

Elle peut sans doute effectuer des taches asynchrones pour imprimer, recalculer le nombre de page, se connecter à un service Web, ou récupérer des données en arrière plan afin d’éviter de figer l’interface utilisateur.

Dans ce cas la on évite de partager des états entre thread et chaque unité d’exécution est isolée des autres. Nous sommes pour la plupart capable de comprendre correctement le fonctionnement de notre application et d’utiliser des unités d’exécutions bien distinctes pour effectuer des travaux en asynchrone. La difficulté, n’étant pas de créer et d’exécuter ces unités, mais bien de maitriser son état d’avancement (arrêt en cours d’exécution) et d’éventuelles exceptions qui pourraient survenir.

Une autre difficulté, mais plus épineuse, est si l’application partage des ressources entre deux threads, nous devons alors maitriser les concepts inhérents à ce partage. C'est-à-dire, les problèmes qui peuvent survenir, ainsi que la manière d’y remédier. Concepts qui ne sont pas toujours simple à appréhender, qui apportent plus d’ennuis et qui plus est, ne sont pas fondamentalement d’un intérêt crucial pour le bon fonctionnement de l’application puisque le PC ne possède qu’un processeur. En tant que développeur, il est naturel de se consacrer à sa fonctionnalité et ont évite d’y mettre le nez, et donc de développer en mode multi-threads avec des états partagés.

Technologies Utilisées:

En règle générale on utilise le thread ou le pool de thread. Sur la plate-forme .NET il est également possible d’utiliser de abstractions de plus haut niveau pour des exécutions asynchrones, telle que les délégués ou le BackGroundWorker

Système Parallèle, non distribué

Architecture matérielle:

Machine multiprocesseurs à mémoire partagée, PC, ou serveur.

Pour les serveurs, en règle générale, l’architecture peut être constituée.

  • Plusieurs sockets mono-coeur
  • Plusieurs sockets multi-coeurs

Pour les PCs de bureau ou portable, désormais nous aurons des architectures qui possèdent au moins :

  • Un socket multi-coeurs

Applications Serveur :

Applications logiquement massivement parallèle, les bases de données, la messagerie, les serveurs Web et autres jeux en ligne. Les développeurs de ce type d’applications sont des experts du développement parallèle, et connaissent depuis longtemps les paradigmes associés. Les problèmes qu’ils pourront rencontrer sont les mêmes que pour une application de bureau. Car la mémoire de l’ordinateur est partagée entre chaque socket et chaque cœur.

La technologie qu’ils utilisent en règle générale est la plus petite unité d’exécution qu’est le thread ou les pool de threads..

Pour développer en mode multi-thread ils pourront alors utiliser les threads Win32 et leurs objets de synchronisation associés. Mais il n’est pas exclu qu’ils utilisent des bibliothèques de plus haut niveau telles que OpenMP, la bibliothèque Threading Building Block d’Intel (TBB), ou la Parallel Pattern Library de Microsoft.

Les développeurs .NET ne sont pas en reste non plus, car .NET est tout à fait conçu pour le développement de composants serveur robustes et performants.

Le pool de thread serait alors à privilégié, mais le thread peut également être utilisé.

Dans un future proche, il sera alors préférable d’utiliser des bibliothèques de plus haut niveau comme les extensions parallèles qui sont conçues pour tirer profit au maximum des ressources du système, tout en fournissant un modèle de programmation plus simple à employer.

Applications de bureau :

Les développeurs de jeux, d’applications de manipulation multimédia (Images, Vidéos, 3D), d’outils de compression, sont comme les développeurs d’applications serveurs, en règle générale des experts dans le développement parallèle, car ils doivent manipuler simultanément un nombre important de données. Dans ce type d’application, il ne suffit pas d’exécuter des travaux en asynchrone, mais bien de partager le temps d’exécution d’un calcul par exemple sur plusieurs processeurs/cœurs simultanément.

Les technologies utilisées jusqu'à maintenant sont le thread, les pools de threads, mais les développeurs C++ pourront utiliser, pour se simplifier la vie, des bibliothèques de plus haut niveau comme OpenMP, la TBB d’Intel, la PPL de Microsoft.

Pour les développeurs .NET, il est possible d’utiliser le thread, mais pour simplifier la vie des développeurs,  Microsoft inclura dans sa version 4.0, des extensions parallèles.

Tirer profit de toute la puissance locale de la machine est crucial, il n’est donc pas exclut qu’ils utilisent la puissance de calcul de la carte graphique (les développeurs de jeux le font de base bien évidement) pour faire du GPGPU (General-Purpose Processing on Graphics Processing Units)

Sur ce type d’architecture, ont peut bien sûr, développer des applications de gestions traditionnelles, exécuter des travaux en asynchrone, et ne pas se préoccuper des différents cœurs, car nous ne sommes pas des experts en développement parallèle. En effet, jusqu’à présent, mon application à toujours fonctionnée correctement et a profitée de la puissance toujours croissante des PCs.

Malheureusement aujourd’hui le constat, c’est que nous sommes à un point de rupture, qui nous oblige à regarder de plus prêt ces histoires de parallélisme.

N’étant pas expert dans ce domaine, Microsoft et ses partenaires n’encouragent pas les développeurs à se plonger dans des technologies de bas niveau, telle que le thread, mais à se pencher de plus prêt sur des bibliothèques de plus haut niveau, telles que TBB, PPL pour les développeurs C++, et les extensions parallèles pour les développeurs .NET.

Attention, il n’en reste pas moins que connaitre les modèles de programmation parallèle, ainsi que les problèmes potentiels est une nécessité, mais au moins, en utilisant des bibliothèques de plus haut niveau, elles vous permettront respectivement de les implémenter et de les éviter plus facilement.

Système Parallèle, distribué

Architecture matérielle:

Interconnexion de machines monoprocesseur, multi-processeurs ou  multi-coeurs (ou tout type de combinaison) via des réseaux spécialisés haut débit.

Type d’application :

Les applications développées dans ce contexte demandent en règle générale une puissance de calcul importante (Météorologie, Exploration Pétrolifère, Animation 3D, recherche scientifique, etc…), et qui ne doivent surtout pas être limitées par les ressources. L’ajout d’un nœud de calcul doit pouvoir résoudre les problèmes de montée en charge. Nous sommes dans le domaine du High Performance Computing (HPC), ou les développeurs sont reconnus comme des experts dans le domaine du développement parallèle depuis des décennies.

Pour le dialogue, la synchronisation et le transfert des données entre chaque machine ou nœud du cluster, la technologie utilisée est Message Passing Interface (MPI). Néanmoins, un nœud peut-être constitué de plusieurs cœurs, il est donc tout à fait possible également d’utiliser (en local pour un nœud) les threads, les bibliothèques OpenMP, TBB, PPL, TPL ou toutes autres technologies. On développe dans ce dernier cas une application dite hybride.

MPI reservé jusqu’à présent au développeur C/C++ et Fortran, s’ouvre au monde .NET, avec NET.MPI introduit avec Windows HPC serveur 2008.

Conclusion

Le but ultime du développement parallèle, serait d’arriver à une technologie voir un langage qui permettent aux développeurs de développer son application sans se poser de questions, en terme de parallélisme, d’architecture matérielle, a savoir si la mémoire est partagée ou distribuée comme sur les clusters de calcul. Mais avant d’en arriver la, vous comme moi seront sans doute à la retraite.

Néanmoins, Microsoft et ses partenaires comme Intel ont pris à bras le corps le problème et sont à l’initiative de recherches dans ce domaine. Intel et Microsoft ont d’ailleurs en Mars 2008 signés un partenariat avec les universités de Berkeley en Californie et l’université de l’Illinois qui vise à accélérer le développement parallèle, pour les développeurs traditionnels.

A coté de ça, que ce soit dans le domaine de la recherche pur ou en incubation dans les équipes de développement, Microsoft travail, sur des outils d’aide au débogage et à l’analyse des problèmes dus au parallélisme, sur des langages qui permettraient d’exprimer de manière plus naturelle le parallélisme, ainsi que sur les notions de mémoire transactionnelle.

Pour lés débutants, je suis en train de préparer une série de vidéos qui je l’espère devraient pouvoir vous aider à comprendre les tenants et aboutissants du développement parallèle. J’essaierai d’aborder les sujets pas toujours très simples à appréhender de manière la plus didactique possible.

Alors rester à l’écoute.

Eric Vernié