[C#] Parallel for dotNET 2.0
Publié : 29 Mai 2013 17:48
Vous connaissez la classe Parallel ? Non ?
C'est une classe qui permet de paralléliser des boucles très simplement (for et foreach), et donc de booster assez efficacement certaines tâches. Petit hic... c'est minimum du dotNET 4 ! (Unity, c'est 2 max).
Cette classe est donc un portage assez "basique" en dotNET 2 (avec des perfs parfois supérieures à celles de la classe originale ), elle peut bien sûr être utilisée hors de Unity.
Attention : cette classe ne va pas faire des miracles et c'est pas un prétexte pour ne pas optimiser vos algos (le mieux est je pense d'optimiser en premier, puis de multithreader si besoin).
Vous ne pouvez pas utiliser l'API d'Unity en dehors du main thread (à part quelques trucs du style Mathf et Vector).
Un minimum de connaissance est requis sur le multithreading (accès concurrent aux objets par exemple), sur les delegates (avec Action et Func), et c'est encore mieux si vous connaissez les lambdas.
- NOM DU SCRIPT : Parallel
- AUTEUR(S): artemisart + code basé sur http://coding-time.blogspot.fr/2008/03/ ... -in-c.html
- DESCRIPTION : .NET 4 Parallel like
- UTILISATION : 4 méthodes (en comptant les surcharges )
public static void Parallel.Invoke (params Action[] actions)
-> permet d'appeler plusieurs méthodes qui seront dispatchées sur plusieurs threads
public static void Parallel.For (int fromInclusive, int toExclusive, Action<int> action, int chunkSize, int threadCount)
-> exécute une boucle for par "groupes" de [chunkSize] sur [threadCount] threads (commençant à l'index [fromInclusive] et terminant à [toExclusive].
public static void Parallel.For (int fromInclusive, int toExclusive, Action<int> action)
-> exécute une boucle for par "groupes" de 4 sur [Environment.ProcessorCount] threads.
public static void Parallel.ForAutoChunkSize (int fromInclusive, int toExclusive, Action<int> action)
-> exécute une boucle for en la divisant en [Environment.ProcessorCount] groupes (presque) égaux, chaque groupe étant exécuté sur un thread différent.
Je sais pas si c'est très clair , un exemple :
Nous avons le code suivant, non multithreadé :
Le résultat est : "Hello World !" avec un retour à la ligne.
Maintenant, avec un Parallel.For :
Et le résultat... "Hellrld !o Wo" et oui ! l’exécution ne se fait pas dans l'ordre, car plusieurs threads écrivent en même temps dans la console. C'est le principe du multithreading, il ne faut cependant pas l'oublier.
Action<int> représente donc une méthode qui prend un int en paramètre (l'index, égale à "i" dans une boucle for classique) et ne retourne rien.
chunkSize est le nombre d'items qui seront exécutés en même temps (en gros, le tout est séparé en groupes de taille [chunkSize], et chaque thread exécute des groupes 1 à 1 tant qu'il en reste).
threadCount est le nombre de threads qui seront lancés (ça sert pas à grand chose de monter au dessus de Environment.ProcessorCount).
La méthode Invoke est très simple à utiliser, ex :
Résultat (variable) : "!\nHello World ".
Toutes ces méthodes sont bloquantes (comme la classe originale), cad qu'elles ne se termineront pas tant avant que tous les threads aient terminés (si vous voulez simplement lancer une méthode asynchrone, regardez du côté de la classe Thread, BeginInvoke, ou ThreadPool.QueueUserWorkItem).
- SCRIPT :
C'est une classe qui permet de paralléliser des boucles très simplement (for et foreach), et donc de booster assez efficacement certaines tâches. Petit hic... c'est minimum du dotNET 4 ! (Unity, c'est 2 max).
Cette classe est donc un portage assez "basique" en dotNET 2 (avec des perfs parfois supérieures à celles de la classe originale ), elle peut bien sûr être utilisée hors de Unity.
Attention : cette classe ne va pas faire des miracles et c'est pas un prétexte pour ne pas optimiser vos algos (le mieux est je pense d'optimiser en premier, puis de multithreader si besoin).
Vous ne pouvez pas utiliser l'API d'Unity en dehors du main thread (à part quelques trucs du style Mathf et Vector).
Un minimum de connaissance est requis sur le multithreading (accès concurrent aux objets par exemple), sur les delegates (avec Action et Func), et c'est encore mieux si vous connaissez les lambdas.
- NOM DU SCRIPT : Parallel
- AUTEUR(S): artemisart + code basé sur http://coding-time.blogspot.fr/2008/03/ ... -in-c.html
- DESCRIPTION : .NET 4 Parallel like
- UTILISATION : 4 méthodes (en comptant les surcharges )
public static void Parallel.Invoke (params Action[] actions)
-> permet d'appeler plusieurs méthodes qui seront dispatchées sur plusieurs threads
public static void Parallel.For (int fromInclusive, int toExclusive, Action<int> action, int chunkSize, int threadCount)
-> exécute une boucle for par "groupes" de [chunkSize] sur [threadCount] threads (commençant à l'index [fromInclusive] et terminant à [toExclusive].
public static void Parallel.For (int fromInclusive, int toExclusive, Action<int> action)
-> exécute une boucle for par "groupes" de 4 sur [Environment.ProcessorCount] threads.
public static void Parallel.ForAutoChunkSize (int fromInclusive, int toExclusive, Action<int> action)
-> exécute une boucle for en la divisant en [Environment.ProcessorCount] groupes (presque) égaux, chaque groupe étant exécuté sur un thread différent.
Je sais pas si c'est très clair , un exemple :
Nous avons le code suivant, non multithreadé :
Code : Tout sélectionner
for (int i = 0; i < "Hello World !\n".Length; i++)
Console.Write ("Hello World !\n"[i]);
Maintenant, avec un Parallel.For :
Code : Tout sélectionner
Parallel.For (
0,
"Hello World !\n".Length,
(i) => Console.Write ("Hello World !\n"[i])
);
// si vous aimez pas les lambdas :
Parallel.For (
0,
"Hello World !\n".Length,
Write
);
// avec la fonction Write étant :
public static void Write (int index)
{
Console.Write ("Hello World !\n"[index]);
}
Action<int> représente donc une méthode qui prend un int en paramètre (l'index, égale à "i" dans une boucle for classique) et ne retourne rien.
chunkSize est le nombre d'items qui seront exécutés en même temps (en gros, le tout est séparé en groupes de taille [chunkSize], et chaque thread exécute des groupes 1 à 1 tant qu'il en reste).
threadCount est le nombre de threads qui seront lancés (ça sert pas à grand chose de monter au dessus de Environment.ProcessorCount).
La méthode Invoke est très simple à utiliser, ex :
Code : Tout sélectionner
Parallel.Invoke (
() => Console.Write ("Hello "),
() => Console.Write ("World "),
() => Console.Write ("!\n")
);
Toutes ces méthodes sont bloquantes (comme la classe originale), cad qu'elles ne se termineront pas tant avant que tous les threads aient terminés (si vous voulez simplement lancer une méthode asynchrone, regardez du côté de la classe Thread, BeginInvoke, ou ThreadPool.QueueUserWorkItem).
- SCRIPT :
Code : Tout sélectionner
using System;
using System.Collections.Generic;
using System.Threading;
// code : see http://coding-time.blogspot.fr/2008/03/implement-your-own-parallelfor-in-c.html
public static class Parallel
{
public static void ForAutoChunkSize (int fromInclusive, int toExclusive, Action<int> action)
{
For (
fromInclusive,
toExclusive,
action,
(toExclusive - fromInclusive) / Environment.ProcessorCount + 1,
Environment.ProcessorCount
);
}
public static void For (int fromInclusive, int toExclusive, Action<int> action)
{
For (fromInclusive, toExclusive, action, 4, Environment.ProcessorCount);
}
public static void For (int fromInclusive, int toExclusive, Action<int> action, int chunkSize, int threadCount)
{
if (chunkSize < 1)
chunkSize = 1;
if (threadCount < 1)
threadCount = 1;
int index = fromInclusive - chunkSize;
var locker = new object ();
Action process = () =>
{
int chunkStart;
while (true)
{
chunkStart = 0;
lock (locker)
chunkStart = index += chunkSize;
for (int i = chunkStart; i < chunkStart + chunkSize; i++)
{
if (i >= toExclusive)
return;
action (i);
}
}
};
IAsyncResult[] results = new IAsyncResult[threadCount];
for (int i = 0; i < threadCount; ++i)
results[i] = process.BeginInvoke (null, null);
// wait all
for (int i = 0; i < threadCount; ++i)
process.EndInvoke (results[i]);
}
public static void Invoke (params Action[] actions)
{
IAsyncResult[] results = new IAsyncResult[actions.Length];
for (int i = 0; i < actions.Length; i++)
results[i] = actions[i].BeginInvoke (null, null);
for (int i = 0; i < actions.Length; i++)
actions[i].EndInvoke (results[i]);
}
}