蛙蛙推薦:F#實現並行排序算法

摘要:F#是微軟推出的一套函數式編程語言,能在CLR中運行,且和.NET其它語言能很好的交互,又因爲它對併發編程的特殊支持,比如不變對象,異步表達式,新的並行基元等,所以很值得入門學習一下。現在我們綜合應用這些技術寫一個並行排序算法,並對其進行性能測試。

思路:並行算法中其中有一種比較常見的方法就是先把要處理的數據分成若干份,然後讓不同的線程(CPU)去處理,然後所有的線程處理完成後,把結果匯聚在一起,在一個獨立的線程裏完成結果合併,從而形成最終結果。在做分割的時候儘量讓每個線程只訪問自己獨立的數據,而不訪問全局數據和其它線程的數據(這裏說的數據是非只讀數據),在合併結果的時候要有一種高效的算法來合併。

排序算法裏有歸併排序算法,我們先寫一個多路歸併排序算法,然後把要排序的數組分成CPU的個數份,讓每個CPU去對每一份進行排序,所有線程排序完成後匯聚在一起,在一個獨立的線程裏進行歸併排序。

大概再解釋一下代碼,可能有些人對f#還不熟悉。
1、歸併算法的思路就是把多個已經排序的數組合併成一個大的排序數組,先從每個分數組的最小下標開始,誰都最小就放到大數組裏,然後這個數組的下標加一,然後再比較,再把最小的放到大數組裏,重複,直到所有的小數組的下標已經指向到末尾。其中會用到一個臨時變量min,所以用mutable關鍵字修飾。
2、F#的數組的長度用Array.length方法得出,變量和數組的賦值符號是<-,而不是=,=相當於c#裏的==,f#裏沒有continue和break等關鍵字
3、async關鍵字是一個新的並行基元,用它擴住的代碼由f#自動的異步在線程池裏執行,如果裏面要返回結果的話,要用let!和return!關鍵字,我們的排序只是對數組進行操作,並不返回,所以這裏比較簡單。
4、(fun a b -> a - b)是一個lamda表達式,它可以自動轉換成Comparer<T>,起到排序依據的作用
5、Array.map表示把一個數組裏的每個元素應用一個方法,它這時候不執行,會通過管道傳遞給Async.Parallel方法,Async.Parallel方法返回一個異步執行數組Async<'a array>,最後用Async.Run來真正執行Async.Parallel返回的結果。
6、|>表示管道的意思,大致就是把前一個函數的結果讓後一個函數來用,這樣一條語句可以表達很連貫的邏輯。

整體代碼如下:

 1 #light
 2 
 3 open System
 4 open System.Diagnostics
 5 open Microsoft.FSharp.Control.CommonExtensions
 6 
 7 let merge_sort destArray source cmp =
 8    let N = Array.length source
 9    let L = Array.length destArray - 1
10    let posArr = Array.create N 0
11    for i = 0 to L do
12       let mutable min = -1
13       for j = 0 to N - 1 do
14          if posArr.[j] >= Array.length source.[j] then ()
15          else
16             if min = -1 then min <- j
17             else
18                if (cmp source.[j].[posArr.[j]] source.[min].[posArr.[min]]) < 0 then min <- j
19       if min = -1 then ()
20          else
21             destArray.[i] <- source.[min].[posArr.[min]]                             
22             posArr.[min] <- posArr.[min] + 1
23 
24 let parallel_sort cmp arr =
25     let processorCount = Environment.ProcessorCount;
26     let partArray = Array.create processorCount [||]
27     let mutable remain = Array.length arr
28     let partLen = Array.length arr / processorCount
29 
30     for i = 0 to processorCount - 1 do
31         if i = processorCount - 1 then
32             let temp_arr = Array.create remain 0
33             Array.Copy(arr, i*partLen, temp_arr, 0, remain)
34             partArray.[i] <- temp_arr
35         else
36             let temp_arr = Array.create partLen 0
37             Array.Copy(arr, i*partLen, temp_arr, 0, partLen)
38             remain <- remain - partLen
39             partArray.[i] <- temp_arr
40 
41     let a_sort_one arr =
42         async {
43             Array.sort cmp arr
44         }
45         
46     let a_sort_all =
47         partArray
48         |> Array.map (fun f -> a_sort_one f)
49         |> Async.Parallel
50         |> Async.Run
51         
52     a_sort_all
53     let ret = Array.create (Array.length arr) 0
54     merge_sort ret partArray (fun a b -> a - b)
55     ret
56 
57 let arr = Array.create 1000000 0
58 let rnd = new Random()
59 for i = 0 to Array.length arr - 1 do
60     arr.[i] <- rnd.Next()
61 
62 let stop = Stopwatch.StartNew()
63 stop.Start
64 let sorted_arr = parallel_sort (fun a b -> a-b) arr
65 stop.Stop
66 printfn "並行排序結果/r/n=%A/r/n用時%d毫秒" sorted_arr stop.ElapsedMilliseconds          
67 
68 let stop2 = Stopwatch.StartNew()
69 Array.sort (fun a b -> a-b) arr
70 stop.Stop
71 printfn "串行排序結果/r/n=%A/r/n用時%d毫秒" arr stop2.ElapsedMilliseconds   
72 
73 Console.ReadKey(true)   


我本機,IBM X200測試串行排序大約在1200多秒,並行排序在900秒左右。

相關鏈接:
從簡單的 F# 表達式構建併發應用程序
http://msdn.microsoft.com/zh-cn/magazine/cc967279.aspx
從C# 3.0到F#
http://www.cnblogs.com/allenlooplee/archive/2008/07/25/1251631.html
F#系列隨筆索引
http://www.cnblogs.com/anderslly/archive/2008/10/08/fs-posts-indices.html
Concurrency with MPI in .NET
http://weblogs.asp.net/podwysocki/archive/2008/05/15/concurrency-with-mpi-in-net.aspx
使用 .NET Framework 中的函數式編程技術
http://msdn.microsoft.com/zh-cn/magazine/cc164244.aspx
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章