Une image de Mandelbrot se calcule pixel par pixel, chaque pixel étant indépendant des autres : elle se découpe donc en bandes que l’on peut confier à des processus différents. Comment occuper plusieurs processus sans lancer tous les calculs à la fois ? Et si l’un d’eux s’arrête avant de répondre ? Ce projet prolonge les messages et monitors d’AP6 par un coordinateur : le processus qui distribue les bandes, garde explicitement la file des travaux et recueille les résultats, sans rien calculer lui-même. Les processus qui calculent sont les travailleurs (workers).
Téléchargez le squelette ferme-rendu.
Son README contient les contrats, les rappels Elixir, les étapes et les essais.
Le calcul des pixels, le dessin SVG, la commande et la version séquentielle
sont fournis ; vous construisez le module RenderFarm.Coordinator. Aucun
frontend n’est à développer, et le projet n’a aucune dépendance à télécharger.
Le schéma montre le cœur du sujet : avec une limite de deux calculs en vol, le coordinateur lance deux bandes ; la seconde répond la première, ce qui libère une place pour la troisième. L’ordre des réponses ne dit donc rien de la place des bandes dans l’image : c’est l’identifiant de chaque travail qui la donne.
Voir le résultat avant de répartir le travail
mix compile
mix test --only provided
mix render sequential 1 out/sequential.svg
Ouvrez l’image produite. Chaque bande a un identifiant et une position ; l’ordre des réponses ne doit pas décider de sa place dans l’image. Le test fourni, qui vérifie que les bandes couvrent chaque pixel une seule fois, passe dès le départ. Les cinq autres tests échouent avec « à compléter » tant que la coordination n’est pas écrite.
Le contrat à respecter est RenderFarm.run(jobs, work, limit) : jobs est
une liste de couples {id, payload}, work une fonction pure appliquée à
chaque payload, et le résultat rend, dans l’ordre initial, un {:ok, valeur}
ou un {:error, raison} par identifiant. Les tests emploient aussi de petits
calculs sans pixel : un échange de messages se comprend sur deux additions.
Construire le coordinateur
Vous écrirez la boucle qui lance au plus limit travailleurs, recueille
leurs résultats par identifiant et remplit une place libérée par un autre
travail. Les travailleurs calculent réellement les pixels ; le coordinateur
reste disponible pour traiter leurs messages. Contrairement à AP7, les
clients ne délèguent donc pas tous leur calcul à un unique GenServer.
Le README avance en deux temps. D’abord, recevoir sans confondre les travaux :
avec spawn, send et receive comme en AP6, deux travailleurs répondent au
coordinateur, qui range chaque résultat dans une map indexée par identifiant.
Une question avant d’écrire la boucle : qui connaît le PID auquel répondre,
et à quel moment faut-il le capturer ? Ensuite, limiter les calculs en cours :
l’état explicite du coordinateur contient la file des travaux restants, la
map des travailleurs actifs et la map des résultats obtenus.
mix test --only coordination
mix render parallel 4 out/parallel.svg
Le cas hors ordre est provoqué par des messages de libération, sans attendre
une durée arbitraire. Gardez deux invariants : au plus limit travaux actifs,
et un résultat final par identifiant. Une file vide n’est pas encore une
preuve de terminaison : des calculs peuvent toujours être en cours. Il faut
donc à la fois une file vide et aucun travailleur actif.
Réagir à une panne
Une panne, ici, est un travailleur qui s’arrête sans avoir envoyé de
résultat : avec spawn seul, le coordinateur l’attendrait indéfiniment.
spawn_monitor crée le processus en le surveillant, comme le monitor d’AP6 :
à sa fin, le coordinateur reçoit un message :DOWN. Transformez cette fin
sans réponse en résultat d’erreur, puis libérez sa place pour le travail
suivant. Le socle ne réessaie pas automatiquement les calculs et ne détecte
pas un travailleur qui reste vivant mais bloqué indéfiniment.
mix test --only monitoring
mix test
Comparez l’image séquentielle et l’image parallèle avant de comparer leurs temps. La parallélisation peut coûter plus cher pour une petite image ; le nombre de schedulers (les ordonnanceurs de la BEAM, en général un par cœur), le travail et la machine comptent. Une accélération n’est pas un critère de réussite.
La reprise limitée, les échéances, l’annulation ou une variante sous
Task.Supervisor sont des prolongements guidés. Le README précise les
messages supplémentaires et les limites à expliquer.
Ce projet noté débute pendant les trois heures d’AP9, puis se termine à la
maison. Le démarrage est accompagné ; périmètre, barème et échéance seront
précisés avant janvier, et les trois étapes ne sont pas un barème. Conservez
le code, vos tests, une image, une trace commentée de trois travaux dont un
échec, et COMPTE-RENDU.md, qui justifie la borne de concurrence et le
critère de terminaison. Pour toute question, vous pouvez me contacter par
email.
Pour les curieux : le fascicule
Un petit fascicule d’approfondissement accompagne ce sujet : les idées derrière le projet, leur histoire, ce qu’elles deviennent dans les systèmes réels et quoi lire ensuite. C’est un cadeau de fin de cours ; rien n’y est à rendre.
