蛙蛙推荐: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
posted @ 2009-08-19 08:00  蛙蛙王子  Views(...)  Comments(...Edit  收藏