FR 2931269 A1 20091120 FR 0853176 A 20080516 PROCÉDE ET SYSTEME DE DEVELOPPEMENT DE PROGRAMMES PARALLELES DOMAINE TECHNIQUE DE L'INVENTION [0001] L'invention se rapporte à un procédé de développement de programmes parallèles, et à un système pour la mise en oeuvre du procédé. [0002] L'invention se rapporte au domaine des architectures multiprocesseur ou multicoeur, à mémoire partagée ou à mémoire distribuée, et plus particulièrement aux environnements de développement pour la programmation parallèle. ÉTAT DE LA TECHNIQUE ANTERIEURE [0003] Dans divers domaines d'activité, des systèmes ayant une forte puissance de calculs sont requis. De tels systèmes mettent en oeuvre des calculs parallèles consistant en l'exécution d'un traitement pouvant être partitionné en tâches élémentaires adaptées afin de pouvoir être réparties entre plusieurs (coeurs de) processeur(s) opérant simultanément en vue d'un traitement plus rapide que dans une exécution séquentielle de ces tâches. Ces systèmes permettent la réalisation d'économies dans presque tous les domaines du calcul, incluant: la dynamique des fluides, les prédictions météorologiques, la modélisation et simulation de problèmes de dimensions plus grandes, le traitement de l'information et l'exploration de données, le traitement d'images ou la fabrication d'images de synthèse, tels que l'intelligence artificielle et la fabrication automatisée. [0004] À l'opposé, il est souvent nécessaire de simuler un comportement parallèle sur une machine séquentielle pour garantir une expérience utilisateur convenable, par exemple pour simuler l'exécution de plusieurs programmes en même temps sur un PC monoprocesseur. [0005] Une architecture délocalisée telle que client-serveur est aussi un système de programmation parallèle : l'essentiel n'est pas le comportement individuel de chaque poste, mais bien le fait qu'ils soient capables de coopérer en vue d'un résultat. [0006] Ainsi le calcul en parallèle répond à trois besoins principaux : - le besoin de hautes performances, qui peut être atteint en répartissant un calcul sur plusieurs processeurs, ou sur plusieurs coeurs dans le cas d'un seul processeur; - le besoin d'effectuer ou de simuler plusieurs tâches en même temps, par exemple plusieurs programmes ouverts en même temps sur un ordinateur, et - la répartition géographique, par exemple lorsqu'un poste client est localisé à distance d'un poste serveur. [0007] Dans l'art antérieur, on connaît des systèmes mettant en oeuvre deux 10 catégories de calculs parallèles : - à mémoire partagée : cas de plusieurs processeurs d'un même ordinateur partageant un ou plusieurs zones de mémoire communes, des processeurs multicoeurs où plusieurs unités de calcul sont disposées sur la puce, et des monoprocesseurs simulant une exécution simultanée de plusieurs programmes, 15 et - à mémoire distribuée : cas d'architectures spécialisées où plusieurs processeurs d'un même ordinateur communiquent par échange de messages, et d'ordinateurs distants communiquant à travers un réseau. [0008] De nouvelles technologies comme les processeurs multicoeurs 20 permettent de mettre en oeuvre ce parallélisme. Ces processeurs multicoeurs correspondent à l'assemblage de plusieurs coeurs de processeurs côte à côte sur une plaque de silicium. L'apparition des processeurs multicoeurs correspond à la fin de la loi de Moore : les limites physiques font qu'il n'est plus possible d'augmenter la puissance de calculs d'un processeur unique, on se dirige donc 25 vers un modèle où plusieurs processeurs (coeurs) coopèrent sur une même puce. [0009] Lorsque le parallélisme était réservé à des applications à très hautes performances, la difficulté de la programmation parallèle pouvait être résolue avec des équipes de développeurs spécialisés. Avec la généralisation d'ordinateurs comprenant des processeurs multicoeurs se pose le problème de mettre la programmation parallèle à la portée de tous les programmeurs d'application, sans remettre en cause les pratiques et les outils actuels. Un problème se pose de disposer de procédés de développement de programmes parallèles adaptés aux outils de développement classiquement utilisés. [0010] Dans l'art antérieur, on connaît des procédés de développement de programmes parallèles de deux types : - basés sur les langages de programmation classiques et compatibles avec les outils de développement actuels : ils sont d'une telle complexité qu'ils restent réservés à une minorité spécialisé d'utilisateurs programmeurs, car l'utilisation de code de langage impératif dans les procédés de développement classiques de programmes reste limité pour ce qui concerne la programmation parallèle, - basés sur des langages de programmation spécialisés pour le parallélisme, mieux adaptés mais incompatibles avec les pratiques et les outils de développement actuels [0011] Les procédés de développement de programmes parallèles au moyen de codes de langage de programmation impératifs classiques ont l'avantage d'être compatibles avec les pratiques habituelles, mais entraînent des difficultés particulières par rapport à la programmation séquentielle classique : - l'accès à des zones de mémoire partagée par plusieurs programmes ou fragments de programmes exécutés en parallèle peut être source d'erreurs si l'ordre des accès n'est pas contrôlé très précisément, - les techniques connues pour éviter ce problème, telles que sémaphores, mutex, etc., entraînent des baisses de performance très significatives. Elles peuvent aussi entraîner des problèmes d'interblocages liés à de multiples demandes de synchronisation. - de plus, les fragments de code écrits de cette façon ne sont pas composables. [0012] Deux fragments de programme corrects chacun séparément peuvent donner lieu à un programme incorrect lorsque composés en parallèle. Ceci fait qu'il est difficile voire impossible de garantir la correction d'un programme parallèle développé selon ce procédé. [0013] Un procédé de développement connu de l'art antérieur, consiste à utiliser des librairies spécialisées de création de processus légers (� threads �) ou lourds qui sont exécutés en parallèle, sur un ou plusieurs processeurs, avec ou non un support d'ordonnancement (� scheduling �) par le système d'exploitation et/ou à échanger des messages dans le cadre de l'utilisation de programmes distribués sur plusieurs processeurs d'un même ordinateur, ou d'ordinateurs distants à travers un réseau. L'inconvénient de ce type de procédé se rapporte à l'impossibilité de pouvoir traiter des notions qui sont externes au langage de programmation utilisé. La notion de processus, lourd ou léger, est une notion de bas niveau peu appropriée comme primitive de programmation, et de fait les langages de programmation classiques n'incluent pas cette notion. Les processus, lourds ou légers, sont créés par des appels à des librairies externes, c'est-à-dire hors de portée du compilateur, qui ne peut donc pas aider à vérifier la correction des programmes parallèles comme il le fait pour les programmes séquentiels. [0014] Pour pallier ces inconvénients, on connaît dans l'art antérieur un procédé de programmation parallèle mettant en oeuvre des codes de langage spécialisés basés sur de nouveaux modèles de calcul (par exemple le pi-calcul ou la programmation fonctionnelle). Il existe des extensions parallèles pour les langages fonctionnels, par exemple Erlang. Ces codes ont tous en commun de ne pas être des codes de langage impératifs, c'est-à-dire qu'il est impossible de modifier la valeur d'une variable une fois créée. [0015] Un inconvénient majeur de ce procédé est d'être d'une mise en oeuvre difficile et incompatible avec les environnements de développement classiques. De plus un changement de modèle de calcul aussi radical nécessite un long apprentissage pour l'adaptation de l'utilisateur programmeur à ces nouvelles techniques. EXPOSE DE L'INVENTION [0016] La présente invention vise à résoudre le problème lié aux difficultés techniques rencontrées pour mise en oeuvre de mécanismes de développement permettant d'assister un utilisateur développeur dans la génération de programme parallèle. L'invention propose d'améliorer les procédés de développement de programmes parallèles en les rendant compatibles avec les méthodes et les outils de programmation actuels à partir de codes sources comprenant une combinaison de trois éléments : - des fragments de code source impératif, permettant d'exprimer la programmation séquentielle classique, - des fragments de code source algébrique, basé sur un formalisme de type pi-calcul, permettant d'exprimer la composition parallèle, le non-déterminisme et l'échange de messages, et - des éléments indicateurs permettant d'exprimer la correction de la composition parallèle, ainsi que sur un mécanisme d'interaction à double sens entre programmeur et compilateur pour mettre au point de façon interactive les éléments indicateurs nécessaires. [0017] Plus précisément, l'invention a pour objet un procédé de développement de programme parallèle à partir d'un terminal, comprenant les étapes suivantes : - création dans des moyens de mémoire du terminal d'un fichier, - enregistrement par l'utilisateur à partir des moyens de saisie et d'affichage du terminal d'un code source de programmation, ledit code source étant relatif à une combinaison de code impératif, de code algébrique et d'éléments de référence ; - vérification, lors de la compilation, par un module d'analyse chargé dans les moyens de mémoire du terminal, de chaque élément de référence compris dans le code source de programmation de sorte que : • si tous les éléments de référence sont identifiés comme corrects, un compilateur génère le programme parallèle, • si au moins un élément de référence est identifié comme incorrect, des informations représentatives de cet au moins un élément de référence identifié comme incorrect sont affichées par le terminal à partir des moyens d'affichage de manière à ce que l'utilisateur puisse effectuer des corrections, et • si aucun élément de référence n'est identifié, des informations représentatives de cet état sont affichées par le terminal à partir des moyens d'affichage de manière à ce que l'utilisateur puisse effectuer des corrections, - répétition de l'étape précédente de vérification tant que tous les éléments de référence compris dans le code source de programmation ne sont pas identifiés comme correct par le compilateur. Selon des modes de réalisation particuliers : - l'étape de validation comprend une sous-étape de contrôle par le module d'analyse qu'une même zone de mémoire du terminal ne sera pas écrasée lors de l'exécution en parallèle d'au moins deux tâches différentes résultant du traitement du programme parallèle, - l'affichage d'informations est réalisé à partir d'une représentation graphique structurée en forme d'arbre comprenant des sections à exécuter en parallèle ainsi que les éléments de référence associés à ces sections ; - les informations affichées comprennent des données se rapportant à des propositions de corrections des éléments de référence identifiés comme incorrects et à l'emplacement dans le code source de programmation ou elles doivent être enregistrées ; - les informations affichées comprennent des données relatives à des erreurs comprises dans une composition parallèle ; - une étape de transmission du fichier audit compilateur, succédant à l'étape d'enregistrement. ; - le module d'analyse corrige les éléments de référence identifiés comme incorrects dans le code source ; - le module d'analyse génère au moins un élément de référence lorsque aucun élément de référence n'est identifié dans le code source, et - les informations relatives aux éléments de référence corrigés et audit au moins un élément de référence généré par le module d'analyse sont transmises à l'utilisateur à partir des moyens d'affichage du terminal de sorte que celui-ci puisse sélectionner les éléments de référence corrigés ou générés à partir des moyens de saisie. [0018] Plus précisément, l'invention se rapporte également à un système de développement de programme parallèle, pour la mise en oeuvre du procédé, dans lequel le terminal comporte des moyens de saisie reliés à des moyens d'affichage et associés à des moyens de traitement permettant de créer un fichier comprenant le code source de programmation, lesdits moyens de traitements du terminal comprenant au moins un processeur et des moyens de mémoire pour la mise en oeuvre d'un compilateur aptes à traiter le contenu dudit fichier par la vérification des éléments de référence compris dans le code source de programmation, ledit système permettant à partir des moyens d'affichage (du terminal d'afficher des informations représentatives d'au moins un élément de référence et à générer un programme parallèle. Selon un mode de réalisation particulier, les moyens d'affichage et de saisie permettent à l'utilisateur de modifier le contenu du fichier en fonction d'informations représentatives d'au moins un élément de référence identifié par le compilateur dans graphe structuré en forme d'arbre. BREVE DESCRIPTION DES FIGURES [0019] D'autres caractéristiques et avantages de l'invention ressortiront à la lecture de la description qui suit, en référence aux figures annexées, qui illustrent : - la figure 1définit un organigramme d'un mode de réalisation selon l'invention, - la figure 2 illustre un exemple de réalisation selon l'invention, et - la figure 3 décrit un terminal selon l'invention.30 [0020] Pour plus de clarté, les éléments identiques ou similaires sont repérés par des signes de référence identiques sur l'ensemble des figures. [0021] Le procédé de développement de programmes parallèles, représenté à la figure 1 et à la figure 2, est mis en oeuvre au sein des moyens de traitement d'un terminal. Ce terminal peut être un ordinateur fixe ou portable. [0022] La figure 3 illustre le terminal lequel comprend notamment : - des moyens de traitement 33, - une interface de communication, - des moyens de saisie 38, et - des moyens d'affichage 39. [0023] Ces moyens de traitement 33 comprennent au moins un microprocesseur 34 à 37 et des moyens de mémoire 40. Ces moyens de mémoire 40 peuvent être volatiles ou non volatiles, ou encore correspondre à de la mémoire de masse. Au moins un fichier 41 de code sources 15 et un compilateur 16 sont stockés dans ces moyens de mémoire 40. Ces moyens de mémoire 40 associés à au moins un microprocesseur 34 permettent de mettre en oeuvre ces codes 15 et ce compilateur 16 et de traiter les données échangées entre les moyens de saisie 38, l'interface de communication, les moyens d'affichage 39. [0024] Selon l'invention, l'outil mis en oeuvre par le procédé permet de combiner un code de langage impératif traditionnel (ou code impératif) et un modèle algébrique de parallélisme (ou code algébrique) au moyen d'extension de langage et d'un module d'analyse statique 7 (ou analyse d'effets). [0025] Le code impératif 12 se rapporte à un code de programmation très utilisé, dans lequel les instructions qui modifient des données sont exécutées simplement les unes après les autres, avec quelques structures de contrôle permettant de créer des boucles ou des alternatives. Ce code impératif 12 correspond par exemple à un langage JavaTM ou Pascal, ou encore C, Perl, Cobol, Fortran, etc. [0026] Le code algébrique 13 (ou modèle algébrique de parallélisme) permet de réaliser un calcul de processus parallèles afin de rendre compte de systèmes dont la topologie de communication change dynamiquement. Ce code algébrique 13 se rapporte par exemple à CCS/Occam, pi-calcul (parallélisme asynchrone), ou encore EsterelTM (parallélisme synchrone). [0027] On peut noter, comme mentionné précédemment, que le développement de programme parallèle permet de: - simuler le fonctionnement simultané de plusieurs programmes, par exemple plusieurs applications fonctionnant en même temps sur un terminal monoprocesseur afin de créer l'illusion d'un fonctionnement simultané, en général sur la base d'un mécanisme de temps partagé dans lequel chaque application utilise à tour de rôle les ressources matérielles. Le changement étant suffisamment rapide pour donner l'illusion d'un comportement parallèle, et - augmenter les performances d'un programme en exécutant différentes parties sur différents processeurs. [0028] Avec l'arrivée des processeurs multicoeurs, tous les utilisateurs programmeurs devront être capables de saisir du code parallèle pour profiter des performances de ces processeurs. [0029] Les procédés de développement de programmes parallèles utilisés actuellement dans l'industrie sont basés sur : - les threads et processus, pour les programmes à mémoires partagée : les threads et processus sont exécutés en parallèle, sur un ou plusieurs processeurs, avec ou non un support de scheduling par le système d'exploitation. La gestion du partage de mémoire requiert des techniques de synchronisations telles que sémaphores, mutexes, moniteurs, etc., afin de garantir une exécution correcte des programmes, et - l'échange de messages, pour les programmes distribués (sur plusieurs processeurs d'une même machine, ou de machines distantes à travers un réseau). [0030] Un problème classiquement rencontré dans un tel contexte se rapporte à la gestion du partage de mémoire, avec des techniques de synchronisations telles que sémaphores, mutexes, moniteurs, etc., pour garantir une exécution correcte des programmes, ainsi que les risques d'interblocage liés à de multiples demandes de synchronisation. [0031] D'un autre côté, encore essentiellement dans le monde académique, on trouve des modèles de parallélisme tels que CCS et pi-calcul définis de façon essentiellement algébrique. Ce sont des objets mathématiques qui permettent d'obtenir des preuves de propriétés très intéressantes concernant le comportement de programmes parallèles, mais qui sont fondamentalement incompatibles avec le modèle de programmation impératif. [0032] Le modèle du pi-calcul est utilisé par exemple comme base sémantique de BPEL (Business Process Execution Language), un langage de description des procédures d'entreprise. [0033] Les modèles théoriques et les langages correspondants sont réservés à des experts, et incompatibles avec les plateformes de développement habituelles. Ils sont définis de façon essentiellement algébrique, à l'opposé des langages de programmation classiques impératifs. Malgré leurs avantages, ils ne sont pas utilisables dans un contexte industriel. [0034] Les modèles ad-hoc ont d'énormes problèmes de productivité et de qualité du développement: le modèle des processus légers (� threads �) est difficile à comprendre et à maîtriser, nécessite une formation très spécifique, les programmes basés sur ce modèle sont difficiles à vérifier et souvent incorrects, ce qui provoque interblocages ou résultats non prédictibles. En particulier la notion de � thread � est mal définie et non composable. [0035] Les modèles de programmation parallèle utilisés aujourd'hui dans l'industrie souffrent de nombreux défauts largement reconnus: - les programmes parallèles sont verbeux et difficilement lisibles : c'est beaucoup dû au fait que les primitives parallèles sont intégrées au moyen d'APIs, qui ont une capacité d'expression très réduite, - ces modèles de programmation parallèle sont longs à apprendre et difficiles à maîtriser, réservant de fait la programmation parallèle à une élite d'experts, - les notions offertes par ces modèles de programmation, typiquement la notion de �thread �, ne sont pas composables : il est donc difficile de construire de gros programmes par combinaison de petits, - les programmes sont dépendants de l'architecture visée et de l'API utilisée : passer par exemple d'un modèle de mémoire distribuée à un modèle de mémoire partagée implique une réécriture complète, alors que la logique du programme n'a que peu changé, - un programme parallèle s'écrira de façon très différente du programme séquentiel équivalent mais surtout, le bon fonctionnement du programme, impliquant par exemple le fait d'utiliser des librairies � thread safe � ou de posséder un sémaphore avant d'accéder à une variable, est entièrement de la responsabilité du programmeur d'application. Les erreurs sont faciles à faire et extrêmement difficiles à détecter. Des situations de compétition (� race conditions �) peuvent rester cachées pendant des années dans du code en production, de nombreux exemples concrets l'attestent. [0036] Les codes de programmation théoriques, définis de façon algébrique, évitent la plupart de ces problèmes, mais leur combinaison avec des codes de programmation impératifs se heurte à un dilemme: les modèles théoriques de type pi-calcul séparent toujours les objets et interdisent la mise à jour (affectation), alors que le partage des objets et l'affectation sont à la base de la programmation impérative. [0037] Dans un mode de réalisation, le procédé selon l'invention comprend une étape de création 1 dans des moyens de mémoire 40 du terminal, d'un fichier 41. Dans ce fichier 41 est enregistré par l'utilisateur au travers des moyens de saisie 38 et d'affichage 39 un code source 11 lors d'une étape d'enregistrement 2. [0038] Ce code source 11 comprend une combinaison : - d'un modèle mathématique de parallélisme défini par un code algébrique, par exemple le pi-calcul, avec - un code programmation généraliste de type impératif, par exemple JavaTM et - des éléments de référence 14 (ou indicateurs). [0039] Plus généralement, cette combinaison permet à des utilisateurs programmeurs non experts d'écrire des programmes parallèles composables et sûrs. [0040] On entend par programmes parallèles composables des programmes où la responsabilité de vérifier la correction d'une composition parallèle (par exemple vérifier l'absence de situations de compétition ou d'interblocages) incombe au programmeur qui réalise cette composition. Ce n'est pas le cas des modèles de programmation actuels basés sur les � threads �. [0041] On entend par programmes parallèles sûrs des programmes où la correction de la composition parallèle est vérifiée lors de la compilation par des outils automatiques ou semi-automatiques, comme cela se pratique par exemple pour la vérification des types de données. [0042] Dans ce code impératif 12 sont intégrées des constructions parallèles de type CCS ou pi-calcul sous la forme d'une extension de langage et non pas d'une 20 API. [0043] Dans les moyens de mémoire 40 du terminal 32 est stocké un module d'analyse 7 du code source nommé analyse d'effets. [0044] L'analyse d'effets permet aux deux codes impératif 12 et algébrique 13 de cohabiter sainement. 25 [0045] Cette analyse d'effets 7 se rapporte à une analyse statique qui permet de garantir lors de la compilation que deux tâches différentes exécutées en parallèle ne vont pas écraser les mêmes zones de mémoire. D'autres types d'analyses statiques permettent par exemple de garantir l'absence d'interblocages. [0046] L'analyse d'effets 7 va à partir des éléments de référence intégrés dans le code source 15 pourvoir extraire les informations qui lui seront utiles. [0047] Ces éléments de référence 14 constituent une extension de langage qui est compatible avec les codes de langages de programmation impératifs 12 et les codes algébriques 14 (modèles de parallélisme) utilisés aujourd'hui (threads, etc). Ces éléments de référence conviennent au parallélisme synchrone et asynchrone, ainsi qu'à la mémoire partagée ou distribuée (MIMD). [0048] Les modèles de programmation impératif et parallèle sont combinés en réalisant une extension du langage impératif, code impératif, avec des opérateurs tels qu'envoi et réception de messages (parallélisme asynchrone), émission et écoute d'un événement (parallélisme synchrone), mise en parallèle, choix non déterministe, réplication. [0049] Afin de préserver la sémantique du parallélisme, des parties de code exécutées en parallèle ne doivent pas modifier en même temps les mêmes zones de mémoire: c'est l'analyse d'effets qui permet de garantir statiquement cette condition, lors de la compilation. [0050] L'analyse d'effets est coûteuse en temps et indécidable en général: c'est la raison pour laquelle des éléments de référence sont utilisés afin de guider le module d'analyse (analyse d'effets) et de la rendre interactive (le module d'analyse peut demander au programmeur d'insérer des éléments de référence additionnels), et incrémentale (pas besoin de ré-analyser l'ensemble du programme à chaque fois). [0051] La simplicité de mise en oeuvre de la programmation parallèle rend la mise en oeuvre du procédé selon l'invention accessible à tous les utilisateurs programmeurs avec un minimum de formation. Ce qui constitue un avantage tant pour les fabricants de processeurs multicoeurs que pour les utilisateurs programmeurs d'applications qui peuvent ainsi exploiter au mieux toute la puissance des processeurs des ordinateurs. [0052] L'invention comporte d'autres avantages tels que: - la productivité et la qualité : les programmes parallèles sont développés 5 plus rapidement, avec beaucoup moins de risques d'erreurs ; - la composabilité : la responsabilité du bon fonctionnement en parallèle revient à celui qui compose des processus en parallèle (pas à celui qui écrit le code initial comme c'est le cas aujourd'hui, avec par exemple la notion de code � thread-safe �) ; 10 - un seul code source pour tous les types d'architectures cibles (séquentiel, mémoire partagée, mémoire distribuée) : le code source exprime la logique du programme, par exemple le fait qu'il est possible de mettre deux processus en parallèle ; le fait de les exécuter effectivement en parallèle ou non relève d'annotations ou de directives de compilation ; 15 - indépendance du matériel : le même code source peut produire des programmes fonctionnant sur des architectures aussi diverses que mémoire partagée (threads) ; supercalculateurs à processeurs répartis, middleware tel que Corba, réseau Internet, en changeant les annotations ou les options de compilation : la logique du code reste la même. 20 De la même façon, le mécanisme d'échange de messages est paramétré par des annotations ou des options de compilation. [0053] Le compilateur compris dans les moyens de mémoire 40 du terminal 32 comprend le module d'analyse 7. [0054] Ce compilateur est apte à réaliser le traitement de code impératif (ex : 25 JavaTM) [0055] L'exécution du code impératif tel que JavaTM consiste en l'exécution successive d'instructions de base (les instructions pouvant être groupées en blocs). Les instructions de base sont atomiques, c'est-à-dire qu'il n'est pas possible de les décomposer. [0056] Le code algébrique basé sur le pi-calcul est décrit sous forme algébrique, un processus étant constitué d'instructions de base composées au moyen d'opérateurs : - END : processus vide, en général omis de la notation ; - a.P : composition séquentielle, exécution de l'instruction a suivie de l'exécution du processus P ; - PIQ : composition parallèle, exécution des processus P et Q en parallèle ; - P+Q : choix indéterministe, exécution de P ou de Q ; - !P : réplication, exécution parallèle de multiples copies de P ; - (nu x) : création d'un nouveau canal x invisible de l'extérieur. La communication est définie par trois instructions additionnelles : - c!x : envoi d'une valeur x sur le canal c ; - c?x : réception d'une valeur x sur le canal c ; - tau : sert à indiquer qu'une communication a eu lieu. La sémantique du pi-calcul est définie par des équations dont voici les deux plus importantes : (1) a.P 1 b.Q = a.(P 1 b.Q) + b.(a.P 1 Q) signifiant : exécuter deux processus en parallèle revient à exécuter en séquence, dans un ordre indéterminé, les instructions qui les composent. Par exemple, si il, i2, i3, i4 sont des instructions, on a (il .i2) 1 (i3.i4) il .(i2 1 i3.i4) + i3.(il .i2 1 i4) il .(i2.(i3.i4) + i3.(i2Ii4)) + i3.(il .(i2Ii4) + i4.(il .i2)) il .i2.i3.i4 + il .i3.i2.i4 + i3.il .i2.i4 + i3.i4.il .i2 30 On voit bien ici que l'équation (1) donne tous les entrelacements (� interleavings �) possibles. (2) c!x.P I c?y.Q = tau.(PIQ[x/y]) signifiant : deux processus en parallèle, l'un envoyant une valeur x sur un canal c et l'autre recevant une valeur y sur ce même canal, peuvent communiquer et le résultat de la communication est la composition parallèle de P et de Q dans lequel y est remplacé partout par x (tau sert à indiquer qu'une action irréversible a eu lieu). Cette équation est suffisante pour décrire toutes les possibilités de communication. [0057] L'intégration de ces deux modèles de programmation est réalisée par les moyens de traitement 33 du terminal 32 en identifiant la notion d'instruction : une instruction impérative (JavaTM) est identifiée à une instruction du calcul parallèle (CCS ou pi-calcul). [0058] Les 3 instructions c!x (émission), c?y (réception) et tau sont ajoutées au jeu d'instruction du langage impératif de départ. [0059] Un canal pi-calcul est identifié à un type JavaTM particulier Channel 20 réservé à cet usage (T indique le type des valeurs échangées par ce canal). [0060] L'opérateur de restriction (nu) est identifié à la déclaration de variable locale en JavaTM [0061] Un processus pi-calcul se retrouve ainsi identifié à une séquence arbitraire de code JavaTM, typiquement un bloc. Les opérateurs de composition de 25 processus pi-calcul s'appliquent sur des blocs de code, et produisent eux-mêmes de nouvelles instructions. [0062] Le pi-calcul est un modèle purement mathématique qui ne connaît ni la notion d'effet de bord ni celle de partage de données, alors que ces deux notions sont fondamentales en JavaTM En particulier, d'après l'équation (1), l'ordre d'exécution des instructions ne doit pas influer sur le résultat : s1Is2=s1.s2+s2.s1 L'effet doit être le même qu'on exécute d'abord s1 puis s2, ou s2 puis s1. Ce n'est clairement pas le cas avec un langage impératif à cause de la possibilité d'effets de bord (ce problème n'existe pas avec les langages fonctionnels, sans effets de bord, tels que Erlang mentionné plus haut). Deux processus en parallèle ne doivent jamais accéder directement ou indirectement aux mêmes données. Ou plus précisément, un des processus d'une composition parallèle ne doit pas modifier une zone de mémoire si un des autres processus y accède en lecture. Nous définissons donc pour un processus (un bloc de code) P : - Writes(P) une estimation conservatrice (un sur-ensemble) de toutes les zones mémoires potentiellement accédées en écriture par P, et - Reads(P) une estimation conservatrice (un sur-ensemble) de toutes les zones mémoires potentiellement accédées en lecture par P. Writes(P) et Reads(P) sont des estimations conservatrices car il est impossible (indécidable) de déterminer exactement quelles zones seront effectivement accédées lors d'une exécution. La restriction suivante est ajoutée à l'opérateur de composition parallèle : des processus P1 ... PN ne peuvent être composés en parallèle P1 I... IPN que si pour tout x dans Writes(Pi), alors x n'est pas dans Writes(Pj) ni dans Reads (Pj), pour tout j différent de i. C'est-à-dire que si la zone x est accédée en écriture par un des processus de la composition, elle n'est accédée ni en lecture ni en écriture par aucun des autres processus de la composition. Si le compilateur n'arrive pas à vérifier cette condition pour une composition parallèle, alors le programme est refusé. Nous voyons que tout repose sur la capacité à déterminer de bonnes estimations pour Reads et Writes. La méthode standard pour déterminer Reads et Writes est l'analyse d'effets effectuée par le module d'analyse 7 (analyseur d'effets). Mais une analyse d'effets entièrement automatique est lourde et atteint très vite les limites de l'indécidabilité, ce qui conduirait à des temps de compilation prohibitifs et au refus de nombreux programmes pourtant corrects. [0063] L'introduction d'éléments de référence 14 tels que les estimations de Reads et Writes dans le code source du programme permet de guider un analyseur d'effets 7. Cela a aussi l'avantage d'améliorer la compréhension du programme en faisant apparaître explicitement les effets. L'analyse d'effets est rendue incrémentale (après une modification du programme, il suffit de n'en ré- analyser qu'une petite partie). [0064] Une approche est d'utiliser des expressions régulières du type Reads(P) = x.y.* , t[], ... signifiant � P peut potentiellement accéder en lecture toutes les valeurs de la forme x.y.a, x.y.b, x.y.c, .etc., ainsi que toutes les valeurs de la forme t[0], t[1], t[2], .etc. Les expressions régulières ont l'avantage d'être stables par composition, intersection et complément, et l'inclusion et l'équivalence sont décidables. [0065] Des expressions plus complexes sont envisageables, par exemple Reads(P) = t[2*i], pour i >= 0 P ne peut accéder en lecture que les éléments pairs du tableau t. La � qualité � de notre intégration parallélisme/impératif, c'est-à-dire le fait de pouvoir considérer comme corrects un plus grand ensemble de programmes, est donc étroitement liée à la capacité d'expression des formules utilisées pour définir Reads et Writes. [0066] Une plus grande capacité d'expression implique plus de difficultés pour le compilateur 16 pour vérifier les éléments de référence 14, pouvant aller jusqu'à une impossibilité théorique. Une différence essentielle entre les diverses variantes de l'invention sera donc le choix de la capacité d'expression des éléments de référence 14 Reads et Writes. [0067] De même, pour améliorer l'étape de vérification, des éléments de référence 14 supplémentaires telles que les échappements et alias de variables (la possibilité pour deux noms différents de représenter ou non le même objet) peuvent être ajoutées au langage. [0068] Une fois que le compilateur 16 a vérifié ces éléments de référence 14, l'utilisateur programmeur peut avoir à insérer des � lemmes � (annotations secondaires) à des endroits choisis. [0069] Jusqu'ici le compilateur 16 est montré dans un rôle de vérificateur des éléments de référence (annotations d'effets) écrites par le programmeur. Il est aussi parfois possible que le compilateur 16 (ou un autre outil externe) puisse inférer de lui-même certains éléments de référence, en particulier les plus évidentes et donc les plus ennuyeuses à écrire pour le programmeur. Dans ce cas, le compilateur 16 a aussi la capacité de modifier le code source 15 sous contrôle du programmeur pour y insérer directement ces éléments de 20 référence 14. Ce principe peut aussi s'appliquer pour tout autre type d'éléments de référence 14. 25 Le procédé selon l'invention permet de pouvoir partager des zones de mémoire entre deux processus en parallèle en spécifiant au moyen d'éléments de référence similaires à reads et writes les zones pouvant être partagées. L'accès à ces zones partagées devra alors être protégé par les techniques classiques de verrous (� locks �) ou de moniteurs. Le procédé peut vérifier ou non que ces accès sont corrects. Dans le cas de zones partagées, l'équation (1) ne tient plus nécessairement : le programme peut avoir un comportement indéterministe. Mais avec l'obligation de noter les zones partagées, ces parties indéterministes sont clairement identifiées. [0070] L'extension de langage au travers des éléments de référence ne fait que spécifier les parties du code source 15 qui peuvent être exécutées en parallèle : c'est une vision logique des choses, qui garantit que quelle que soit l'implantation choisie, le comportement restera identique. [0071] Elle ne précise pas quelles parties doivent être exécutées en parallèle, ni sous quelle forme. Elle ne traite pas non plus d'aspects tels que l'équilibrage de charge, le type de � scheduling � (préemptif ou non), les priorités de � threads �, ou la mobilité du code. [0072] Tous ces aspects ne concernent pas l'aspect logique du programme : quelle que soit l'implantation choisie, le résultat sera identique. Seules les performances et l'ordonnancement des tâches dans le temps peuvent être différents. [0073] Ces aspects peuvent être spécifiés typiquement de deux façons : - par des éléments de référence au sein du code source, par exemple les annotations JavaTM, et/ou par des options de compilation Par exemple dans le fragment de code correspondant en annexe 1. On spécifie que les 1000 instances du corps p(i) peuvent être exécutées en parallèle, car différentes instances écrivent dans des zones de mémoire distinctes. En particulier, ces 1000 instances peuvent être exécutées séquentiellement, et dans n'importe quel ordre. Le fragment de code précédent pourrait donc être implanté par une simple boucle for, au choix du compilateur, tel que précisé en annexe 2. 5 Un élément de référence peut indiquer que le programmeur veut explicitement que ces instances soient exécutées séquentiellement (en JavaTM, le caractère @ introduit une annotation), tel que précisé en annexe 3. De la même façon, ces instances peuvent être exécutées en parallèle en créant 10 des processus légers (� threads �) ou en utilisant la technique du � thread-pool �, avec un nombre de � threads � ou une taille de grain (nombre d'instances par thread) donné, maximal ou déterminé dynamiquement. Elles peuvent aussi être exécutées sur un noeud distant d'une architecture distribuée ou un ordinateur distant à travers un réseau. Ceci est illustré par un exemple a en annexe 4. 15 Ces éléments de référence ne sont pas prédéfinis, et sont potentiellement illimités : ce sont eux qui permettent d'adapter finement le code généré 21 par le compilateur 16 pour profiter au mieux d'une architecture donnée. Ces éléments de référence ne modifient pas la logique du programme, et ont donc 20 un rôle et un statut très différent des éléments de référence 14 reads, writes et shared définies précédemment. [0074] De la même façon, une émission ou réception sur un canal peut prendre des formes très différentes, par exemple : appel de méthode locale, 25 - primitive de processeur multicoeur, appel de méthode distant (RMI), socket TCP/IP, middleware (corba), ou requête Web service (SOAP).30 [0075] Dans un exemple de réalisation, nous étendons le langage JavaTM avec les instructions suivantes (la syntaxe est donnée à titre indicatif). [0076] Instructions additionnelles : émission : send c ! x, où c est une expression de type Channel et x une 5 expression de type T ; réception : receive c ? y, où c est une expression de type Channel et y est un identificateur. y est considéré comme une déclaration de variable locale, et toutes les règles de portée concernant les variables locales s'y appliquent ; - il n'est pas nécessaire d'ajouter l'instruction tau dans le langage, elle n'est 10 utile que pour des vérifications sémantiques. [0077] Opérateurs de composition de processus : composition parallèle : parallel { si li ... ll sN }, où les si sont des instructions ou des blocs d'instructions ; choix non-déterministe : select { s1 + ... + sN }, où les si sont des 15 instructions ou des blocs d'instructions ; réplication : replicate s, où s est une instruction ou un bloc d'instructions. La composition parallèle de s1, ..., sN ne sera considérée correcte que si si , ..., sN sont annotés avec des effets vérifiant les conditions de l'opérateur parallèle. Ces trois opérateurs peuvent aussi être paramétrés par des listes de quantificateurs, tel que présenté en exemple en annexe 5. Dans ce cas, les éléments de référence peuvent aussi faire référence aux 25 variables introduites par les qualificateurs : parallel(int i : 1..3) reads a[i]: p(i) ; On notera que l'opérateur parallèle ressemble beaucoup à une boucle for : 20 puisque tous les ordonnancements sont possibles, un parallel(int i : 1..3) p(i) peut en particulier être implémenté de façon séquentielle par un for(int i:1..3)p(i); à l'inverse, chaque fois qu'il est possible de trouver les éléments de référence 14 appropriés, une boucle for peut être remplacée par la construction � parallel � correspondante : l'existence des éléments de référence garantit en effet que l'ordre d'exécution des itérations de la boucle est sans importance. Les notations d'effets (ou élément de références) peuvent précéder toute instruction ou bloc d'instructions. Elles sont notées Reads : reads F1, ..., FN : inst où F1, ..., FN sont des formules dans le langage choisi et inst est une instruction ou un bloc d'instruction - Writes : writes F1, ..., FN : inst où F1, ..., FN sont des formules dans le langage choisi et inst est une instruction ou un bloc d'instruction Ces éléments de référence peuvent aussi accompagner toutes les constructions JavaTM introduisant des instructions ou blocs d'instructions, comme par exemple 20 les déclarations de méthodes. On peut remplacer dans les annotations des listes vides par un mot-clé � nothing � (qui signifie rien) à pour mieux mettre en évidence le fait qu'aucune zone de mémoire n'est partagée. Les éléments de référence 14 sont toujours vérifiées par le compilateur 16. L'impossibilité de vérifier un élément de référence entraîne un rejet du programme. Les éléments de référence portant sur les déclarations de méthodes sont aussi 30 conservées dans les fichiers objets (par exemple byte-code JavaTM), afin de permettre la vérification incrémentale des annotations de programmes utilisant des librairies. 25 L'annotation de variables partagées peut apparaître dans la composition parallèle ou le choix non-déterministe : elle spécifie les variables partagées de façon explicite, en utilisant les mêmes formules que pour les éléments de référence: parallel shared F1, ..., FN { ... } où F1, ..., FN sont des formules dans le langage choisi, - select shared F1, ..., FN { ... } où F1, ..., FN sont des formules dans le langage choisi. Exemples de code écrits dans cette extension du langage JavaTM Serveur Web : 15 Le serveur Web le plus simple est la combinaison parallèle d'un grand nombre de processus identiques : lire une requête sur un canal, traiter la requête et répondre sur le même canal. À titre d'exemple un code est présenté en annexe 6. Ce code pourrait comporter de nombreux éléments de référence pour garantir par 20 exemple une bonne répartition de la charge de travail. Mais la logique du programme elle-même ne serait pas changée. Somme en parallèle : 25 On cherche à calculer sum = a[0] + ... + a[n-1]. Ce type de programme composant les résultats d'un opérateur associatif-commutatif est parfois appelé � parallel reduce �. À titre d'exemple un code est présenté en annexe 7. 30 Multiplication de matrices : On cherche à calculer le produit C[M,N] = A[M,L] * B[L,N] 10 À titre d'exemple un code est présenté en annexe 8. Utilisation de librairies concurrentes : Un problème courant dans un langage comme JavaTM est le choix d'une librairie concurrente ou non. Dans le premier cas, la gestion du parallélisme à l'exécution peut beaucoup dégrader les performances, dans le deuxième cas, le comportement du programme peut être erroné. Or ce choix pour le moins essentiel est entièrement laissé à l'appréciation du programmeur d'application, sans aucun support du compilateur. Supposons que nous ayons à disposition une librairie proposant une classe vecteur (séquence ordonnée d'éléments). Le code source de la librairie pourrait ressembler à celui présenté en annexe 9. À cause de l'annotation � writes array, currentSize �, il ne sera jamais possible de composer en parallèle deux processus appelant directement ou indirectement add() sur le même vecteur, car les deux appels à add modifient en écriture les mêmes zones de mémoire (les champs privés array et currentSize du vecteur). À titre d'exemple un code est présenté en annexe correspondant en annexel0. Si la librairie était conçue pour être utilisée par des processus concurrents, les annotations (et bien sûr le code) de la librairie seraient différentes. À titre d'exemple un code est présenté en annexe 11. La composition est maintenant possible. La gestion des accès concurrents aux champs array et currentSize est de la responsabilité du code de la méthode add(), par exemple en utilisant des mécanismes de sémaphores. À titre d'exemple un code est présenté en annexe 12. 30 Dans cet exemple, le langage garantit la correction des programmes parallèles en forçant l'utilisation de librairies de collections concurrentes (� thread-safe �)25 chaque fois que cela est nécessaire. À l'opposé, en JavaTM par exemple, il est de la responsabilité du programmeur d'application de choisir le bon type de collection. Cet exemple montre aussi que les annotations et éléments indicateurs introduits par le programmeur dans le code source doivent se retrouver sous une forme ou une autre dans les fichiers objets, pour permettre l'utilisation de librairies ou la compilation séparée. Circuits logiques : Un amplificateur opérationnel est un circuit à deux entrées et une sortie, émettant sur sa sortie la différence entre les deux valeurs d'entrée. À titre d'exemple un code est présenté en annexe 13. Un différentiateur est un circuit à une entrée et une sortie, utilisant un amplificateur opérationnel en reliant sa sortie à une des entrées. À titre d'exemple un code est 15 présenté en annexe 14. [0078] On peut noter que l'analyse d'effets est une technique connue pour vérifier la correction de la composition parallèle, mais seulement dans des cas très particuliers : en effet c'est un problème indécidable en général. C'est le fait d'introduire une boucle d'interaction avec le programmeur qui permet de trouver 20 les bonnes annotations et faire les preuves de correction. Un autre point est que l'analyse d'effets est très coûteuse en temps, c'est le fait d'avoir introduit des éléments de référence qui permet de la rendre incrémentale et donc rapide. [0079] Dans un autre mode de réalisation, dans lequel il s'agit de réaliser la somme de tableaux, on dispose de deux tableaux a et b de 10 éléments chacun et 25 on veut donc réaliser la somme suivante : a[i] = a[i] + b[i] Les syntaxes utilisées pour le code séquentiel, le code algébrique et les éléments de référence sont purement indicatifs. 30 Dans une approche séquentielle, on dispose d'un fragment de programme séquentiel réalisant cette opération, écrit dans un code impératif en JavaTM À titre d'exemple un code est présenté en annexe 15. Typiquement ce fragment de code sera inclus dans le corps d'une méthode m, tel que présenté en annexe 16. Lors d'une décomposition parallèle, on partage a et b en deux tableaux de 5 éléments chacun, a1, a2 et b1, b2. al et b1 contiennent la première moitié des éléments, a2 et b2 la seconde. À titre d'exemple un code est présenté en annexe 17. On peut composer ces deux fragments en parallèle à partir du code algébrique, comme présenté en annexe 18. On obtient par la suite avec l'enregistrement des éléments de référence la combinaison de code impératif 12, code algébrique et les éléments de référence, tel qu'il est présenté en annexe 19. [0080] Il est important de prendre en compte l'aliasing, c'est-à-dire le fait que a1 et a2 ne correspondent pas par inadvertance à la même zone mémoire; ces informations d'alias peuvent aussi apparaître sous forme d'éléments de référence dans le code source. [0081] On peut rajouter explicitement des éléments de référence. Par exemple reads a1 [*] signifie qu'au moins un des éléments de al est accédé en lecture, 25 mais sans aucune information sur quel(s) élément(s) précisément. Ces annotations explicites sont utiles pour deux raisons: - documentation, à l'usage des programmeurs ou d'autres outils de programmation - incrémentalité: la prochaine fois qu'il faudra prouver des annotations, il n'y 30 aura pas besoin de refaire des preuves pour l'ensemble du programme: si le fragment de code annoté n'a pas changé, alors l'annotation correspondante est toujours valide. C'est important car l'analyse d'effets d'un programme complet peut être très longue. Ces annotations peuvent aussi être insérées de façon automatique dans le code source par l'analyse d'effets. [0082] Dans un deuxième exemple de décomposition parallèle, plutôt que de couper les tableaux a et b en deux, on peut les garder intacts et avoir deux boucles qui travaillent chacune sur une moitié. À titre d'exemple un code est présenté en annexe 20. [0083] Le fichier 41 comprenant le code source 15 est pris en charge 27 par le compilateur 16. Lors de la compilation, le module d'analyse effectue une analyse d'effets du code source reçu 9 afin de déterminer la compatibilité de ml et m2. Dans cet exemple, une erreur déterminer par le module d'analyse 7 peut correspondre par exemple à l'impossibilité de la composition parallèle. Le module d'analyse 7 transmet alors au moyen d'affichage du terminal des informations représentatives de l'état qu'il aura identifié. Dans le cas présent, il peut inviter l'utilisateur programmeur 22 à introduire des éléments de référence plus précis. Avant la correction apportée par l'utilisateur programmeur, on a un code tel que le en 20 annexe 21. Après la correction apportée par l'utilisateur programmeur, on dispose d'un code correspondant à l'annexe 22. En effet, l'utilisateur programmeur sait qu'il a divisé les tableaux en deux 25 intervalles, il introduit donc cette notion d'intervalle dans les éléments de référence. Avec reads a[0-4] signifiant que certains des éléments 0 à 4 du tableau a sont accédés en lecture. La notion d'intervalle apparaît explicitement dans le langage d'annotations. Un module d'analyse 7 (ou analyseur d'effets) comprenant la notion d'intervalle doit être capable de prouver les éléments de référence ci-dessus en les vérifiant. Le code source modifié 23 est de nouveau transmis au compilateur 16. Si aucune erreur n'est trouvée par le module d'analyse 7, les informations relatives 17 au code source sont donc présentées au programmeur avec tous les éléments de référence identifiés comme correct. Ce code source est alors compilé pour l'obtention 10 du programme parallèle 21. [0084] Dans un troisième exemple de décomposition parallèle le partage des tableaux peut être beaucoup plus fin que simplement un découpage en deux intervalles. On peut par exemple imaginer une boucle qui opère sur les éléments pairs et l'autre sur les éléments impairs. À titre d'exemple un code est présenté en annexe 23. Dans ce cas l'absence d'éléments de référence entraîne l'envoi par le module d'analyse aux moyens d'affichage du terminal d'information se rapportant à cet état. Ainsi, le code source sera présenté au programmeur à partir des moyens d'affichage avec un message d'erreur précisant qu'il est impossible de vérifier la compatibilité de la composition parallèle. Cette présentation correspond à des informations qui sont représentées graphiquement 17 et est structurée en forme d'arbre comprenant des sections à exécuter en parallèle ainsi que les éléments de référence associés à ces sections. Cet arbre 17 représente de manière structurée les différents codes impératif 18, algébrique 19 et les éléments de référence 20. Avant la correction apportée par l'utilisateur programmeur, on a un code tel que présenté 25 en annexe 24. Après la correction apportée par l'utilisateur programmeur, on dispose d'un code présenté en annexe 25. 30 Ainsi l'utilisateur précise à partir des éléments de référence qu'il est nécessaire de raisonner en modulo 2. Avec cette indication dans les éléments de référence, le module d'analyse 7 peut vérifier la compatibilité. Par la suite, le code source est affiché sans erreurs et se compile vers un programme parallèle. [0085] Dans un quatrième exemple de décomposition parallèle, qui comme dans le troisième exemple reprend le partage des tableaux de manière beaucoup plus fine qu'un simple découpage en deux intervalles avec une boucle qui opère sur les éléments pairs et l'autre sur les éléments impairs dans un code enregistré de façon obscure. À titre d'exemple un code est présenté en annexe 26. Comme dans le troisième exemple, la composition parallèle requiert l'intervention de l'utilisateur programmeur. En effet, l'analyse d'effets va inviter l'utilisateur programmeur à fournir des éléments de référence précis afin de pouvoir vérifier la compatibilité de la composition parallèle. À titre d'exemple un code est présenté en annexe 27. Par contre, avec la complexité du code de ml et m2, les éléments de référence au niveau de l'opérateur de composition parallèle risquent de ne pas être suffisants. Après une vérification réalisée par le module d'analyse, il est impossible de vérifier la validité de ces éléments de référence. À titre d'exemple un code est présenté en annexe 28. On voit ici un deuxième cycle d'interaction. Le programmeur fournit les annotations 20 nécessaires dans le code de ml et m2. À titre d'exemple, un code est présenté en annexe 29. Et avec ces indications supplémentaires, le module d'analyse arrive à terminer sa preuve. Le code source est affiché sans erreurs et compilé vers un programme 25 parallèle. [0086] Dans le cas ou les deux boucles accèdent effectivement aux mêmes éléments, il sera impossible de prouver la correction de la composition parallèle quelles que soient les annotations rajoutées par le programmeur. À titre d'exemple, un code est présenté en annexe 30. Par contre les indications données par le module d'analyse peuvent aider à comprendre pourquoi cette composition est impossible. [0087] Dans le cas de sérialisation par sémaphore ou mutex, et comme dans l'exemple précédent, la correction du programme est garantie en sérialisant les 5 accès aux ressources partagées au moyen de sémaphores. À titre d'exemple, un code est présenté en annexe 31. Les informations sur les sémaphores peuvent être utilisées par le module d'analyse et peuvent aussi apparaître explicitement dans les éléments de référence. À titre d'exemple, un code est présenté en annexe 32. 10 Ces éléments de référence indiquent que tous les accès aux éléments de a et b sont protégés par le � locked(o) � (qui signifie que tous les accès de tableau sont verrouillés û � locked � - par le verrou o), la combinaison parallèle est donc autorisée. Le code source sera présenté sans erreur au programmeur et compilé 15 vers un programme parallèle. Ces indications explicites de verrouillage (� locking �) dans les éléments de référence peuvent être utilisées par ailleurs pour une vérification de l'absence d'interblocage. [0088] Ainsi, lors de l'étape de vérification 3 qui se déroule pendant la réalisation de la compilation du code source 15 le module d'analyse 7 chargé dans les moyens de mémoire 40 du terminal, analyse les éléments de référence 14 compris dans ce code source 15 de programmation de sorte que : • si tous les éléments de référence sont identifiés comme corrects, le compilateur 16 génère 6 le programme parallèle 21, • si au moins un élément de référence 14 est identifié comme incorrect, des informations représentatives de cet au moins un élément de référence identifié comme incorrect sont affichées par le terminal 32 à partir des moyens d'affichage 39 de manière à ce que l'utilisateur puisse effectuer des corrections, et 30 • si aucun élément de référence n'est identifié, des informations représentatives de cet état sont affichées par le terminal à partir des moyens d'affichage de manière à ce que l'utilisateur puisse effectuer des corrections. L'étape de vérification est répétée tant que tous les éléments de référence compris dans le code source de programmation ne sont pas identifiés comme correct par le compilateur. [0089] On peut noter que le module d'analyse comprend des moyens qui lui permettent de corriger les éléments de référence identifiés comme incorrects dans le code source. Ce module d'analyse peut aussi générer au moins un élément de référence lorsque aucun élément de référence n'a été identifié dans le code source durant l'étape de vérification. Les informations relatives aux éléments de référence corrigés et à au moins un élément de référence généré par le module d'analyse sont transmises à l'utilisateur à partir des moyens d'affichage du terminal de sorte que celui-ci puisse sélectionner les éléments de référence corrigés ou générés à partir des moyens de saisie. Ainsi le module d'analyse est capable de suggérer des éléments de références appropriés (donc identifiable comme étant corrects) et de les insérer dans le code source automatiquement ou après validation de l'utilisateur programmeur. [0090] On voit clairement sur ces exemples que le module d'analyse utilisé est interchangeable. Selon la "puissance" du module d'analyse utilisé, il peut être nécessaire ou non d'ajouter certains éléments de référence. Avec les avancées dans le domaine de la preuve automatique, on peut s'attendre à devoir mettre de moins en moins d'éléments de référence. Mais la théorie nous assure que jamais un module d'analyse ne pourra fonctionner de manière totalement automatique en se passant totalement d'éléments de référence et d'interaction avec le programmeur. [0091] On peut remarquer qu'il est possible de mémoriser les preuves réalisées en les stockant par exemple avec le code objet généré. L'intérêt est que les preuves peuvent alors être réutilisées facilement : il n'est plus nécessaire de chercher à refaire une preuve, mais simplement à vérifier que la preuve mémorisée est correcte. [0092] En outre, dans le cas où un programme utilise des annotations � shared � pour indiquer de façon explicite que des processus en parallèle peuvent partager des zones de mémoire, d'autres systèmes d'annotations peuvent être introduits pour vérifier que les accès partagés sont correctement gérés, par exemple que les variables sont protégées par des sémaphores [0093] Ainsi, il est entendu que l'invention n'est pas limitée aux exemples de réalisation décrits et illustrés. Le code impératif peut se rapporter à tous les langages de programmation autres que JavaTM. De plus le procédé selon l'invention peut être appliqué au développement de programmes parallèles synchrones, dans lesquelles est présente la notion de temps global et où les échanges de messages ont lieu à certains instants bien définis (on y parle souvent d'� événements � plutôt que de � messages �).15 ANNEXES ANNEXE 1 : int[] a ; // a est un tableau de 1000 éléments parallel(int i : 0..999) { writes a[i] : p(i) } ANNEXE 2 : 15 int[] a ; // a est un tableau de 1000 éléments for(int i : 0..999) { PO) ; } 20 ANNEXE 3 : int[] a ; // a est un tableau de 1000 éléments @Sequential parallel(int i : 0..999) { writes a[i] : p(i) 25 } ANNEXE 4 : int[] a ; // a est un tableau de 1000 éléments @Thread Pool(g rainSize=10) 30 parallel(int i : 0..999) { writes a[i] : p(i) } ANNEXE 5: 10 parallel(int i : 1..3) p(i) ; // équivalent à parallel { p(1) Il p(2) II p(3) } parallel(int i : 1..2, int j : 1..2, j > i) p(i,j); // équivalent à parallel { p(1 ,1) Il p(1,2) II p(2,2) } ANNEXE 6 : // le tableau c contient la liste de tous les canaux à écouter 10 Channel channels[] ; void webServer() { replicate(Channel c : channels) 15 { read c ? requête ; Xml réponse = traitement(requête) ; send c ! réponse ; 20 } ANNEXE 7: float sum = 0.0; 25 @GrainSize(100) parallel(int i : 0 .. a.length-1) reads a[i]; shared sum; // on partage sum en utilisant le fait que // l'addition est associative-commutative 30 // le résultat sera donc déterministe { // pas besoin de lock si += est une opération atomique sum += a[i]; } 36 } ANNEXE 8: @GrainSize(10, 20) parallel(int i : O..M, int j : 0.. N) reads a[i][*], b[*]Gl // ligne de a, colonne de b writes c[i][j] // intersection de ligne et colonne { float sum = 0.0; forint k : 0..L-1) { sum += a[i][k] * b[k][j] ; } c[i][j] = sum ; ANNEXE 9 : class SequentialVector { private int array ; private int currentSize = 0 ; public void add(int element) writes array, currentSize; { if(currentSize + 1 >= array.length) { int newArray = new int[array.length * 2]; replicate(int I : 0..currentSize-1) reads array[i] writes newArray[i] { 30 newArray[i] = array[i]; } array = newArray; } 20 25 } array[currentSize++] = element; } ... autres méthodes de la classe ... } ANNEXE 10 : SequentialVector v = ... ; parallel { writes v.array: v.add(1) I I writes v.array: v.add(2) } // erreur : ANNEXE refusé 15 ANNEXE 11 : class ConcurrentVector { public void add(int element) shared array, currentSize; { } } ANNEXE 12 : ConcurrentVector v = ... , parallel shared v { v.add(1) I I v.add(2) 20 25 30 38 OK, programme accepté ANNEXE 13 : void ampliOp(Channel for(int i=0; i void m() { 35 for(int i=0; i void ml () // traite al et b1 { for(int i=0; K5; i++) { al [i] = a1 [i] + bl [i]; } } void m2() // traite a2 et b2 // (code impératif) { for(int i=0; i parallel // (code algébrique) 25 ml () // (code impératif) Il // (code algébrique) m2() // (code impératif) 30 ANNEXE 19,: 20 parallel reads al [*], b1 [*] writes al [*] ml() 35 ll reads a2[*], b2[*] writes a2[*j m2() // (code algébrique) // (éléments de référence) // (code impératif) // (code algébrique) // (éléments de référence) // (code impératif) 40 ANNEXE 20 : void m1() // traite les 5 premiers éléments de a et b { for(int i=0; 1 void m2() // traite les 5 derniers éléments de a et b { for(int i=5; i parallel // (code algébrique) ml() // (code impératif) Il // (code algébrique) m2() // (code impératif) ANNEXE 22 : parallel // (code algébrique) reads a[0-4], b[0-4] writes a[0-4] // (éléments de référence) ml() // (code impératif) I I // (code algébrique) reads a[5-9], b[5-9] writes a[5-9] // (éléments de référence) m2() // (code impératif) 25 ANNEXE 23 : void ml() // traite les éléments pairs { for(int i=0; i parallel // (code algébrique) 10 reads a[i:i%2==0], b[i:i%2==0] writes a[i:i%2==0] // (éléments de référence) ml() il (code imperative) Il // (code algébrique) reads a[i:i%2!=0], b[:i%2!=0] writes a[:i%2!=0] Il (éléments de référence) m2() II (code imperative) 15 ANNEXE 26 : void ml() // traite les éléments pairs 20 { for(int i=0; k20; i=i+4) { // accède dans l'ordre les éléments 0,4,8,2,6 a[i%10] = a[i%10] + b[i%10]; } } void m2() // traite les éléments impairs { for(int i=1; k20; i=i+4) { // accède dans l'ordre les éléments 1,5,9,3,7 a[i%10] = a[i%10] + b[i%10]; } ANNEXE 27 : parallel // (code algébrique) ml() // (code impératif) 40 Il // (code algébrique) m2() // (code impératif) ANNEXE 28 : 45 parallel // (code algébrique) reads a[i:i%2==0], b[i:i%2==0] writes a[i:i%2==0] // (éléments de référence) ml() // (code imperative) 5 }35 Il // (code algébrique) reads a[i:i%2!=0], b[:i%2!=0] writes a[:i%2!=0] 11 (éléments de référence) m20 // (code imperative) ANNEXE 29 : void ml () 11 traite les éléments pairs { for(int i=0; i void ml() // accède tous les éléments { 20 for(int i=0; i 25 void m2() // accède tous les éléments { for(int i=0; i ANNEXE 31 : Object o; void m1() // accède tous les éléments { for(int i=0; i ANNEXE 32 : parallel // (code algébrique) reads a[*1 locked(o), b[*] locked(o) writes a[*1 locked (o) // (éléments de référence) m1() // (code imperative) Il // (code algébrique) reads a[*] locked(o), b[*] locked(o) writes a[*] locked (o) // (éléments de référence) m2() // (code imperative) 15 20 25 } 30 REVENDICATIONS1. Procédé de développement de programme parallèle à partir d'un terminal, caractérisé en ce qu'il comprend les étapes suivantes : création (1) dans des moyens de mémoire du terminal d'un fichier (11), enregistrement (2) à partir des moyens de saisie (38) et d'affichage (39) du terminal (32) d'un code source de programmation, ledit code source (15) étant relatif à une combinaison de code impératif (12), de code algébrique (13) et d'éléments de référence (14) ; vérification (3), lors de la compilation, par un module d'analyse (7) chargé dans les moyens de mémoire (40) du terminal, de chaque élément de référence compris dans le code source de programmation de sorte que : si tous les éléments de référence (14) sont identifiés comme corrects, le compilateur génère (6) le programme parallèle, • si au moins un élément de référence est identifié comme incorrect, des informations représentatives de cet au moins un élément de référence identifié comme incorrect sont affichées par le terminal à partir des moyens d'affichage de manière à ce que l'utilisateur puisse effectuer des corrections (4), et • si aucun élément de référence n'est identifié, des informations représentatives de cet état sont affichées par le terminal à partir des moyens d'affichage de manière à ce que l'utilisateur puisse effectuer des corrections (4), - répétition (5) de l'étape précédente de vérification tant que tous les éléments de référence compris dans le code source de programmation ne sont pas identifiés comme correct par le compilateur. 2. Procédé selon la revendication 1, dans lequel l'étape de vérification 30 comprend une sous-étape de contrôle par le module d'analyse qu'une même zonede mémoire du terminal ne sera pas écrasée lors de l'exécution en parallèle d'au moins deux tâches différentes résultant du traitement du programme parallèle. 3. Procédé selon la revendication précédente, dans lequel l'affichage d'informations est réalisé à partir d'une représentation graphique (17) structurée en forme d'arbre comprenant des sections à exécuter en parallèle ainsi que les éléments de référence associés à ces sections. 4. Procédé selon l'une quelconque des revendications précédentes, dans lequel lesdites informations affichées comprennent des données se rapportant à des propositions de corrections des éléments de référence identifiés comme incorrects et à l'emplacement dans le code source de programmation où elles doivent être enregistrées. 5. Procédé selon l'une quelconque des revendications précédentes, dans lequel lesdites informations affichées comprennent des données relatives à des erreurs comprises dans une composition parallèle. 6. Procédé selon l'une quelconque des revendications précédentes, 20 comprenant une étape de transmission du fichier audit compilateur, succédant à l'étape d'enregistrement. 7. Procédé selon l'une quelconque des revendications précédentes, dans lequel le module d'analyse corrige les éléments de référence identifiés comme 25 incorrects dans le code source. 8. Procédé selon l'une quelconque des revendications précédentes, dans lequel le module d'analyse génère au moins un élément de référence lorsque aucun élément de référence n'est identifié dans le code source. 9. Procédé selon l'une quelconque des revendications 7 ou 8, dans lequel les informations relatives aux éléments de référence corrigés et audit au moins un 30élément de référence généré par le module d'analyse sont transmises à l'utilisateur à partir des moyens d'affichage du terminal de sorte que celui-ci puisse sélectionner les éléments de référence corrigés ou générés à partir des moyens de saisie. 10. Système de développement de programme parallèle, pour la mise en oeuvre du procédé selon la revendication 1, caractérisé en ce qu'il comprend le terminal (32) comportant les moyens de saisie (38) reliés aux moyens d'affichage (39) et associés à aux moyens de traitement (33) permettant de créer un fichier comprenant de code source de programmation, lesdits moyens de traitements (33) du terminal comprenant au moins un processeur (34) et les moyens de mémoire (40) pour la mise en oeuvre d'un compilateur aptes à traiter le contenu dudit fichier (11) par la vérification des éléments de référence (14) compris dans le code source de programmation, ledit système permettant à partir des moyens d'affichage (39) du terminal d'afficher des informations représentatives d'au moins un élément de référence et à générer un programme parallèle (21). 11. Système selon la revendication précédente, dans lequel les moyens d'affichage (39) et de saisie (38) permettent à l'utilisateur de modifier le contenu dudit fichier en fonction d'informations représentatives d'au moins un élément de référence identifié par le compilateur (16) dans graphe structuré en forme d'arbre (17). 12. Système selon la revendication précédente, dans lequel les moyens de 25 mémoires du terminal comprennent un module d'analyse.