现在普通的计算机都是多核的了,并行计算(Parallel Programming)是程序员必须考虑的一个问题。函数式编程(Functional Programming)的一个好处就是非常适合于并行计算,一个原因在于我们可以很方便的把功能封装成一个个函数,然后将函数交给计算机去运行,另一个原因是,在纯粹的函数式编程中没有变量(variable),没有全局状态(state),只有值(value),输入值和输出值,这样的话,任何一个函数的执行都无法影响其他函数的执行(no side effect),所以并行计算才能进行。

F#中支持并行计算的功能包括Async静态类和async { expression } 结构(construct)。async {} 用来告诉运行环境,大括号中的运算可以异步的进行,即在另一个线程上运行。Async.Parallel函数则用来并行的执行多个计算任务,当然这些计算任务应该是可以以异步方式运行的。所以我们一般会将Async.Parallelasync {}结合起来使用。

让我们来计算π小数点后非常远的位置的数字,比如第一千万位。1996年数学家发现了π的一个级数表达式:

通过这个表达式,可以直接运算小数点后任意位置的值,你可以在这个链接中找到这个BBP算法的详细步骤。不过这个算法算出的是16进制下的值,比如第1,000,000位后面的10位数是6C65E52CB4。

现在我们用这个算法来算第一千万位,并测量一下计算的总时间。这里主要的实现函数是nthHexDigitOfPi:


let i = 10000000I 
let startTime = DateTime.Now
let result = nthHexDigitOfPi(i)
let finishTime = DateTime.Now
printfn "%A Result=%s Elasped Time=%s" i result (finishTime.Subtract(startTime).ToString()) 

数字后面的I表示这是一个bigint,一个任意大的整数。

看上面π的公式,把括号展开后这个公式可以分解成四个无穷级数,这四个无穷级数的形式是相似的,算法也是相同的。


let nthHexDigitOfPi (d:bigint) = 
    let s1 = series(1I, d)
    let s2 = series(4I, d)
    let s3 = series(5I, d)
    let s4 = series(6I, d)
    let result = frac(4.0*s1-2.0*s2-s3-s4)
    getHex(result, 10)

fracgetHex是两个帮助函数,frac返回一个浮点数的小数部分,getHex返回一个小数的小数部分用十六进制表示后的指定长度的数字串。(比如getHex(0.42342979756754035859981…, 10) = “6C65E52CB4”)。

全部程序会附在文章最后。这里的情况是,很显然我们可以并行的计算 s1, s2, s3, s4。将nthHexDigitOfPi改写成并行的方式也十分简便:


let nthHexDigitOfPiAsync (d:bigint) = 
    let s = 
        Async.Parallel [
            async { return series(1I, d) };
            async { return series(4I, d) };
            async { return series(5I, d) };
            async { return series(6I, d) }
            ] |> Async.RunSynchronously
    let result = frac(4.0*s.[0]-2.0*s.[1]-s.[2]-s.[3])
    getHex(result, 10)

你也许奇怪为什么要把这四个并行运算pipe-forward(|>)Async.RunSynchronouslyAsync.Parallel只是声明了一个运算,这个运算包括四个并行运算。Async.RunSynchronously的任务就是运行这个运算并得到所有返回结果。

我的笔记本电脑配置是Intel酷睿2双核T9400 2.53GHz CPU, 3GB内存。串行(Sequential)的程序运算第一千万位用时15分49秒,并行程序用时8分38秒。

----------------------------------------------------------
  10 Hex Digits After  Sequential Time   Parallel Time
----------------------------------------------------------
    10,000 8AC8FCFB80  00:00:00.6093750  00:00:00.3593750
   100,000 35EA16C406  00:00:07.2031250  00:00:03.9687500
 1,000,000 6C65E52CB4  00:01:22.9687500  00:00:47.7343750
10,000,000 7AF5863EFF  00:15:49.5000000  00:08:38.1875000
----------------------------------------------------------

并行程序比串行程序大约快了一倍。在串行的情况下CPU使用率是57%,而并行时CPU使用率是100%。如果是四核的话,并行程序的速度将是串行程序的4倍,因为正好我们有4个运算分配给4个核同时运行,所有CPU将满负荷运行。

 

注:

  1. 文中提到的这个算法并不是最快的,事实上它是最慢的,因为它使用的是Plouffe公式。有关资料可以参考http://www.numberworld.org/misc_runs/pi-5t/details.html#formulas
  2. 以上算法的F#实现基本上是网上C和C#实现的F#移植,有关资料可以参考http://www.experimentalmath.info/bbp-codes/,http://code.google.com/p/pi-hex-screensaver/
  3. 最近网上有不少关于计算π的有趣新闻,比如http://www.numberworld.org/misc_runs/pi-5t/details.html, 以及Yahoo!的http://www.bbc.co.uk/news/technology-11313194,雅虎采用Hadoop云计算技术,1000台电脑历时23天,算出了π小数点后第2千万亿位(二进制)-2后面15个零!

 

附件(程序清单

Posted on 2010-10-08 16:45  yanmingcao  阅读(1771)  评论(3编辑  收藏  举报