功能化数字代码

时间:2016-07-07 05:04:48

标签: performance f# functional-programming mathnet

使用F#,我试图以更实用的方式思考代码。我的很大一部分工作恰好都是数字化的,所以我在想这种再教育是否有意义。是否正在以一种功能性的方式编写数字代码,例如试图在圆孔中安装方形钉,或者它只是一个陡峭的学习曲线而不管应用程序是什么?

例如,让我们拿一个演示大数字弱法则的片段:

open System
open System.IO
open System.Windows.Forms
open System.Windows.Forms.DataVisualization
open FSharp.Data
open FSharp.Charting
open FSharp.Core.Operators
open MathNet.Numerics
open MathNet.Numerics.LinearAlgebra
open MathNet.Numerics.Random
open MathNet.Numerics.Distributions
open MathNet.Numerics.Statistics


let T = 1000

let arr1 = Array.init T (fun i -> float i*0.)
for i in 0 .. T-1 do
    arr1.[i] <- [|for j in 1..i do yield Exponential.Sample(0.1)|] |> Statistics.Mean

let arr2 = Array.init T (fun i -> float i*0.)
for i in 0 .. T-1 do
    arr2.[i] <- arr1.[1 .. i] |> Statistics.Mean

arr2 |> Chart.Line |> Chart.Show

是否有简洁的功能方式表达上述内容?有多少功能范例可以融入到这样的工作中?

不确定问题是否属于主题。感谢。

3 个答案:

答案 0 :(得分:6)

这实际上是两个问题:一个是关于改进给定代码,一个是关于F#中的功能数字代码。由于其他答案已经集中在特定代码上,我将重点关注更一般的问题。

是关于表现吗?

根据我的经验,函数式编程在数字中的适用性取决于性能要求。执行时间越重要,您就越想在功能风格上妥协。

如果性能不是问题,功能代码往往效果很好。它简洁而安全,与命令式编程相比,它更接近数学写作。当然,一些问题很好地映射到命令式程序,但总的来说,功能风格是一个很好的默认选择。

如果性能有些问题,您可能希望在不变性方面做出妥协。 F#中功能代码的主要成本来自垃圾收集器,尤其是来自具有中间生命周期的对象。使昂贵的对象变得可变并重新使用它们可以在执行速度上产生巨大的差异。如果你想以简洁安全的方式写出流体动力学,身体模拟或游戏等东西,但不是针对踏板到金属的执行速度,那么多范式的F#风格可能会很好要走的路。

如果性能就是一切,那么您可能无论如何都要执行GPU。或者可以充分利用CPU矢量单元,多线程等。虽然有人试图在GPU上使用F#,但这种语言并不是为了不惜一切代价而设计的。在这种情况下使用一种更接近硬件的语言可能会更好。

当问题是这些问题的混合时,通常可以混合解决方案。例如,昨天我需要对一组图像进行逐像素计算,执行时​​间很重要。所以我使用.NET库读取F#中的图像,然后将它们与转换像素的GLSL计算着色器一起上传到GPU,然后将它们下载回#34; F#land&#34;。这里的要点是管理运作不高效;代码仍在抄袭周围没有任何实际原因。但这只是一次操作会影响所有性能,因此在一次操作中使用高性能工具是合理的,而所有其他操作在F#中安排得非常安全。

答案 1 :(得分:5)

我首先不要将呼叫分开到let arr1 = Array.init T (fun i -> [|for j in 1..i do yield Exponential.Sample 0.1 |] |> Statistics.Mean ) 并设置初始值。您可以使用@ s952163在其答案中使用的表单,或者根据您的代码:

let arr1 = Array.init T (fun i ->
    Exponential.Samples 0.1 |> Seq.take (i+1) |> Seq.average
)

问题是你正在分配中间数组,这是昂贵的 - 而且你无论如何都要在计算平均值之后丢弃它们。替代方法:

let sums, _ =
    arr1
    |> Array.mapFold (fun sumSoFar xi ->
        let s = sumSoFar + xi
        s, s
    ) 0.0
let arr2 = 
    sums
    |> Array.mapi (fun i sumi -> sumi / (float (i + 1)))

现在对于第二部分:您正在重复计算元素1..i的平均值,这将成为O(n ^ 2)运算。您可以使用元素1..i的总和是元素1 .. {i-1}加上第i个元素的总和,在O(n)中求解它。

Array.scan

当然,你们都可以在一个管道中写出来。

或者,使用库函数T+1计算累积总和,在这种情况下会给出长度为let arr2 = Array.sub (Array.scan (+) 0.0 arr1) 1 T |> Array.mapi (fun i sumi -> sumi / (float (i + 1))) 的结果,然后从中删除第一个元素:

Seq.scan (+) 0.0 arr1
|> Seq.skip 1
|> Seq.mapi (fun i sumi -> sumi / (float (i + 1)))
|> Seq.toArray

或避免使用中间数组:

            <div class="repeater" ng-repeat="files in files">  {{files.name}}              
                <div class='progress-bar'>
                  <div class='percentage' ng-style="style">
                  </div>
                </div>
            </div>

答案 2 :(得分:0)

我认为这是一个非常好的问题。我的印象是,在编写函数数字代码时会遇到的麻烦(想想Matlab与Mathematica)不是语法而是性能。但与此同时,并行化代码也非常容易。

我会这样写你的代码:

MyObj myObj = new MyObj("Test input 2"); RestTemplate restTemplate = new RestTemplate(); String response = restTemplate.postForEntity("http://localhost:8080/get2", myObj, String.class).getBody();

注意a)没有可变赋值,b)没有索引和c)我没有初始化一个基于0的数组并填充它,而是用函数初始化数组。

我还会研究是否可以使用Exponential.Sample直接生成样本,而不是将其调用1,000次。

修改

像这样:let arr1' = [|for i in 0..1000 -> Array.init i (fun i -> Exponential.Sample(0.1)) |> Statistics.Mean |]

根据@ChristophRüegg的评论如下:

Exponential.Samples(0.1) |> Seq.take 1000

我没有对此进行基准测试。