共用方式為


在 PLINQ 中的性能加速

本文提供的資訊可協助您撰寫盡可能有效率的 PLINQ 查詢,同時仍會產生正確的結果。

PLINQ 的主要目的是在多核心計算機上平行執行查詢委派,以加速 LINQ to Objects 查詢的執行。 PLINQ 在來源集合中處理每個元素時表現最佳,且個別委派之間沒有涉及任何共享狀態。 這類作業在 LINQ to Objects 和 PLINQ 中很常見,通常稱為「令人愉快的平行」,因為它們可讓您輕鬆地在多個線程上排程。 不過,並非所有查詢都完全由令人愉快的平行作業所組成。 在大部分情況下,查詢牽涉到某些無法平行處理的運算符,或是讓平行執行變慢。 即使使用完全令人愉快的平行查詢,PLINQ 仍必須分割數據源,並在線程上排程工作,而且通常會在查詢完成時合併結果。 所有這些作業都會新增至平行處理的計算成本;新增平行處理的成本稱為 額外負荷。 若要在 PLINQ 查詢中達到最佳效能,目標是將令人愉快的平行元件最大化,並將需要額外負荷的元件降到最低。

影響 PLINQ 查詢效能的因素

下列各節列出影響平行查詢效能的一些最重要的因素。 這些是一般語句,它們本身並不足以在所有情況下預測查詢效能。 一如往常,請務必在具有一系列代表性組態和負載的計算機上測量特定查詢的實際效能。

  1. 整體工作的計算成本。

    若要達到加速,PLINQ 查詢必須有足夠的令人愉快的平行工作來抵消額外負荷。 運算工作量可以表示為每個委派所需的計算資源消耗,再乘以來源集合中的元素數量。 假設作業可以平行處理,計算成本愈高,加速的機會就越大。 例如,如果函式執行需要一毫秒,則超過1000個元素的循序查詢需要一秒才能執行該作業,而具有四個核心之電腦上的平行查詢可能只需要250毫秒。 這會加快 750 毫秒。 如果函式需要針對每個元素執行一秒,則加速會是 750 秒。 如果委派操作非常昂貴,則 PLINQ 可能會透過來源集合中的少數項目提供顯著的加速。 相反地,具有簡單委派的小型來源集合通常不是 PLINQ 的好候選專案。

    在下列範例中,queryA 可能是 PLINQ 的好候選專案,假設其 Select 函式牽涉到很多工作。 queryB 可能不是一個好的候選者,因為 Select 語句中沒有足夠的工作,且平行處理的額外負荷會抵消大部分或全部的速度提升。

    Dim queryA = From num In numberList.AsParallel()  
                 Select ExpensiveFunction(num); 'good for PLINQ  
    
    Dim queryB = From num In numberList.AsParallel()  
                 Where num Mod 2 > 0  
                 Select num; 'not as good for PLINQ  
    
    var queryA = from num in numberList.AsParallel()  
                 select ExpensiveFunction(num); //good for PLINQ  
    
    var queryB = from num in numberList.AsParallel()  
                 where num % 2 > 0  
                 select num; //not as good for PLINQ  
    
  2. 系統上的邏輯核心數目(平行處理原則程度)。

    這一點是上一節的明顯推論,因為工作可以分割成更多並行線程,因此在具有更多核心的計算機上執行速度較快的查詢。 速度提升的總量取決於查詢整個工作的百分比中可被平行處理的部分。 不過,請勿假設所有查詢在八核心計算機上執行的速度會比四核心計算機快兩倍。 調整查詢以獲得最佳效能時,請務必在具有各種核心數目的計算機上測量實際結果。 此點與點 #1 相關:需要較大的數據集,才能利用更大的運算資源。

  3. 作業的數目和種類。

    PLINQ 提供 AsOrdered 運算元,用於在需要維持來源序列中元素順序的情況下。 有與訂購相關聯的成本,但此成本通常很適中。 GroupBy 和 Join 作業同樣會產生額外負荷。 PLINQ 在允許以任何順序處理來源集合中的元素時,會執行最佳效能,並在準備好後立即將它們傳遞至下一個運算符。 如需詳細資訊,請參閱 PLINQ 中的訂單保留

  4. 查詢執行的形式。

    如果您要藉由呼叫 ToArray 或 ToList 來儲存查詢的結果,則所有平行線程的結果都必須合併到單一數據結構中。 這牽涉到不可避免的計算成本。 同樣地,如果您使用 foreach(在 Visual Basic 中的 For Each)迴圈反覆循環檢視結果,工作線程的結果必須序列化至列舉器線程。 但是,如果您只想根據每個線程的結果執行一些動作,您可以使用 ForAll 方法在多個線程上執行這項工作。

  5. 合併選項的類型。

    PLINQ 可以被配置為緩衝其輸出,並在整個結果集產生後以區塊方式或一次性地輸出,或者在個別結果產生時即時串流傳輸。 前者會導致整體運行時間降低,後者會導致產生元素之間的延遲降低。 雖然合併選項不一定會對整體查詢效能產生重大影響,但它們可能會影響感知到的效能,因為它們會控制用戶必須等候多久才能看到結果。 如需詳細資訊,請參閱 PLINQ 中的合併選項

  6. 分割的種類。

    在某些情況下,可編製索引來源集合的 PLINQ 查詢可能會導致工作負載不平衡。 發生這種情況時,您可以藉由建立自定義數據分割器來增加查詢效能。 如需詳細資訊,請參閱 PLINQ 和 TPL 的自定義分割器

當 PLINQ 選擇循序模式時

PLINQ 一律會嘗試執行查詢,至少和查詢順序一樣快。 雖然 PLINQ 不會查看使用者委派的計算成本,或輸入來源有多大,但它確實會尋找特定的查詢「圖形」。具體而言,它會尋找查詢運算符或運算子的組合,這些運算元通常會導致查詢在平行模式中執行速度較慢。 當找到這類圖形時,PLINQ 預設會回復為循序模式。

不過,測量特定查詢的效能之後,您可能會判斷它實際上以平行模式執行得更快。 在這種情況下,您可以透過 ParallelExecutionMode.ForceParallelism 方法來使用 WithExecutionMode 旗標,指示 PLINQ 平行處理查詢。 如需詳細資訊,請參閱 如何:在 PLINQ 中指定執行模式

下列清單描述 PLINQ 預設會以循序模式執行的查詢圖形:

  • 在已移除或重新排列原始索引的排序或篩選運算符之後,包含 Select、indexed Where、indexed SelectMany 或 ElementAt 子句的查詢。

  • 包含Take、TakeWhile、Skip、SkipWhile 運算元以及來源序列中索引不是原始順序的查詢。

  • 包含 Zip 或 SequenceEquals 的查詢,除非其中一個數據源具有原始排序的索引,而另一個數據源是可編製索引的(亦即數組或 IList(T))。

  • 包含 Concat 的查詢,除非它應用於能索引的數據源。

  • 包含 Reverse 的查詢,除非套用至可編製索引的數據源。

另請參閱