Parallel Programming – PLINQ #2

Aggiungiamo un altro tassello all’immenso mosaico della parallel programming con .NET 4.0: è possibile migliorare ulteriormente le prestazioni  dell’ultimo esempio presentato nella puntata precedente.

Esaminiamo l’esempio in questione :

var pings = from ip in addrs.AsParallel().WithDegreeOfParallelism(8)
                 select new Ping().Send(ip);

foreach (var ping in pings)

{
     Console.WriteLine("{0}: {1}", ping.Status, ping.Address);

}

L’operazione veniva eseguita in circa  7 secondi; proviamo a forzare un numero maggiore di thread :

var pings = from ip in addrs.AsParallel().WithDegreeOfParallelism(16)
                 select new Ping().Send(ip);

foreach (var ping in pings)

{
     Console.WriteLine("{0}: {1}", ping.Status, ping.Address);

}

nonostante i thread indicati per effettuare l’operazione siano praticamente raddoppiati, l’operazione viene eseguita sulla mia macchina dual core in circa 9 secondi risultando più lenta della precedente di due secondi.

[Cit.] La domanda nasce spontanea. Cosa sta accadendo?

Quando il .NET frame work esegue una query PLINQ il data source oggetto dell’elaborazione viene partizionato utilizzando i thread disponibili nel sistema, ovvero i dati contenuti nel data source di origine vengono segmentati su ogni singolo thread per elaborare in parallelo. Man mano che l’elaborazione delle singole partizioni viene completata, i risultati confluiscono nell’oggetto di ritorno  IEnumerable<T> disponibile solo dopo che tutte le elaborazioni sono terminate.

In definitiva il comportamento dell’operazione precedente è influenzato ed è influenzabile Smile dal ThreadPool. Per ogni processo il sistema rende disponibile un pool di thread. Ogni pool di thread, di default, ha una dimensione predefinita pari a 250 thread di lavoro per processore disponibile e a 1000 thread di completamento di I/O. Il pool di thread mantiene un numero minimo di thread inattivi. Per i thread di lavoro il valore predefinito di questo numero minimo corrisponde al numero di processori.Il metodo GetMinThreads ottiene il numero minimo di thread di lavoro inattivi e di thread di completamento di I/O. Quindi, in definitiva, sulla mia macchina dual core  i thread di lavoro inattivi disponibili sono solo due; possiamo verificare il tutto utilizzando il seguente segmento di codice attraverso il valore della variabile minWorker:

int minWorker, minIOC;

System.Threading.ThreadPool.GetMinThreads(out minWorker, out minIOC);

il valore varierà a seconda della disponibilità di processori sulla macchina su cui è eseguita l’istruzione.

il pool di thread non avvia immediatamente la creazione di nuovi thread ma, per evitare di allocare inutilmente spazio dello stack, crea nuovi thread inattivi a intervalli di circa mezzo secondo; per tanto, poiché l’esempio in questione produce picchi di attività e di fatto consuma i thread disponibili, il ritardo causato dalla creazione di nuovi thread inattivi produce un collo di bottiglia: ecco spiegato il perché dei due secondi in più Smile.

Vediamo come porre rimedio al problema settando un numero più alto di thread inattivi disponibili utilizzando il metodo SetMinThreads :

int minWorker, minIOC;

bool result = System.Threading.ThreadPool.SetMinThreads(64, minIOC);

var addrs = new[] { "10.1.0.2", "10.1.0.10", "10.1.0.5", "10.1.0.4", "10.1.0.3", "10.1.0.1", "10.1.0.8", "10.1.0.7", "10.1.0.9", "10.1.0.6" };

var pings = from ip in addrs.AsParallel().WithDegreeOfParallelism(16)                                          
                  select new Ping().Send(ip);

eseguendo il codice sulla mia macchina dual core, l’operazione effettua i 10 ping in meno di 3 secondi Big Smile non male, eravamo partiti da 25 secondi nella versione sequenziale.

Chiaramente l’aumento dei thread inattivi causerà l’aumento dello spazio allocato nello stack per ciascun thread, e questo potrebbe causare problemi di prestazioni nel momento in cui la macchina che esegue la nostra applicazione esegua altre applicazioni “affamate” di risorse macchina, rallentando tutti i processi in esecuzione.

Per chi volesse cimentarsi con gli esempi utilizzati in questo post e magari verificare le relative tempistiche può trovarli qui; attenzione: questo codice, se non eseguito con cautela, può nuocere gravemente alla salute del vostro pc. Smile

Published Sunday, June 13, 2010 8:49 PM by leo.alario

Comments

# Parallel Programming – PLINQ #3

Sunday, June 20, 2010 11:40 AM by .Net Café

Nelle precedenti puntate avevamo visto come elaborare in parallelo query PLINQ incrementando le prestazioni

Powered by Community Server (Commercial Edition), by Telligent Systems