共用方式為


ParallelHelper

ParallelHelper包含高效能 API,可搭配平行程式代碼使用。 它包含效能導向方法,可用來快速設定和執行指定數據集或反覆專案範圍或區域的平行作業。

平臺 API:ParallelHelper、、IActionIAction2DIRefAction<T>、、IInAction<T>

運作方式

ParallelHelper 類型是以三個主要概念為基礎所建置:

  • 它會在目標反覆專案範圍上執行自動批處理。 這表示它會根據可用的CPU核心數目自動排程正確的工作單位數目。 這樣做是為了減少針對每個單一平行反覆運算叫用平行回呼一次的額外負荷。
  • 它會大量運用在 C# 中實作泛型型別的方式,並使用 struct 實作特定介面的類型,而不是像 這樣的 Action<T>委派。 如此一來,JIT 編譯程式就能夠「查看」使用的每個個別回呼類型,如此一來,就能夠盡可能完全內嵌回呼。 這可以大幅降低每個平行反覆項目的額外負荷,特別是使用非常小型的回呼時,單獨使用委派調用會有一個微不足道的成本。 此外,使用 struct 型別做為回呼,需要開發人員手動處理在關閉時擷取的變數,這可防止意外從實例方法擷取 this 指標,以及其他可能會讓每個回呼調用變慢速度的值。 這是其他效能導向連結庫中所使用的相同方法,例如 ImageSharp
  • 它會公開 4 種類型的 API,這些 API 代表 4 種不同類型的反覆專案:1D 和 2D 迴圈、具有副作用的專案反覆專案,以及專案反覆專案而沒有副作用。 每個動作類型都有對應的interface類型,必須套用至struct傳遞至 API 的ParallelHelper回呼:這些是IActionIAction2DIRefAction<T>IInAction<T><T>。 這可協助開發人員撰寫更清楚其意圖的程序代碼,並允許 API 在內部執行進一步優化。

語法

假設我們有興趣處理一些 float[] 陣列中的所有專案,並將每個專案乘以 2。 在此情況下,我們不需要擷取任何變數:我們只能使用 IRefAction<T>interface ,並將 ParallelHelper 載入每個專案以自動饋送至回呼。 只需要定義我們的回呼,該回呼將接收 ref float 自變數並執行必要的作業:

// 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);

ForEach使用 API 時,我們不需要指定反覆專案範圍:ParallelHelper會自動批處理集合和處理每個輸入專案。 此外,在此特定範例中,我們甚至不需要傳遞做struct為自變數:因為它未包含任何需要初始化的ParallelHelper.ForEach欄位,因此我們可以在叫struct用 時將其類型指定為類型自變數:該 API 接著會自行建立新的實例,並使用該實例來處理各種專案。

若要介紹關閉的概念,假設我們想要將陣列元素乘以運行時間指定的值。 若要這樣做,我們需要在回呼 struct 類型中「擷取」該值。 我們可以這麼做:

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));

我們可以看到, struct 現在包含一個字段,代表我們想要用來相乘元素的因數,而不是使用常數。 而叫 ForEach用 時,我們會明確地建立回呼類型的實例,並包含我們感興趣的因素。 此外,在此情況下,C# 編譯程式也能夠自動辨識我們所使用的類型自變數,因此我們可以一起從方法調用中省略它們。

建立需要從回呼存取之值字段的這個方法可讓我們明確宣告我們想要擷取的值,這有助於讓程式代碼更具表達性。 當我們宣告存取某些局部變數的 Lambda 函式或本機函式時,C# 編譯程式在幕後執行的動作完全相同。

以下是另一個範例,這次使用 For API 平行初始化陣列的所有專案。 請注意這次我們直接擷取目標陣列的方式,而我們使用 IActioninterface 作為回呼的,這會提供方法目前的平行反覆運算索引作為自變數:

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));

注意

由於回呼類型為 struct-s,所以 會藉由複製 傳遞至執行平行的每個線程,而不是以傳址方式傳遞。 這表示也會複製將值型別儲存為回呼類型的欄位。 若要記住詳細數據並避免錯誤,最好是將回 struct 呼標示為 readonly,因此 C# 編譯程式不會讓我們修改其字段的值。 這隻適用於 實值類型的實例 欄位:如果回呼 struct 具有 static 任何類型的欄位或參考欄位,則該值會在平行線程之間正確共用。

方法

這些是 所ParallelHelper公開的 4 個主要 API,對應至 IActionIAction2DIRefAction<T>IInAction<T> 介面。 此 ParallelHelper 類型也會公開這些方法的數個多載,提供許多方法來指定反覆專案範圍或輸入回呼的類型。 For和處理 For2DIActionIAction2D 實例,而且當某些平行工作需要完成不需要對應至可以使用每個平行反覆專案索引直接存取的基礎集合時,就會使用這些工作。 多ForEach載會改為在 和 IRefAction<T> 實例上 IInAction<T> wotk,而且當平行反覆專案直接對應至可以直接編製索引之集合中的專案時,就可以使用這些多載。 在此情況下,它們也會將索引編製邏輯抽象化,讓每個平行調用只需要擔心輸入專案才能運作,而不是如何擷取該專案。

範例

您可以在單元測試中找到更多範例。