Performance 如何重构代码使其具有功能性?
在玩F#的时候,我试着用一种更实用的方式来思考代码。我的大部分工作碰巧是数字性质的,所以我在想这种再教育是否有意义。以功能性的方式编写数字代码是否就像试图在圆孔中安装方形销钉,还是仅仅是一个陡峭的学习曲线问题,而不考虑应用程序 例如,让我们看一个演示弱大数定律的片段:Performance 如何重构代码使其具有功能性?,performance,f#,math.net,mathnet-numerics,Performance,F#,Math.net,Mathnet Numerics,在玩F#的时候,我试着用一种更实用的方式来思考代码。我的大部分工作碰巧是数字性质的,所以我在想这种再教育是否有意义。以功能性的方式编写数字代码是否就像试图在圆孔中安装方形销钉,还是仅仅是一个陡峭的学习曲线问题,而不考虑应用程序 例如,让我们看一个演示弱大数定律的片段: open System open System.IO open System.Windows.Forms open System.Windows.Forms.DataVisualization open FSharp.Data o
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
开放系统
开放系统
打开System.Windows.Forms
打开System.Windows.Forms.DataVisualization
打开FSharp.Data
打开FSharp.Charting
打开FSharp.Core.Operators
开放MathNet.Numerics
打开MathNet.Numerics.LinearAlgebra
打开MathNet.Numerics.Random
打开MathNet.Numerics.Distributions
打开MathNet.Numerics.Statistics
设T=1000
让arr1=Array.init T(乐趣i->float i*0)
因为我在0。。T-1 do
arr1.[i]统计数据。平均值
让arr2=Array.init T(乐趣i->float i*0)
因为我在0。。T-1 do
arr2.[i]统计数据。平均值
arr2 |>图表.线条|>图表.显示
是否有一种简洁实用的方式来表达上述内容?有多少功能范式可以被纳入到这样的工作中 我认为这是一个很好的问题。我的印象是,编写函数数字代码(想想Matlab vs Mathematica)时遇到的麻烦不是语法问题,而是性能问题。但同时,并行化代码也非常容易 我会这样编写您的代码:
let arr1'=[| for i in 0..1000->Array.init i(fun i->index.Sample(0.1))|>Statistics.Mean |]
请注意,a)没有可变赋值,b)没有索引,c)我没有初始化基于0的数组并填充它,而是使用函数初始化数组
我还将研究是否有可能直接使用index.Sample生成样本,而不是将其称为1000次
编辑
这样:index.Samples(0.1)|>Seq.take 1000
根据@ChristophRüegg的以下评论:
let expoMean (x:float []) =
Exponential.Samples(x,0.1)
x |> Statistics.Mean
Array.init 1000 (fun _ -> Array.replicate 1000 0. |> expoMean)
我还没有做过基准测试 首先,我不会将对
Array.init的调用与初始值的设置分开。您可以使用@s952163在其答案中使用的表格,或基于您的代码:
let arr1 = Array.init T (fun i ->
[|for j in 1..i do yield Exponential.Sample 0.1 |] |> Statistics.Mean
)
问题是,您正在分配中间数组,这是非常昂贵的,而且在计算平均值之后,您会立即丢弃它们。备选方案:
let arr1 = Array.init T (fun i ->
Exponential.Samples 0.1 |> Seq.take (i+1) |> Seq.average
)
现在来看第二部分:重复计算元素1..i的平均值,这将成为一个O(n^2)运算。通过使用元素1..i的和是元素1..{i-1}加上第i个元素的和这一事实,可以在O(n)中求解它
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)))
当然,你们都可以在一个管道中编写
或者,使用库函数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
这实际上是两个问题:一个是关于改进给定代码的问题,另一个是关于F#中的函数数字代码的问题。由于其他答案已经集中在特定的代码上,所以我将集中在更一般的问题上
是关于性能的吗?
根据我的经验,函数式编程在数值计算中的适用性取决于性能要求。执行时间越重要,您就越想在功能风格上妥协
如果性能不是问题,则功能代码往往工作得很好。它简洁、安全,比命令式编程更接近于数学写作。当然,有些问题可以很好地映射到命令式程序,但总的来说,函数式风格是一个很好的默认选择
如果性能是一个问题,您可能希望在不变性方面做出妥协。F#中函数代码的主要成本来自垃圾收集器,尤其是具有中间生存期的对象。使昂贵的对象可变并重新使用它们可以极大地提高执行速度。如果你想以简洁、安全的方式编写流体力学、n体模拟或游戏之类的东西,但不想让踏板达到金属的执行速度,多半径F#风格可能是一个不错的选择
如果性能决定一切,那么很可能您还是希望GPU执行。或者可以充分利用CPU向量单元、多线程等。尽管有人试图在GPU上使用F#,但这种语言并不是为了速度而不惜一切代价设计的。在这种情况下,最好使用更接近硬件的语言
当问题是这些问题的混合体时,通常可以混合解决方案。例如,昨天我需要对一组图像进行逐像素计算,执行时间很重要。因此,我使用.NET库读取了F#中的图像,然后将它们与GLSL计算着色器一起上传到GPU,该着色器可以转换像素,然后将它们下载回“F#land”。这里的要点是管理运作效率不高;代码仍然在无缘无故地复制东西。但是这仅仅是一个会消耗所有性能的操作,因此在这一个操作中使用高性能工具是合理的,而所有其他操作都在F#中整齐安全地进行。最快的方法是创建一个数组,然后将其传递给静态index.Samples(数组,0.1)
来填充它。是的!我在等一本关于折叠的书:)顺便说一句,这是一本关于科学家的书,虽然有点过时了。这里有这本书的摘录。也许最近的一些书对mathnet有了更好的解释。很高兴有人选择详细回答这部分问题。我还要补充一点,您可以通过不同的途径来提高性能。命令式代码是alw