2010-10-27 19 views

risposta

26

La classe Partitioner è usato per fare le esecuzioni parallele più grosso. Se hai un sacco di compiti molto piccoli da eseguire in parallelo, il sovraccarico di invocare delegati per ciascuno può essere proibitivo. Utilizzando Partitioner, è possibile riorganizzare il carico di lavoro in blocchi e fare in modo che ogni chiamata parallela funzioni su un set leggermente più grande. La classe astrae questa funzione ed è in grado di partizionare in base alle condizioni effettive del set di dati e dei core disponibili.

Esempio: immagina di voler eseguire un semplice calcolo come questo in parallelo.

Parallel.ForEach(Input, (value, loopState, index) => { Result[index] = value*Math.PI; }); 

Questo invocherà il delegato per ogni voce in Input. Fare così aggiungerebbe un po 'di spese generali a ciascuno. Utilizzando Partitioner possiamo fare qualcosa di simile

Parallel.ForEach(Partitioner.Create(0, Input.Length), range => { 
    for (var index = range.Item1; index < range.Item2; index++) { 
     Result[index] = Input[index]*Math.PI; 
    } 
}); 

Questo ridurrà il numero di invoca come ogni invoke lavorerà su un insieme più ampio. Nella mia esperienza, ciò può aumentare significativamente le prestazioni quando si parallelizzano operazioni molto semplici.

+8

Il rangeSize predefinito è uno per Partitioner.Create(). Pertanto, la partizione è la stessa per entrambi gli esempi di codice. A meno di Partitioner.Create (0, Input.Length, i); dove i> 1, avrà ancora lo stesso numero di thread. – Pingpong

2

Per parallelizzare un'operazione su un'origine dati, uno dei passaggi essenziali consiste nel suddividere l'origine in più sezioni a cui è possibile accedere contemporaneamente da più thread. PLINQ e Task Parallel Library (TPL) forniscono partizionatori predefiniti che funzionano in modo trasparente quando si scrive una query parallela o un ciclo ForEach. Per scenari più avanzati, è possibile collegare il proprio partizionatore.

Per saperne di più here:

+2

mi piacerebbe aggiungere a questo: In generale * voi * non lo uso. PLINQ lo fa, e probabilmente farai franca con i partizionatori di default. –

1

La partizione di intervallo, come suggerito da Brian Rasmussen, è un tipo di partizionamento che deve essere utilizzato quando il lavoro è impegnativo per la CPU, tende ad essere piccolo (rispetto a una chiamata al metodo virtuale), molti elementi devono essere elaborati ed è per lo più costante quando si tratta di run time per elemento.

L'altro tipo di partizione che deve essere considerato è il partizionamento del blocco. Questo tipo di partizionamento è anche noto come algoritmo di bilanciamento del carico perché un thread di lavoro raramente rimane inattivo mentre c'è più lavoro da fare, cosa che non è il caso di una partizione di intervallo.

Una partizione di blocco deve essere utilizzata quando il lavoro presenta alcuni stati di attesa, tende a richiedere più elaborazione per elemento o ogni elemento può avere tempi di elaborazione del lavoro significativamente differenti.

Un esempio di questo potrebbe essere la lettura in memoria e l'elaborazione di 100 file con dimensioni molto diverse. Un file 1K verrà elaborato in un tempo molto inferiore rispetto a un file 1mb. Se per questo viene utilizzata una partizione di intervallo, alcuni thread potrebbero rimanere inattivi per un po 'di tempo poiché si verificavano file più piccoli.

A differenza di una partizione di intervallo, non è possibile specificare il numero di elementi da elaborare da ciascuna attività, a meno che non si scriva il proprio partizionatore personalizzato. Un altro svantaggio nell'usare una partizione di blocchi è che potrebbe esserci qualche contesa quando torna a prendere un altro blocco dal momento che un blocco esclusivo viene utilizzato in quel punto. Quindi, chiaramente una partizione di blocchi non dovrebbe essere usata per una piccola quantità di lavoro intensivo della CPU.

Il partizionatore del blocco predefinito inizia con una dimensione del blocco di 1 elemento per blocco. Dopo ogni thread elabora tre blocchi di 1 elemento, la dimensione del blocco viene incrementata a 2 elementi per blocco.Dopo che tre blocchi di 2 elementi sono stati elaborati da ciascun thread, la dimensione del blocco viene incrementata nuovamente a 3 elementi per blocco e così via. Almeno questo è il modo in cui funziona secondo Dixin Yan, (vedi la sezione di partizionamento di Chunk) che lavora per Microsoft.

A proposito, il simpatico strumento di visualizzazione nel suo blog sembra essere il Concurrency Visualizer profile tool. Lo docs for this tool afferma che può essere utilizzato per individuare colli di bottiglia delle prestazioni, sottoutilizzo della CPU, conflitto di thread, migrazione di thread tra core, ritardi di sincronizzazione, attività di DirectX, aree di I/O sovrapposte e altre informazioni. Fornisce visualizzazioni di dati grafici, tabulari e testuali che mostrano le relazioni tra i thread in un'app e il sistema nel suo complesso.

Altre risorse:

MSDN: Custom Partitioners for PLINQ and TPL

Part 5: Parallel Programming - Optimizing PLINQ di Joseph Albahari

Problemi correlati