Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Innehåller ParallelHelper
API:er med höga prestanda för att fungera med parallell kod. Den innehåller prestandaorienterade metoder som kan användas för att snabbt konfigurera och köra parallella åtgärder över en viss datauppsättning eller iterationsintervall eller område.
Plattforms-API:er:
ParallelHelper
,IAction
,IAction2D
,IRefAction<T>
,IInAction<T>
Så här fungerar det
ParallelHelper
typ bygger på tre huvudkoncept:
- Den utför automatisk batchning över det målinriktade iterationsintervallet. Det innebär att det automatiskt schemalägger rätt antal arbetsenheter baserat på antalet tillgängliga CPU-kärnor. Detta görs för att minska kostnaderna för att anropa parallell återanrop en gång för varje parallell iteration.
- Det utnyttjar kraftigt hur generiska typer implementeras i C# och använder
struct
typer som implementerar specifika gränssnitt i stället för ombud somAction<T>
. Detta görs så att JIT-kompilatorn kan "se" varje enskild återanropstyp som används, vilket möjliggör att återanropet kan infogas helt när det är möjligt. Detta kan avsevärt minska kostnaderna för varje parallell iteration, särskilt när du använder mycket små återanrop, vilket skulle ha en trivial kostnad enbart för delegatanropet. Dessutom kräver användning av enstruct
-typ som återanrop att utvecklare manuellt hanterar variabler som fångas i slutningen, vilket förhindrar oavsiktliga fångster avthis
-pekaren från instansmetoder och andra värden som skulle kunna sakta ner varje återanrop betydligt. Det här är samma metod som används i andra prestandaorienterade bibliotek,ImageSharp
till exempel . - Den exponerar 4 typer av API:er som representerar 4 olika typer av iterationer: 1D- och 2D-loopar, objekt-iteration med bieffekt och objekt-iteration utan bieffekt. Varje typ av åtgärd har en motsvarande
interface
typ som måste tillämpas på återkallningarnastruct
som skickas till API:ernaParallelHelper
: dessa ärIAction
,IAction2D
,IRefAction<T>
ochIInAction<T><T>
. Detta hjälper utvecklare att skriva kod som är tydligare när det gäller dess avsikt och gör det möjligt för API:erna att utföra ytterligare optimeringar internt.
Syntax
Anta att vi är intresserade av att bearbeta alla objekt i en matris float[]
och att multiplicera var och en av dem 2
med . I det här fallet behöver vi inte samla in några variabler: vi kan bara använda IRefAction<T>
interface
och ParallelHelper
läser in varje objekt för att mata in till vårt återanrop automatiskt. Allt som behövs är att definiera återanropet, som tar emot ett ref float
argument och utför den nödvändiga åtgärden:
// 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);
Med API:et ForEach
behöver vi inte ange iterationsintervallen: ParallelHelper
batchar samlingen och bearbetar varje indataobjekt automatiskt. Dessutom behövde vi i det här specifika exemplet inte ens skicka vårt struct
argument: eftersom det inte innehöll några fält som vi behövde initiera, kunde vi bara ange dess typ som ett typargument när vi anropade ParallelHelper.ForEach
: det API:et skapar sedan en ny instans av det struct
på egen hand och använder den för att bearbeta de olika objekten.
För att introducera konceptet closures, föreställer vi oss att vi vill multiplicera arrayelementen med ett värde som anges vid körning. För att göra det måste vi "fånga" det värdet i vår återanropstyp struct
. Vi kan göra så här:
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));
Vi kan se att struct
nu innehåller ett fält som representerar den faktor som vi vill använda för att multiplicera element, i stället för att använda en konstant. Och när vi anropar ForEach
, skapar vi uttryckligen en instans av vår callback-typ, med den faktorn som vi är intresserade av. I det här fallet kan C#-kompilatorn dessutom automatiskt identifiera de typargument som vi använder, så att vi kan utelämna dem tillsammans från metodanropet.
Med den här metoden för att skapa fält för värden som vi behöver komma åt från ett återanrop kan vi uttryckligen deklarera vilka värden vi vill samla in, vilket gör koden mer uttrycksfull. Det här är exakt samma sak som C#-kompilatorn gör i bakgrunden när vi deklarerar en lambda-funktion eller lokal funktion som också har åtkomst till någon lokal variabel.
Här är ett annat exempel, den här gången använder API:et For
för att initiera alla objekt i en matris parallellt. Observera hur vi den här gången samlar in målmatrisen direkt, och vi använder för återanropet IAction
interface
, vilket ger vår metod det aktuella parallella iterationsindexet som argument:
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));
Anmärkning
Eftersom återuppringningstyperna är struct
-s, överförs de genom kopiering till varje tråd som körs parallellt, inte som referens. Det innebär att även värdetyper som lagras som fält i återkallningstyper kommer att kopieras. En bra idé att komma ihåg den informationen och undvika fel är att markera återanropet struct
som readonly
, så att C#-kompilatorn inte låter oss ändra värdena för dess fält. Detta gäller endast instansfält av en värdetyp: om ett återanrop struct
har ett static
fält av någon typ eller ett referensfält delas det värdet korrekt mellan parallella trådar.
Metoder
Det här är de 4 huvudsakliga API:erna som exponeras av ParallelHelper
, som motsvarar IAction
, IAction2D
IRefAction<T>
och IInAction<T>
-gränssnitten. Typen ParallelHelper
exponerar också ett antal överlagringar för dessa metoder, som erbjuder ett antal sätt att ange iterationsintervall eller typ av motringning av indata.
For
och For2D
arbeta med IAction
och IAction2D
instanser, och de är avsedda att användas när en del parallellt arbete behöver utföras som inte behöver mappas till en underliggande samling som kan nås direkt med indexen för varje parallell iteration. Överlagringarna ForEach
wotk i stället på IRefAction<T>
och IInAction<T>
instanser, och de kan användas när parallella iterationer mappas direkt till objekt i en samling som kan indexeras direkt. I det här fallet abstraherar de också bort indexeringslogiken, så att varje parallellt anrop bara behöver koncentrera sig på indataobjektet att arbeta med, och inte på hur de hämtar det objektet.
Exempel
.NET Community Toolkit