SYSTEME D'ORDONNANCEMENT DE L'EXECUTION DE TACHES CADENCE PAR UN
TEMPS LOGIQUE VECTORIEL
Domaine technique de l'invention L'invention est relative à l'ordonnancement de l'exécution de tâches interdépendantes dans un système multi-tâches, notamment dans le cadre de l'exécution de tâches d'un processus flot de données pouvant inclure du contrôle dépendant des données.
État de la technique Un problème récurrent dans les systèmes multi-tâches est l'ordonnancement des tâches, c'est-à-dire l'exécution de chaque tâche à un moment où toutes les conditions requises par la tâche sont réunies. Ces conditions sont notamment la disponibilité de données consommées par la tâche et la disponibilité d'un espace pour recevoir les données produites par la tâche, dans le cas d'un traitement de type flot de données.
Il existe diverses méthodes d'ordonnancement de tâches, par exemple basées sur la construction et le parcours de graphes. Certaines méthodes visent l'optimisation des performances, tandis que d'autres visent la sûreté de fonctionnement. Les méthodes visant la sûreté de fonctionnement tentent de réduire, voire supprimer, la possibilité d'inter-blocage (« deadlock » en anglais), survenant, par exemple, dans une situation où deux tâches ne peuvent s'exécuter car la méthode employée détermine que l'exécution de chacune de ces tâches dépend de l'exécution de l'autre tâche.
La demande de brevet US2008-0005357 décrit une méthode applicable aux traitements de type flot de données, visant l'optimisation des performances. La méthode est basée sur la construction de graphes et de circulation de jetons. Une tâche ne peut s'exécuter que si elle dispose d'un jeton produit par une autre tâche. Lorsque la tâche est exécutée, le jeton est transmis à la tâche suivante. Il s'agit d'une transcription assez directe d'un modèle de calcul qui ne permet pas de tenir compte de contraintes offrant des garanties de sûreté de fonctionnement.
Résumé de l'invention
On a donc besoin d'une méthode d'ordonnancement offrant à la fois des bonnes performances et une sûreté de fonctionnement.
On tend à satisfaire ce besoin en prévoyant un procédé d'exécution de plusieurs tâches interdépendantes sur un système multi-tâches, comprenant les étapes suivantes : associer à chaque tâche un vecteur de temps logique indicatif des dépendances de la tâche par rapport à un ensemble d'autres tâches, et définir une relation d'ordre partiel sur l'ensemble des vecteurs de temps logique, de manière qu'une tâche successeur qui dépend de l'exécution d'une occurrence d'une tâche prédécesseur ait un vecteur supérieur à celui de la tâche prédécesseur ; exécuter la tâche excepté si son vecteur est supérieur au vecteur de l'une quelconque des autres tâches ; et mettre à jour le vecteur d'une tâche terminée pour refléter les dépendances d'une nouvelle occurrence de la tâche terminée. Selon un mode de mise en œuvre, le procédé comprend les étapes suivantes : associer à chaque tâche un compteur de dépendances indicatif du nombre de conditions à satisfaire pour exécuter une occurrence de la tâche ; lorsqu'une tâche est terminée, décrémenter le compteur de dépendances de chaque tâche ayant un vecteur supérieur à celui de la tâche terminée ; mettre à jour le vecteur de la tâche terminée pour refléter les dépendances d'une nouvelle occurrence de la tâche ; incrémenter le compteur de dépendances de la tâche terminée pour chaque tâche dont le vecteur est inférieur à celui de la tâche terminée ; et incrémenter le compteur de dépendances de chaque tâche ayant un vecteur supérieur à celui de la tâche terminée. Selon un mode de mise en œuvre, le vecteur de temps logique d'une tâche courante comprend une composante associée à chaque tâche possible. La composante associée à la tâche courante contient le numéro d'occurrence de la tâche courante. Une composante associée à une autre tâche identifie l'occurrence de cette autre tâche qui doit être terminée avant que la tâche courante puisse être exécutée, une composante nulle indiquant que la tâche courante ne dépend pas de la tâche associée à la composante nulle.
Afin d'accélérer le procédé, on prévoit dans un système processeur un module de comparaison de deux données de Nm bits, comprenant une sortie de comparaison indicative d'une relation d'ordre entre les deux données, ladite
sortie étant définie par une table comprenant des rangées associées aux valeurs possibles consécutives de la première donnée et des colonnes associées aux valeurs possibles consécutives de la deuxième donnée, où chaque rangée comprend un état 1 à l'intersection avec la colonne associée à la même valeur, suivi d'une série d'états 0. La série d'états 0 est suivie d'une série d'états 1 complétant la rangée de manière circulaire, le nombre d'états 0 étant le même pour chaque rangée et inférieur à la moitié de la valeur maximale des données.
Un comparateur de deux vecteurs selon une relation d'ordre partiel, où chaque vecteur comprend des composantes ayant un nombre de bits multiple de Nm, comprend une pluralité de modules de comparaison du type susmentionné, connectés dans une chaîne par des bornes de propagation de retenue ; une porte disposée entre les bornes de propagation de retenue de deux modules consécutifs, apte à interrompre la propagation de retenue entre lesdits modules consécutifs en réponse à un état actif d'un signal déterminant une frontière entre composantes ; et une porte disposée à la sortie de comparaison, apte à inhiber la prise en compte de l'état de cette sortie en réponse à un état inactif du signal de détermination de frontière.
Selon un mode de réalisation, chaque module comprend une sortie d'égalité indicative de l'égalité des données présentées au module, et le comparateur comprend de la logique prévue pour établir une indication active si et seulement si toutes les sorties de comparaison des modules sont actives et la sortie d'égalité d'au moins un module est inactive.
Description sommaire des figures D'autres avantages et caractéristiques ressortiront plus clairement de la description qui va suivre de modes particuliers de réalisation donnés à titre d'exemples non limitatifs et illustrés à l'aide des dessins annexés, dans lesquels : la figure 1 représente un exemple élémentaire de succession de tâches à exécuter dans un processus de type flot de données ; la figure 2 est un graphe révélant les dépendances entre différentes occurrences de chaque tâche de la figure 1 ;
la figure 3 correspond au graphe de la figure 2, où chaque occurrence d'une tâche est numérotée par un vecteur de temps logique utilisé pour identifier les dépendances entre occurrences des tâches ; la figure 4 reprend le graphe de la figure 3 avec des durées d'exécution différentes pour certaines occurrences de tâches ; la figure 5 représente un exemple de succession de tâches dans un processus de type flot de données, avec deux tâches à exécution alternative ; la figure 6 est un graphe numéroté par des vecteurs de temps logique des occurrences des tâches de la figure 5 ; la figure 7 est un graphe représentant un exemple de trace d'exécution d'un traitement correspondant à la figure 5, numéroté par des vecteurs de temps logique et des valeurs de compteurs de dépendances ; la figure 8 représente un graphe d'un autre cas de trace d'exécution ; et - la figure 9 représente schématiquement un mode de réalisation de comparateur de vecteurs selon un ordre partiel.
Description d'un mode de réalisation préféré de l'invention
Afin de réaliser le suivi des conditions à satisfaire pour démarrer une occurrence d'une tâche dans un système multi-tâches, notamment des tâches d'un processus de type flot de données, on prévoit ici de maintenir pour chaque tâche un vecteur de temps logique représentatif des dépendances de la tâche.
Ci-après, on entend par « tâche » un ensemble générique de traitements. Par « exécution » de la tâche, ou une « occurrence » de la tâche, on entend l'exécution de la tâche sur un jeu spécifique de données (dans un traitement de type flot de données, on exécute des occurrences consécutives d'une même tâche sur des jeux de données consécutifs d'un flot entrant). Les vecteurs de temps logique sont associés à chaque tâche et reflètent les dépendances de l'occurrence courante de la tâche.
La notion de temps logique vectoriel est introduite dans les articles [M. Raynal et M. Singhal, "Logical time: capturing causality in distributed Systems", IEEE
Computer 29(2), 1996] et [C. Fidge, "Logical time in distributed Computing Systems", IEEE Computer 24(8), 1991 ].
Les vecteurs de temps logique, associés à une relation d'ordre partiel, ont été utilisés pour dater des événements transmis d'un processus à un autre, afin que chaque processus qui reçoit des événements par des voies distinctes puisse les réordonner de manière causale. En d'autres termes, un vecteur de temps logique sert normalement à identifier et dater relativement un événement survenu dans le passé.
Comme on le comprendra ci-après, les vecteurs de temps logique sont utilisés, dans la présente demande, pour déterminer à partir de quel instant une tâche peut être exécutée. En d'autres termes, les vecteurs de temps logique servent à contraindre l'ordre d'exécution des tâches, c'est-à-dire à organiser des événements dans le futur.
Cette utilisation des vecteurs de temps logique sera décrite plus en détail ci- après à l'aide exemples de traitements de type flot de données.
La figure 1 symbolise un traitement élémentaire de type flot de données. Une tâche A fournit une donnée à une tâche B, qui la traite et fournit le résultat à une tâche C. Les tâches communiquent leurs données par l'intermédiaire de mémoires tampon (FIFO), dont la profondeur est de 3 dans cet exemple. Les conditions d'exécution de ces tâches sont les suivantes. La tâche A ne peut s'exécuter que si la première mémoire tampon n'est pas pleine. La tâche B ne peut s'exécuter que si la première mémoire tampon n'est pas vide et la deuxième mémoire tampon n'est pas pleine. La tâche C ne peut s'exécuter que si la deuxième mémoire tampon n'est pas vide. La figure 2 est un graphe représentant les dépendances entre les occurrences des tâches A , B et C. Les rangées correspondent respectivement aux tâches A, B et C. Les cercles consécutifs dans une rangée correspondent à des occurrences consécutives de la même tâche, numérotées dans les cercles. Les colonnes correspondent à des cycles d'exécution consécutifs, en supposant, pour simplifier, que chaque occurrence d'une tâche se termine en un cycle.
Des flèches relient des occurrences dépendantes. Chaque flèche signifie « doit avoir lieu avant ». En d'autres termes, dans le graphe tel que représenté,
chaque flèche doit pointer vers la droite ; elle ne peut ni pointer vers la gauche, ni être verticale. Les flèches en trait plein correspondent à des dépendances imposées par l'ordre d'exécution des tâches. Les flèches en pointillés correspondent aux dépendances imposées par la profondeur (limitée) des mémoires tampon.
Comme la première occurrence de la tâche A doit être exécutée avant la première occurrence de la tâche B, et que celle-ci doit être exécutée avant la première occurrence de la tâche C, les occurrences sont décalées d'un cycle d'une rangée à la suivante. La figure 3 représente le graphe de la figure 2 où chaque occurrence d'une tâche est annotée par un vecteur de temps logique selon le procédé visé ici. Un vecteur de temps logique est associé à chaque tâche, et mis à jour à la fin de chaque occurrence de la tâche. Comme les mises à jour de ces vecteurs correspondent à des incrémentations, ces vecteurs peuvent aussi être dénommés « horloges logiques », notées H.
Pour simplifier la description, on se place dans le cas le plus simple à comprendre où chaque vecteur ou horloge H comprend une composante associée à chaque tâche exécutable sur un système multi-tâches. Il existe des techniques permettant, dans le cadre connu d'utilisation des vecteurs de temps logique, d'optimiser le nombre de composantes par rapport au nombre de tâches - ces techniques sont également applicables ici. Un exemple de telle technique est décrit dans l'article [P. A. S. Ward, « An offline algorithm for dimension-bound analysis », Proceeding of the 1999 IEEE International Conférence on Parallel Processing, pages 128-136]. Ainsi, à la figure 3, on a trois vecteurs H(A), H(B), et H(C) affectés respectivement aux tâches A, B et C, et chaque vecteur a trois composantes affectées respectivement aux tâches A, B et C.
Une composante h. associée à une tâche Ti d'un vecteur H(Tj) associé à une tâche Tj contient, par exemple, l'occurrence de la tâche Ti nécessaire à l'exécution de l'occurrence courante de la tâche Tj. Par extension, la composante hj associée à la tâche Tj contient l'occurrence de la tâche Tj en cours d'exécution. Une composante nulle indique que l'occurrence courante de
la tâche associée au vecteur ne dépend pas de la tâche associée à la composante nulle.
Par exemple, comme on l'a identifié à la figure 3 pour un cycle d'exécution t7, la première composante du vecteur H(A), correspondant à la tâche A, contient 7, qui est l'occurrence courante de la tâche A. Cette occurrence de la tâche A nécessite que la première mémoire tampon (figure 1 ) dispose d'au moins un emplacement, c'est-à-dire que la 4e occurrence de la tâche B ait consommé une donnée dans cette mémoire tampon ; la composante (la 2e) associée à la tâche B dans le vecteur H(A) contient 4. La 4e occurrence de la tâche B nécessite que la deuxième mémoire tampon dispose d'au moins un emplacement, c'est-à-dire que la 1 e occurrence de la tâche C ait consommé une donnée dans cette mémoire tampon ; la composante (la 3e) associée à la tâche C dans le vecteur H(A) contient 1 .
On construit chaque vecteur à partir du graphe en suivant vers l'arrière les flèches de l'occurrence considérée vers l'occurrence la plus proche de chacune des autres tâches. Ainsi, le vecteur H(B) à l'instant t7 contient (6, 6, 3), et le vecteur H(C) contient (5, 5, 5). S'il n'existe pas de telle flèche à suivre en arrière, la composante est nulle, ce qui est le cas pour les premières occurrences des tâches A et B. La construction des vecteurs s'avère simple à réaliser à l'exécution d'une application mettant en œuvre les tâches. On s'aperçoit qu'il suffit, à partir d'une certaine occurrence (ici la sixième pour la tâche A, la troisième pour la tâche B, et la première pour la tâche C), d'incrémenter systématiquement chacune des composantes à chaque exécution de la tâche associée. Il suffit de définir à l'avance les conditions initiales et de mise à jour des vecteurs, ce qui peut être fait par le compilateur, en fonction du type de graphe décrivant les dépendances des tâches. Ces conditions s'expriment sous la forme « incrémenter la composante Xi du vecteur X à partir de la k-ième occurrence ». Les vecteurs sont stockés dans une mémoire partagée et mis à jour par un processus d'ordonnancement des tâches auprès duquel chaque tâche est « enregistrée » par l'application.
A titre d'exemple, les conditions initiales et de mise à jour du vecteur H(A) dans la figure 3 peuvent être définies de la manière suivante :
Afin d'exploiter les vecteurs de temps logique par la suite, on définit une relation d'ordre partiel sur l'ensemble de ces vecteurs. La relation d'ordre partiel entre deux vecteurs X(xo, X2, ... xn) et Y(yo, yi , ... yn) est définie telle que :
X < Y est vrai si et seulement si : quel que soit i compris entre 0 et n, on et il existe j compris entre 0 et n tel que xj < yj.
Cette relation d'ordre est dite « partielle » parce qu'elle n'ordonne pas tous les vecteurs entre eux. Dans certains cas, les vecteurs X et Y ne sont pas comparables, ce que l'on note par X || Y.
On considère maintenant une tâche Ta en attente d'exécution, et on souhaite savoir à un instant courant si cette tâche peut être exécutée. Pour cela, on compare le vecteur courant de la tâche Ta à chacun des vecteurs courants des autres tâches. La tâche Ta peut être exécutée seulement si, quelle que soit l'autre tâche T, on a :
H (Ta) < H(T) ou H (Ta) || H(T), condition que l'on pourra aussi noter
Si pour au moins une autre tâche T on a H(Ta) > H(T), toutes les conditions ne sont pas réunies pour exécuter la tâche Ta, donc la tâche Ta doit attendre.
Dans le graphe de la figure 3, qui correspond à un cas simpliste, on s'aperçoit que les vecteurs dans chaque colonne à partir de la troisième sont incomparables deux à deux. Cela signifie que chacune des tâches correspondantes peut être exécutée en parallèle.
Pour la première colonne, on a H(C) > H(B) > H(A), signifiant que seule la tâche A peut être exécutée.
Pour la deuxième colonne, on a H(C) > H(B), H(B) || H (A) et H (A) || H(C), signifiant que les tâches A et B peuvent être exécutées en parallèle, mais que la tâche C doit attendre.
Dans un cas plus proche de la réalité, les tâches arrivent avec plus ou moins de retard et elles mettent plus ou moins longtemps à s'exécuter.
La figure 4 représente le graphe de la figure 3 modifié pour représenter un cas plus proche de la réalité. Les deux premières occurrences de la tâche B durent deux fois plus longtemps que les autres occurrences. Il en résulte que : la première occurrence de la tâche C démarre avec un cycle de retard, la deuxième occurrence de la tâche C démarre avec deux cycles de retard, et la cinquième occurrence de la tâche A démarre avec un cycle de retard.
Le vecteur de temps logique d'une tâche reste constant sur le nombre de cycles requis pour l'exécution de la tâche associée, ce que l'on voit pour les deux premières occurrences de la tâche B. Un vecteur est mis à jour au moment où la tâche se termine. Ainsi, comme on le voit pour les tâches A et B à la cinquième colonne, la nouvelle valeur du vecteur est en vigueur dès la fin de la tâche associée, et invariante pendant le temps d'attente d'une nouvelle occurrence de la tâche (ce qui est le cas aussi pendant le temps d'attente de l'exécution de la première occurrence des tâches B et C).
On comprendra mieux l'utilisation des vecteurs de temps logique à l'aide de ce graphe. A la troisième colonne, on a H(C) > H(B). Donc, contrairement au cas de la figure 3, la tâche C ne peut encore démarrer. La tâche C peut démarrer à la quatrième colonne, où les vecteurs deviennent incomparables deux à deux. A la cinquième colonne, on a H(A) > H(B) et H(C) > H(B). Ainsi, les tâches A et C doivent attendre, tandis que la tâche B s'exécute. Les tâches A et C pourront être exécutées à la sixième colonne, où les vecteurs deviennent incomparables deux à deux.
On s'aperçoit que le graphe peut ainsi se prolonger jusqu'à l'infini, et donc loger des occurrences de n'importe quelle durée avec n'importe quel retard. Cela garantit l'absence d'inter-blocages.
Comme on l'a précédemment évoqué, les vecteurs de temps logique sont mis à jour par incrémentation systématique de leurs composantes. On ne peut pas envisager en pratique que les composantes tendent vers l'infini. On prévoit de préférence un mécanisme de repliement basé sur une relation d'ordre partiel adaptée sur un sous-ensemble des entiers naturels. Ainsi, les composantes des vecteurs sont définies modulo M, et la relation d'ordre partiel entre deux vecteurs X(xo, X2, . . . xn) et Y(yo, yi , ... yn) est définie telle que :
X < Y est vrai si et seulement si : quel que soit
la relation
étant vraie si et seulement si :
M et S sont des entiers tels que 2S < M, et M est supérieur à l'écart maximum entre composantes d'un vecteur. Dans le cas de la figure 3, l'écart maximum est de 6, pour le vecteur H(A) à partir de la septième occurrence. Cet écart maximum est déterminable à partir de l'instant où toutes les conditions initiales ont été prises en compte, c'est-à-dire à partir de l'instant où on commence à incrémenter toutes les composantes de tous les vecteurs.
Dans l'exemple de la figure 3, avec M = 8 et S = 3, les composantes des vecteurs sont repliées à parti de la valeur 7. Les deux derniers vecteurs du graphe pour la tâche A s'expriment ainsi par (0, 5, 2) et (1 , 6, 3), et le dernier vecteur du graphe pour la tâche B s'exprime par (0, 0, 5).
En considérant les huit valeurs possibles de chaque composante sur un cercle, la comparaison des composantes par la relation « inférieur à »
définie ci- dessus est telle qu'une valeur x est inférieure à chacune des 3 (S) valeurs suivantes, et supérieure à chacune des 4 (M - S -1 ) valeurs précédentes sur le cercle. On a par exemple :
Selon la méthodologie précédemment décrite, à chaque cycle d'exécution, on compare le vecteur de temps logique de chaque tâche à chacun des vecteurs des autres tâches, pour savoir si la tâche peut être exécutée. Cela représente des ressources de calcul significatives si le nombre de tâches est important : le nombre de comparaisons augmente de manière quadratique avec le nombre de tâches. En outre, même si le résultat des comparaisons indique qu'une tâche peut être exécutée, il se peut que la tâche ne puisse pas être exécutée immédiatement compte tenu des ressources de calcul disponibles (on dira que la tâche est exécutable). Il convient donc de gérer une liste de tâches exécutables.
Pour réduire les ressources de calcul, et faciliter la planification des tâches exécutables, on propose d'associer à chaque tâche un compteur de dépendances, noté K, dont le contenu est représentatif du nombre de conditions à satisfaire pour que la tâche soit exécutable. En pratique, le contenu du compteur est égal au nombre de conditions restant à satisfaire ; lorsque le contenu devient nul, la tâche devient exécutable.
Afin de mettre à jour les compteurs de dépendances, on procède de la manière suivante.
A l'initialisation du système, on effectue : H(T) := Ho(T) et K(T) := 0, où Ho(T) est un vecteur de départ pour la tâche T, par exemple (1 , 0, 0) pour la tâche A, (1 , 1 , 0) pour la tâche B, et (1 , 1 , 1 ) pour la tâche C, dans le cas de la figure 3.
Ensuite, le processus d'ordonnancement observe les contenus des compteurs de dépendances et lance l'exécution de chaque tâche dont le compteur est à zéro, ou bien planifie l'exécution de ces tâches si les ressources ne permettent pas de lancer leur exécution en parallèle.
A chaque fois qu'une tâche T se termine, les quatre étapes suivantes sont effectuées, et cela de manière atomique, c'est-à-dire avant de lancer l'exécution d'une nouvelle occurrence d'une tâche : 1. Pour chaque autre tâche Ta pour laquelle on a H(Ta) > H(T), on effectue K(Ta) := K(Ta) - 1 . En d'autres termes, la tâche T qui vient de se terminer
remplit l'une des conditions pour que chacune des ces tâches Ta puisse s'exécuter.
2. On met à jour le vecteur H(T) pour la nouvelle occurrence de la tâche T.
Comme on l'a évoqué, cela peut être réalisé en incrémentant chaque composante du vecteur dès que le nombre d'occurrences atteint une valeur seuil définie pour la composante dans les conditions initiales.
3. Pour chaque autre tâche Ta pour laquelle on a H(T) > H(Ta), on effectue K(T) := K(T) + 1 . En d'autres termes, on identifie toutes les conditions requises pour l'exécution de la nouvelle occurrence de la tâche T, et on les comptabilise dans le compteur de dépendances de la tâche T.
4. Pour chaque autre tâche Ta pour laquelle on a H(Ta) > H(T), on effectue K(Ta) := K(Ta) + 1 . En d'autres termes, on identifie les nouvelles conditions créées par la nouvelle occurrence de la tâche T pour les autres tâches Ta, et on les comptabilise dans les compteurs de dépendances de ces autres tâches.
Les compteurs de dépendances peuvent être réalisés sous forme matérielle et surveillés en parallèle par un circuit de détection de contenu nul. On peut par ailleurs aussi stocker les vecteurs de temps logique dans des registres dédiés, reliés à des comparateurs sous forme matérielle, connectés pour incrémenter et décrémenter les compteurs selon les règles susmentionnées. (Bien entendu, on prévoit suffisamment de compteurs matériels et registres dédiés aux vecteurs pour satisfaire le nombre de tâches distinctes prévues dans les applications que l'on souhaite exécuter sur le système.) Dans ce cas, le logiciel système (le processus d'ordonnancement) se charge seulement de la mise à jour des vecteurs dans les registres dédiés, les comparaisons et mises à jour des compteurs étant réalisées par accélération matérielle.
Les compteurs de dépendances fournissent un indicateur d'imminence d'exécution, ils peuvent donc être utilisés, par exemple, pour piloter des opérations de préchargement des données. En outre, on s'aperçoit que le nombre de comparaisons croît de manière linéaire en fonction du nombre de tâches.
La figure 5 représente un exemple plus complexe de succession de tâches dans un processus de type flot de données, avec deux tâches à exécution
alternative. La tâche B de la figure 1 comprend ici deux tâches, B et B', dont une seule est choisie pour exécution lorsque la tâche A se termine. Chaque donnée produite par une occurrence de la tâche A est aiguillée par un élément de sélection SEL vers l'une des tâches B et B'. La sélection est opérée par une donnée de commande CTL, également produite par la tâche A, et empilée dans une mémoire FIFO de même profondeur que les mémoires FIFO disposées entre les tâches A, B et C. Cette donnée de commande CTL est prise en compte en même temps par un élément de fusion MRG qui choisit la sortie de la tâche B ou B' active pour la transmettre à la tâche C. La figure 6 est un graphe de dépendances correspondant au cas de la figure 5, représenté dans l'hypothèse simplifiée où les occurrences des tâches ont la même durée et ne présentent pas de retard (comme le graphe de la figure 3). On a noté les valeurs des vecteurs de temps logique à l'intérieur des nœuds représentant les occurrences. Les vecteurs ont ici quatre composantes. En outre, on a utilisé la notation repliée des vecteurs, avec des composantes définies modulo 8.
Pour des raisons de clarté, on n'a pas représenté toutes les flèches de dépendances. On a seulement représenté les flèches provenant des première et quatrième occurrences de chaque tâche, sachant que les autres ne constituent qu'une copie d'une occurrence à la suivante. Les dépendances sont construites de la même manière que pour le graphe de la figure 3, en considérant qu'une flèche arrivant ou partant d'une occurrence de la tâche B à la figure 3 est ici dupliquée pour chacune des tâches B et B'. Par ailleurs, il convient de noter la présence d'une flèche partant de chaque occurrence de la tâche B vers l'occurrence suivante de la tâche B', et d'une flèche partant de chaque occurrence de la tâche B' vers l'occurrence suivante de la tâche B.
Une particularité du flot de la figure 5 est qu'on exécute une seule des deux tâches B et B' entre les tâches A et C. Pour tenir compte de cela dans la méthodologie décrite ci-dessus, on émet l'hypothèse que les deux tâches B et B' sont exécutées en même temps à chaque exécution d'une seule de ces deux tâches. En d'autres termes, à chaque exécution de la tâche B ou B', on met à jour les vecteurs des deux tâches, et, dans le cas où on utilise des compteurs de dépendances K, on met à jour de la même manière les compteurs des deux tâches.
La figure 7 représente un exemple de trace d'exécution d'un traitement selon le graphe de la figure 6. Les nœuds en trait plein correspondent à des occurrences exécutées ou en cours d'exécution des tâches. Les nœuds en pointillés correspondent à des occurrences en attente d'exécution. Des flèches de dépendances n'apparaissent qu'à la fin de l'exécution d'une occurrence, c'est-à-dire au moment où on recalcule les vecteurs H et compteurs K. Chaque noeud contient les valeurs correspondantes du vecteur de temps logique et du compteur de dépendances K, dont les valeurs sont mises à jour selon les quatre étapes atomiques précédemment décrites. Pour les valeurs initiales des compteurs K des tâches A, B, B', et C, on suppose que chacune de ces tâches vient de se terminer et que son vecteur H a été mis à jour à sa valeur initiale. En appliquant la troisième étape de mise à jour des compteurs K à chacune des tâches, ceux-ci sont initialisés respectivement à 0, 1 , 1 , et 3. Au démarrage, on parvient à exécuter trois occurrences de la tâche A sur trois cycles consécutifs. La première de ces occurrences démarre la première occurrence de la tâche B qui met trois cycles à se terminer. On considère, du point de vue de son vecteur et de son compteur de dépendances, que la première occurrence de la tâche B' avance en même temps que la première occurrence de la tâche B.
La quatrième occurrence de la tâche A, la deuxième occurrence de la tâche B/B', en fait B', et la première occurrence de la tâche C peuvent démarrer au cinquième cycle. Considérant que les tâches B et B' se terminent en même temps au quatrième cycle, le compteur K de la tâche C au cinquième cycle se trouve décrémenté de 2, en appliquant deux fois la première étape de mise à jour des compteurs, une fois pour la tâche B, et une fois pour la tâche B'.
La quatrième occurrence de la tâche A dure 6 cycles, la deuxième occurrence de la tâche B' dure un cycle, et la première occurrence de la tâche C dure deux cycles. Au huitième cycle, pendant que la quatrième occurrence de la tâche A est encore en cours, on est parvenu à terminer la troisième occurrence de la tâche B/B' (en fait B) et lancer la deuxième occurrence de la tâche C. La quatrième
occurrence de la tâche B/B' (en fait B') doit attendre le onzième cycle, quand la quatrième occurrence de la tâche A se sera terminée.
Dans les exemples d'exécution de tâches décrits jusqu'à maintenant, on n'a pas montré l'utilité de la quatrième étape de mise à jour des compteurs K. La figure 8 est une trace d'un exemple simple d'exécution de deux tâches A et B où cette quatrième étape s'avère utile. On utilise les mêmes conventions de représentation qu'à la figure 7. Chaque occurrence d'une tâche A produit trois données, dont chacune est consommée par une occurrence distincte de la tâche B. On suppose également que la mémoire FIFO entre les tâches A et B a une profondeur de trois données - il en résulte que chaque occurrence de la tâche A remplit la mémoire FIFO d'un coup. Ainsi, une deuxième occurrence de la tâche A ne peut démarrer qu'après la troisième occurrence de la tâche B, qui finit de libérer la mémoire FIFO.
On remarque ici que la deuxième composante du vecteur H(A) est incrémentée de 3 à chaque exécution d'une occurrence de la tâche A, étant donné que le démarrage d'une occurrence de la tâche A est subordonné à l'exécution de trois occurrences consécutives de la tâche B. On remarque aussi que la première composante du vecteur H(B) n'est incrémentée qu'après chaque troisième exécution d'une occurrence de la tâche B. Cela traduit que trois occurrences consécutives de la tâche B sont subordonnées à une même occurrence de la tâche A.
Si on applique les quatre étapes du procédé de mise à jour des compteurs de dépendances K dès la fin de la première occurrence de la tâche B, on a, avec T = B et Ta = A : 1 . H (A) = (2, 3) > H(B) = (1 , 1 ) => K(A) := K(A) - 1 = 0 ;
2. H(B) := (1 , 2) ;
3. On n'a pas H(B) > H (A). K(B) reste inchangé ;
4. H (A) = (2, 3) > H(B) = (1 , 2) => K(A) := K(A) + 1 = 1 . On rétablit la valeur initiale, correcte, de K(A), qui avait été transitoirement modifiée à l'étape 1 .
Ces quatre étapes sont effectuées de manière atomique de manière que la valeur transitoire de K issue de l'étape 1 soit rétablie à sa valeur d'origine à l'étape 4 et n'affecte pas la liste des tâches prêtes.
A chacune des étapes 1 , 3 et 4 de mise à jour des compteurs K, on effectue N- 1 comparaisons de vecteurs de temps logique, où N est le nombre de tâches, et chaque comparaison consiste à comparer deux à deux au plus N composantes de vecteurs. Le nombre de comparaisons de composantes croît ainsi quadratiquement avec le nombre de tâches. Ces opérations peuvent être réalisées de manière logicielle par le processus d'ordonnancement, mais il serait souhaitable de prévoir une assistance matérielle permettant de libérer des ressources logicielles.
La comparaison étant une comparaison utilisant un ordre partiel, et les composantes étant de préférence bornées avec repliement, les comparateurs numériques classiques ne conviennent pas. La figure 9 représente les premiers éléments répétitifs d'un mode de réalisation de comparateur de vecteurs de temps logique HA et HB pouvant satisfaire ces besoins.
On suppose qu'un vecteur de temps logique est défini sur un nombre borné Nv de bits, par exemple 64, et que chaque composante de ce vecteur peut être définie sur un nombre programmable de bits, multiple d'un nombre minimal Nm, par exemple 4. Ce nombre Nm détermine le nombre maximal de composantes d'un vecteur. Ainsi, avec un vecteur de 64 bits et un nombre minimal de 4 bits, on peut définir au plus 16 composantes de 4 bits, et toute combinaison avec moins de composantes définies sur des multiples de 4 bits. Le comparateur de la figure 9 comprend une série de modules de comparaison 10 connectés à la chaîne. Chaque module 10 traite 4 bits de deux composantes à comparer de deux vecteurs HA et HB. Un module 10 peut être apparenté, du point de vue de ses bornes externes, à un comparateur basé sur un soustracteur effectuant la somme de son entrée A et du complément à 2 (~B + 1 ) de son entrée B. Ainsi, le module 10 comprend, outre une entrée pour chacune des données à comparer, une entrée de retenue Ci, une sortie de retenue Co, une sortie E indiquant si A = B, et une sortie GE indiquant si A□ B.
On pourra dans un premier temps, pour simplifier la description, considérer que les modules 10 sont des comparateurs classiques. Comme on le verra ci-après, la table logique de ces modules sera adaptée pour réaliser la comparaison de valeurs repliées. Les modules 10 sont mis à la chaîne par leurs sorties de retenue et leurs entrées de retenue Co et Ci, de manière à construire un comparateur de deux données de 64 bits. Les frontières entre les composantes des vecteurs sont définies à l'aide de portes ET 12, une porte 12 étant disposée entre chaque sortie de retenue Co d'un module et l'entrée de retenue Ci du module suivant. L'entrée de retenue du premier module reçoit 0 (pas de retenue à prendre en compte).
Chaque porte 12 est commandée par un signal S respectif (S0, S1 , S2...) dont l'état actif (1 ) détermine une frontière entre composantes. L'état actif du signal S bloque la porte 12, d'où il résulte que la retenue du module 10 n'est pas transmise au module suivant, et que le module suivant ne propage pas la comparaison - ce module suivant effectue une comparaison indépendante.
Un signal S inactif (0) rend passante la porte 12 et provoque le chaînage de deux modules 10 en permettant la propagation de retenue. Ces deux modules sont ainsi associés à une même composante. Dans la représentation de la figure 1 , si les quatre signaux S sont inactifs, les quatre modules 10 sont associés à une composante unique de 16 bits. Si les signaux S1 et S3 sont actifs, les modules sont associés à deux composantes distinctes de 8 bits. Si tous les signaux S sont actifs, chaque module est associé à une composante distincte de 4 bits. Par ailleurs, chaque signal S est appliqué sur une entrée inverseuse d'une porte OU 14, recevant sur une deuxième entrée la sortie GE du module 10 correspondant. Lorsque le signal S est inactif, la porte 14 ne propage pas la sortie GE du module - il s'agit d'un résultat de comparaison intermédiaire dont il ne faut pas tenir compte. Seul un module dont le signal S est actif voit sa sortie GE propagée par la porte 14 correspondante - cette sortie consolide le résultat de la comparaison établie par le module courant et des modules chaînés qui le précèdent (modules dont le signal S est inactif).
Les sorties des portes 14 arrivent sur une porte ET 16, dont la sortie est par conséquent active si les sorties GE de tous les modules 10 sont actives, c'est- à-dire si chaque composante du vecteur HA est supérieure ou égale à la composante correspondante du vecteur HB
![Figure imgf000020_0002](https://patentimages.storage.googleapis.com/8f/53/6e/269dec29e7ce3c/imgf000020_0002.png)
. (Les sorties des portes 14 bloquées par un signal S à 0 sont en fait à 1 , de sorte qu'elles n'influent pas sur les sorties des autres portes 14.)
Les sorties E inversées des modules 10 arrivent sur une porte OU 18. Ainsi, la sortie de la porte 18 devient active si au moins une des sorties E est inactive, c'est-à-dire si on a une inégalité pour au moins une paire de composantes des vecteurs HA et H B
Les sorties des portes 16 et 18 arrivent sur une porte ET 20. Ainsi, cette porte 20 fournit un signal actif (HA>HB) si toutes les composantes du vecteur HA sont supérieures ou égales à leurs composantes respectives du vecteur HB (porte 16 active), et qu'au moins deux composantes respectives des vecteurs HA et HB sont inégales (donc l'une strictement supérieure à l'autre). On obtient bien une comparaison de vecteurs selon une relation d'ordre partiel.
Il reste à définir comment les modules 10 comparent des composantes repliées. Les sorties de chaque module 10, dans le cadre de l'exemple où le module traite des données A et B de 4 bits, peuvent être définies comme suit : · Co = 1 si A + ~B + Ci > 15 (24 - 1 ). Cela correspond à la définition classique du bit de retenue dans un additionneur utilisé pour réaliser une comparaison.
• E = 1 si A = B.
• est la relation d'ordre « supérieur ou égal»
conforme à la définition précédemment donnée pour travailler sur des valeurs repliées modulo M (M = 16 ici).
Le tableau ci-dessous fournit, dans le cadre d'un exemple de repliement, les valeurs de la sortie GE en fonction de toutes les valeurs possibles de A et de B, indiquées en décimal.
Dans un comparateur classique, les valeurs situées sous la diagonale descendante, y compris les valeurs sur la diagonale, seraient toutes à 1 , et les valeurs situées au dessus de la diagonale seraient toutes à 0. Dans le comparateur utilisé ici, comme cela est indiqué en gras, le coin inférieur gauche, délimité entre (A, B) = (8, 0) et (15, 7), ne contient que des 0, et le coin supérieur droit, délimité entre (A, B) = (0, 9) et (6, 15), ne contient que des 1 . Exprimé autrement, chaque rangée comprend huit zéros consécutifs, partant de
la valeur à 1 de la diagonale, suivis de huit uns consécutifs, ces valeurs se succédant de manière circulaire.
Cet exemple correspond à S = 7 (8 - 1 ) dans la définition générale de la relation d'ordre partiel sur des valeurs repliées (où 2S < M). En diminuant la valeur de S, on diminue le nombre de zéros consécutifs dans les rangées, en complétant par des uns. Par exemple, en prenant S = 5, on aura 6 zéros consécutifs et 10 uns consécutifs dans chaque rangée.
Si n modules 10 sont chaînés pour correspondre à une composante de 4n bits, malgré que chaque module 10 travaille de manière indépendante sur 4 bits, et donc sur des valeurs bornées à 15, l'ensemble des modules chaînés, par transmission de retenues, travaille sur des valeurs de 4n bits, bornées à 24n - 1 .
Si le nombre de composantes des vecteurs est supérieur à la capacité du comparateur, il est néanmoins possible d'effectuer la comparaison en utilisant le comparateur en plusieurs cycles de la manière suivante, à l'aide de quelques éléments supplémentaires.
Lors d'un premier cycle, on compare un premier jeu de composantes. La sortie de la porte 20 est ignorée et les états des sorties des portes 16 et 18 sont stockés pour le cycle suivant, par exemple dans des bascules.
Au cycle suivant, on présente un nouveau jeu de composantes au comparateur. La porte OU 18 reçoit en entrée supplémentaire l'état
précédemment stocké pour sa sortie. Ainsi, si une inégalité a été détectée au cycle précédent, cette détection est imposée au cycle courant. Par ailleurs, une porte ET supplémentaire 22 est interposée entre les portes 16 et 20. La sortie de cette porte 22 n'est active que si la sortie de la porte 16 et l'état précédemment stocké de cette sortie sont tous deux actifs.
La sortie de la porte 20 sera prise en compte au bout d'un nombre suffisant de cycles pour traiter toutes les composantes à l'aide du comparateur.
Bien que dans la description qui précède on se réfère à un état 1 comme un état actif, et à un état 0 comme un état inactif, on comprendra que les natures de ces états peuvent être échangées en adaptant les circuits logiques les utilisant sans changer le résultat recherché.