Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
La ParallelHelper classe contiene API ad alte prestazioni da usare con codice parallelo. Contiene metodi orientati alle prestazioni che possono essere usati per configurare ed eseguire rapidamente operazioni parallele su un determinato set di dati o intervallo o area di iterazione.
API della piattaforma:
ParallelHelper,IAction,IAction2D,IRefAction<T>,IInAction<T>
Funzionamento
Il ParallelHelper tipo è basato su tre concetti principali:
- Esegue l'elaborazione automatica in batch sull'intervallo di iterazione di destinazione. Questa funzionalità pianifica automaticamente il numero corretto di unità di lavoro in base al numero di core CPU disponibili. Questa funzionalità riduce il sovraccarico di richiamo del callback parallelo una volta per ogni singola iterazione parallela.
- Sfrutta molto il modo in cui i tipi generici vengono implementati in C#. Usa tipi che implementano interfacce specifiche con
structinvece di delegati comeAction<T>. Usando questo approccio, il compilatore JIT può "vedere" ogni singolo tipo di callback usato, il che significa che può inline completamente il callback quando possibile. Questo approccio riduce notevolmente il sovraccarico di ogni iterazione parallela, soprattutto quando si usano callback molto piccoli. Il sovraccarico di invocazione del delegato sarebbe altrimenti trascurabile. Inoltre, usando unstructtipo come callback, è necessario gestire manualmente le variabili acquisite nella chiusura. Questo requisito impedisce acquisizioni accidentali delthispuntatore da metodi di istanza e altri valori che potrebbero rallentare notevolmente ogni chiamata di callback. Questo approccio è lo stesso usato in altre librerie orientate alle prestazioni,ImageSharpad esempio . - Espone quattro tipi di API che rappresentano quattro diversi tipi di iterazioni: cicli 1D e 2D, iterazione degli elementi con effetto collaterale e iterazione degli elementi senza effetti collaterali. Ogni tipo di azione ha un tipo corrispondente
interfaceda applicare allestructcallback che passate alleParallelHelperAPI. Queste interfacce sonoIAction,IAction2D,IRefAction<T>eIInAction<T>. Usando queste interfacce, è possibile scrivere codice che ne esprime chiaramente la finalità e le API possono eseguire ulteriori ottimizzazioni internamente.
Sintassi
Si supponga di essere interessati a elaborare tutti gli elementi in una float[] matrice e moltiplicarli per 2. In questo caso non è necessario acquisire alcuna variabile: è sufficiente usare IRefAction<T>interface e ParallelHelper caricherà ogni elemento per il feed al callback automaticamente. Tutto ciò che serve è definire callback, che riceverà un ref float parametro ed eseguirà l'operazione necessaria.
// Be sure to include this using at the top of the file:
using Microsoft.Toolkit.HighPerformance.Helpers;
// First declare the struct callback
public readonly struct ByTwoMultiplier : IRefAction<float>
{
public void Invoke(ref float x) => x *= 2;
}
// Create an array and run the callback
float[] array = new float[10000];
ParallelHelper.ForEach<float, ByTwoMultiplier>(array);
Con l'API ForEach non è necessario specificare gli intervalli di iterazione: ParallelHelper gestirà in batch la raccolta e elaborerà automaticamente ogni elemento di input. Inoltre, in questo esempio specifico non è stato necessario passare struct come argomento: poiché non contiene alcun campo da inizializzare, è possibile specificarne semplicemente il tipo come argomento di tipo quando si richiama ParallelHelper.ForEach: tale API creerà quindi una nuova istanza di che struct da sola e la userà per elaborare i vari elementi.
Per introdurre il concetto di chiusura, si supponga di voler moltiplicare gli elementi della matrice per un valore specificato in fase di esecuzione. A tale scopo, è necessario "catturare" tale valore nel tipo di callback struct. È possibile eseguire questa operazione in questo modo:
public readonly struct ItemsMultiplier : IRefAction<float>
{
private readonly float factor;
public ItemsMultiplier(float factor)
{
this.factor = factor;
}
public void Invoke(ref float x) => x *= this.factor;
}
// ...
ParallelHelper.ForEach(array, new ItemsMultiplier(3.14f));
È possibile notare che ora struct contiene un campo che rappresenta il fattore da usare per moltiplicare gli elementi, anziché usare una costante. Quando si richiama ForEach, stiamo creando in modo esplicito un'istanza del nostro tipo di callback, con il fattore a cui siamo interessati. Inoltre, in questo caso il compilatore C# è anche in grado di riconoscere automaticamente gli argomenti di tipo in uso, in modo da poterli omettere insieme dalla chiamata al metodo.
Questo approccio alla creazione di campi per i valori a cui è necessario accedere da un callback consente di dichiarare in modo esplicito i valori da acquisire, che consente di rendere il codice più espressivo. Questa è esattamente la stessa cosa che il compilatore C# esegue in background quando si dichiara una funzione lambda o una funzione locale che accede anche ad alcune variabili locali.
Ecco un altro esempio, questa volta che si usa l'API For per inizializzare tutti gli elementi di una matrice in parallelo. Si noti come questa volta stiamo acquisendo direttamente l'array di destinazione e stiamo usando IActioninterface per il nostro callback, che fornisce al metodo l'indice di iterazione parallela corrente come argomento:
public readonly struct ArrayInitializer : IAction
{
private readonly int[] array;
public ArrayInitializer(int[] array)
{
this.array = array;
}
public void Invoke(int i)
{
this.array[i] = i;
}
}
// ...
ParallelHelper.For(0, array.Length, new ArrayInitializer(array));
Nota
Poiché i tipi di callback sono struct-s, vengono passati per copia a ogni thread che vengono eseguiti in parallelo, non per riferimento. Ciò significa che anche i tipi valore archiviati come campi in un tipo di callback verranno copiati. È consigliabile ricordare che i dettagli ed evitare errori consiste nel contrassegnare il callback struct come readonly, in modo che il compilatore C# non consenta di modificare i valori dei relativi campi. Questo vale solo per i campi dell'istanza di un tipo valore: se un callback struct ha un static campo di qualsiasi tipo o un campo di riferimento, tale valore verrà condiviso correttamente tra thread paralleli.
Metodi
Queste sono le 4 API principali esposte da ParallelHelper, corrispondenti alle IAction, IAction2D, IRefAction<T> e IInAction<T> interfacce. Il ParallelHelper tipo espone anche un certo numero di overload per questi metodi, che offrono diversi modi per specificare gli intervalli di iterazione o il tipo di callback di input.
For e For2D lavorano su IAction e IAction2D istanze e sono concepiti per essere usati quando è necessario eseguire alcune operazioni parallele che non devono necessariamente mappare una raccolta sottostante accessibile direttamente con gli indici di ogni iterazione parallela. Gli ForEach overload invece agiscono su istanze di IRefAction<T> e IInAction<T> e possono essere usati quando le iterazioni parallele mappano direttamente agli elementi di una raccolta che possono essere indicizzati direttamente. In questo caso, astraggono anche la logica di indicizzazione, in modo che ogni chiamata parallela debba preoccuparsi solo dell'elemento di input su cui lavorare, e non di come recuperare quell'elemento.
Esempi
Altri esempi sono disponibili negli unit test.
.NET Community Toolkit