close
文章出處

  在多核CPU在今天和不久的將來,計算機將擁有更多的內核,Microsoft為了利用這個硬件特性,于是在Visual Studio 2010 和 .NET Framework 4的發布及以上版本中,添加了并行編程這個新特性,我想它以后勢必會改變我們的開發方式。

  在以前或者說現在,我們在并行開發的時候可能會想到利用多線程和鎖技術來做,充分利用多CPU的特性,但是當我們了解并掌握了并行編程技術之后,我們可以不必擔心在多線程中的資源死鎖和繁瑣的DEBUG查找等低級操作。新的并行編程簡單了并行開發,使我們可以用新的方法來編程并行運算的代碼,從而不必直接去處理線程或者線程池。下圖從較高層面上概述了 .NET Framework 4 中的并行編程體系結構。

 

  園子里已經有很多關于LINQ的博客(如一線碼農的《8天玩轉并行開發》系列,非常感謝這些分享的人。:)),本來是不想再寫的,但是想了一下還是寫一下吧,算是作為自己的學習筆記了。

  下面為并行編程所用到的數據,摘自MSDN。

技術

說明

任務并行庫 (TPL)

提供針對 System.Threading.Tasks.Parallel 類的文檔(包括 ForForEach 循環的并行版本),還提供了針對 System.Threading.Tasks.Task 類的文檔(描繪了表示異步操作的首選方式)。

并行 LINQ (PLINQ)

LINQ to Objects 的并行實現,該實現顯著提高了許多情況下的性能。

用于并行編程的數據結構

提供一些鏈接,這些鏈接指向有關線程安全集合類、輕量同步類型以及延遲初始化類型的文檔。

并行診斷工具

提供一些鏈接,這些鏈接指向有關 Visual Studio 任務和并行堆棧調試器窗口以及并發可視化工具的文檔,其中包含 Visual Studio Application Lifecycle Management 探查器中的一組視圖,您可以使用這些視圖來調試和調整并行代碼的性能。

PLINQ 和 TPL 的自定義分區程序

描述分區程序的工作方式,以及如何配置默認分區程序或創建新的分區程序。

任務工廠

描述 System.Threading.Tasks.TaskFactory 類的作用。

任務計劃程序

描述計劃程序的工作方式,以及如何配置默認計劃程序。

在 PLINQ 和 TPL 中的 Lambda 表達式

簡要概述 C# 和 Visual Basic 中的 lambda 表達式,并演示如何在 PLINQ 和任務并行庫中使用這些表達式。

其他閱讀材料(并行編程)

提供一些鏈接,這些鏈接指向其他文檔以及在 .NET Framework 中進行并行編程的示例資源。

.NET Framework 高級讀物

高級主題(例如線程處理和并行編程)的頂級節點。

 

.Net framework并行編程相關為我們提供了一個新的命名空間:System.Threading.Tasks。

 說明
公共類 Parallel 提供對并行循環和區域的支持。
公共類 ParallelLoopState 可用來使 Parallel 循環的迭代與其他迭代交互。此類的實例由 Parallel 類提供給每個循環;不能在您的用戶代碼中創建實例。
公共類 ParallelOptions 存儲用于配置 Parallel 類的方法的操作的選項。
公共類 Task 表示一個異步操作。
公共類 Task<TResult> 表示一個可以返回值的異步操作。
公共類 TaskCanceledException 表示一個用于告知任務取消的異常。
公共類 TaskCompletionSource<TResult> 表示未綁定到委托的 Task<TResult> 的制造者方,并通過 Task 屬性提供對使用者方的訪問。
公共類 TaskExtensions 提供一組用于處理特定類型的 Task 實例的靜態方法(在 Visual Basic 中為共享方法)。
公共類 TaskFactory 提供對創建和計劃 Task 對象的支持。
公共類 TaskFactory<TResult> 提供對創建和計劃 Task<TResult> 對象的支持。
公共類 TaskScheduler 表示一個處理將任務排隊到線程中的低級工作的對象。
公共類 TaskSchedulerException 表示一個用于告知由 TaskScheduler 計劃的某個操作無效的異常。
公共類 UnobservedTaskExceptionEventArgs 為在出錯的 Task 的異常未觀察到時引發的事件提供數據。

 

首先介紹一下并行編程為我們提供的拓展方法

System.Linq.ParallelEnumerable 類為LINQ所能查詢的集合IEnumerable提供了拓展方法,讓我們更加容易的使用并行查詢。如下:

static void Main(string[] args)
        {
            Random random = new Random();

            List<int> intList = new List<int>();
            for (int i = 0; i < 100; i++)
            {
                intList.Add(random.Next(100));
            }
            //查詢大于50的數
            var queryResult = from varable in intList.AsParallel()
                              where varable > 50
                              select varable;
            queryResult.ForAll<int>(tmp => Console.WriteLine(tmp));
        }

默認PLINQ使用主機上的所有處理器,若要指定并行處理的最大任務數,可以用拓展方法WithDegreeOfParallelism(int degreeOfParallelism),其中degreeOfParallelism為設置的最大任務數量。degreeOfParallelism默認為CPU的核心數,如果CPU多余64核,而沒有手動指定,則為64。

  有必要提一下上面用到的ForAll運算符,在普通的LINQ查詢中,執行被延遲到Foreach循環中,或通過調用方法ToList(),ToArray()等。在 PLINQ 中,還可以使用 foreach 執行查詢和循環訪問結果。但是,foreach 本身不會并行運行,因此,它需要將所有并行任務的輸出合并回到該循環正在其上運行的線程中。在 PLINQ 中,當必須保留查詢結果的最終排序時,以順序方式處理結果時,以及例如當為每個語句調用 Console.WriteLine 時,必須使用 foreach。為了使不需要保留排序時以及結果的處理可自己并行化時更快地執行查詢,請使用 ForAll<TSource> 方法來執行 PLINQ 查詢。ForAll<TSource> 不執行此最終合并步驟。

 

PLINQ為我們的查詢進行了加速,在編寫PLINQ查詢需要注意的事項。影響PLINQ查詢的因素有下面這些:

  

1.總體工作的計算開銷。

為了實現加速,PLINQ 查詢必須具有足夠的適合并行工作來彌補開銷。工作可表示為每個委托的計算開銷與源集合中元素數量的乘積。假定某個操作可并行化,則它的計算開銷越高,加速的可能性就越大。例如,如果某個函數執行花費的時間為 1 毫秒,則針對 1000 個元素進行的順序查詢將花費 1 秒來執行該操作,而在四核計算機上進行的并行查詢可能只花費 250 毫秒。這樣就產生了 750 毫秒的加速。如果該函數對于每個元素需要花費 1 秒來執行,則加速將為 750 秒。如果委托的開銷很大,則對于源集合中的很少幾個項,PLINQ 可能會提供明顯的加速。相反,包含無關緊要委托的小型源集合通常不適合于 PLINQ。

 

在下面的示例中,queryA 可能適合于 PLINQ(假定其 Select 函數涉及大量工作)。queryB 可能不適合,原因是 Select 語句中沒有足夠的工作,并且并行化的開銷將抵銷大部分或全部加速。

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

總而言之,就是當我們的Select函數有很少的時候,就不要用PLINQ查詢了。

2.系統上的邏輯內核數(并行度)。

適合并行的查詢在具有更多內核的計算機上運行更快,原因是可以在更多并行線程之間分擔工作。加速的總量取決于查詢的總體工作中可并行化的百分比。但是,請不要假定所有查詢在八核計算機上的運行速度都比在四核計算機的運行速度快兩倍。在調整查詢以實現最佳性能時,衡量具有多個內核的計算機上的實際結果十分重要。這一點與第一點相關:更大的數據集需要消耗更多的計算資源。

 

3.操作的數量和種類。

對于必須要保持元素在源序列中的順序的情況,PLINQ 提供了 AsOrdered 運算符。排序會產生開銷,但此開銷通常是適度的。GroupBy 和 Join 操作同樣也會產生開銷。如果允許按任意順序處理源集合中的元素,并在這些元素就緒時立即將它們傳遞到下一個運算符,則 PLINQ 的性能最佳。

 

4.查詢的執行形式。

如果要通過調用 ToArray 或 ToList 存儲查詢的結果,則必須將來自所有并行線程的結果合并為單個數據結構。這會涉及到不可避免的計算開銷。同樣,如果您使用 foreach循環對結果進行迭代,則需要將來自工作線程的結果序列化到枚舉器線程上。但是,如果只需要基于來自每個線程的結果執行某個操作,您可以使用 ForAll 方法在多個線程上執行此工作。

 

5.合并選項的類型。

可將 PLINQ 配置為將其輸出放入緩沖區,并以區塊方式或在整個結果集完成后同時生成輸出,或者在個別結果生成時流式傳送這些結果。前者可縮短總體執行時間,而后者可縮短生成的元素之間的延遲。盡管合并選項并不總是對總體查詢性能有很大影響,但是,由于它們控制用戶必須等待多長時間才能看到結果,因此可能會對感覺到的性能產生影響。

 

6.分區的種類。

在某些情況下,針對可建立索引的源集合進行的 PLINQ 查詢可能會產生不平衡的工作負載。如果發生這種情況,您也許能夠通過創建自定義分區程序來提高查詢性能。

 

下一節,將寫一下PLINQ中的分區和自定義分區。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 AutoPoster 的頭像
    AutoPoster

    互聯網 - 大數據

    AutoPoster 發表在 痞客邦 留言(0) 人氣()