Quelques exercices pour apprivoiser la multiprogrammation avec C#.
Voici quelques exercices que je vous recommande de faire. Prenez soin de tester chacune de vos réponses (faux partage, zone transit, pipeline, somme des impairs)
EX00 – Soit le programme suivant :
using System;
using System.Diagnostics;
// ...
const int N = 25_000;
var tab = CréerTableau(N * N);
var (r0, dt0) = Tester(() => CompterSi(tab, n => n % 2 != 0, N, N));
Console.WriteLine($"Compté {r0} impairs en {dt0} ms");
// ...
static short[] CréerTableau(int n)
{
short [] tab = new short[n];
for(int i = 0; i != tab.Length; ++i)
tab[i] = (short)(i * 2 + 1);
return tab;
}
static int CompterSi(short [] tab, Func<short, bool> pred, int hauteur, int largeur)
{
int n = 0;
for(int ligne = 0; ligne != hauteur; ++ligne)
for(int colonne = 0; colonne != largeur; ++colonne)
if(pred(tab[ligne * hauteur + colonne]))
++n;
return n;
}
static (T rés, long dt) Tester<T>(Func<T> f)
{
var sw = new Stopwatch();
sw.Start();
T rés = f();
sw.Stop();
return (rés, sw.ElapsedMilliseconds);
}
EX00.0 : notez le temps d'exécution de ce programme en Release, puis dans la fonction CompterSi, inversez l'ordre des deux boucles (passez de ligne, colonne à colonne, ligne) et examinez le résultat affiché. Que remarquez-vous?
EX00.1 : avec la plus rapide des deux versions de CompterSi, modifiez ce programme pour qu'il utilise deux fils d'exécution, puis quatre, puis huit. Assurez-vous qu'il affiche la bonne réponse à chaque fois. Votre code devient-il plus rapide quand on ajoute des fils d'exécution? Note : pour vous rendre la vie plus agréable, réécrivez CompterSi pour qu'elle ait la forme suivante :
using System;
using System.Diagnostics;
// ...
const int N = 25_000;
var tab = CréerTableau(N * N);
var (r0, dt0) = Tester(() => CompterSi(tab, n => n % 2 != 0, 0, tab.Length)); // début, fin
Console.WriteLine($"Compté {r0} impairs en {dt0} ms");
// ...
static short[] CréerTableau(int n)
{
short [] tab = new short[n];
for(int i = 0; i != tab.Length; ++i)
tab[i] = (short)(i * 2 + 1);
return tab;
}
static int CompterSi<T>(T [] tab, Func<T, bool> pred, int début, int fin) // début inclus, fin exclue
{
int n = 0;
for(int i = début; i != fin; ++i)
if(pred(tab[i]))
++n;
return n;
}
static (T rés, long dt) Tester<T>(Func<T> f)
{
var sw = new Stopwatch();
sw.Start();
T rés = f();
sw.Stop();
return (rés, sw.ElapsedMilliseconds);
}
EX01 – écrivez une classe ZoneTransit<T> contenant une List<T> et offrant deux services : Ajouter(List<T>) pour concaténer la liste en paramètre à la fin de la ZoneTransit<T>, et Extraire() retournant la List<T> dans la ZoneTransit<T> et faisant en sorte que la ZoneTransit<T> redevienne vide. Assurez-vous que les opérations Ajouter et Extraire soient synchronisées de manière à éviter que la ZoneTransit<T> ne soit corrompue. Note : vous n'avez pas le droit d'utiliser une ConcurrentQueue<T> ou une autre collection qui ferait le travail à votre place, ce serait de la triche!
EX01.0 : un collègue vous propose d'ajouter des services à votre classe, soit : AjouterUn(T) pour ajouter un seul élément à la fois à la fin de la ZoneTransit<T>; ExtraireUn() pour extraire un élément à la fois du début de la ZoneTransit<T>; une propriété EstVide et une propriété Count. Est-ce que votre collègue a une bonne idée? Expliquez votre réponse...
EX02 – à l'aide de la classe ZoneTransit<T> développée en réponse à EX01, écrivez un programme ayant les particularités suivantes :
Assurez-vous que le programme se termine normalement, que les fichiers résultants soient complets, et que les fils travaillent concurremment.
EX03 – sur la base de vos travaux en EX01 et EX02, écrivez un programme dans lequel on trouvera un pipeline transformant des fichiers .cs en fichiers .html. La forme attendue sera :
Assurez-vous que le programme se termine normalement, que les fichiers résultants soient complets, et que les fils travaillent concurremment.