F#scripts / F#interactive中的性能调优

时间:2013-06-15 17:55:05

标签: .net performance f# profiling f#-interactive

我需要将任意整数(即bigint)转换为数字,因此我可以通过索引访问它们。

我发现自己(a)在两种可能的算法实现之间徘徊:

open System

let toDigits (x:bigint) =
    x.ToString() 
    |> Seq.map (fun c -> (int c) - (int '0'))
    |> Seq.toArray

let toDigits' (x:bigint) =
    seq {
        let x' = ref x
        while !x' <> 0I do
            yield (int (!x' % 10I))
            x' := !x' / 10I
    } |> Seq.toArray |> Seq.rev

哪一个最快我嘟??为了帮助我回答这个问题,我设计了一个简单的profile方法

let profile f times = 
    let x = ref 0
    while !x < times do
        incr x
        f (bigint !x) |> ignore

当与F#Interactive&#39; #time混淆时产生以下输出:

> profile toDigits 10000000;;
Real: 00:00:11.609, CPU: 00:00:11.606, GC gen0: 825, gen1: 1, gen2: 0
val it : unit = ()
> profile toDigits' 10000000;;
Real: 00:00:28.891, CPU: 00:00:28.844, GC gen0: 1639, gen1: 3, gen2: 0
val it : unit = ()

这清楚地表明了toDigit的优越性。我想知道为什么,所以我要问我的同伴F#溢出者,从现在开始我应该做些什么。

在一个典型的Java程序中,我只需启动一个分析器(例如jvisualvm)并让它告诉我在某种CPU采样视图中哪些是热门方法。我想这与我在常规项目中使用.fs文件开发完全相同。因为我在F#Interactive中,我有点迷失了。我应该将.NET Profiler(在本例中为ANTS Memory Profiler)附加到F#Interactive吗?这种软件开发有什么特殊的工作流程吗?

2 个答案:

答案 0 :(得分:4)

也许这不是你想要的答案,但在函数式编程的世界中,任务的正确选择(算法,数据结构......)可能会带来更多优点,而不是.NET Profiler可能提供的任何类似OOP的微观代码分析。

为了说明我的观点,在您的演示案例中,选择在toDigitstoDigits'函数中使用序列进行操作是很难证明的。如果我们选择保留在数组空间内,则可以将等效功能表示为

let toDigits'' (x: bigint) =
    x.ToString().ToCharArray() |> Array.map (fun c -> int(c) - int('0'))

现在转到FSI提供的分析,我们可以观察

> profile toDigits 10000000;;
Real: 00:00:13.020, CPU: 00:00:13.000, GC gen0: 1649, gen1: 2, gen2: 0

> profile toDigits'' 10000000;;
Real: 00:00:02.334, CPU: 00:00:02.343, GC gen0: 604, gen1: 1, gen2: 0

因此,我们得到了 5.6倍的加速,这是因为有更好的数据结构选择可供使用,而FSI分析器就像这个事实的确认一样。

答案 1 :(得分:1)

  

在一个典型的Java程序中,我只需要启动一个分析器(例如jvisualvm)并让它告诉我在某种CPU采样视图中哪些是热门方法。我想这与我在常规项目中使用.fs文件开发完全相同。因为我在F#Interactive,我有点迷失。

使用fsx文件和F#Interactive并不意味着您应该完全放弃编译项目。我会将Build Action中的File Properties更改为Compile,以便直接编译fsx文件。我们需要在某些地方使用条件编译,例如

#if INTERACTIVE
#time "on";;
#endif

编译代码的好处是:

  • 您可以使用Visual Studio分析器获取有关程序中热点的统计信息。
  • 您可以使用ILSpy反编译程序。有时,IL或甚至等效的C#代码可以让您深入了解程序的行为方式。

随着代码的发展,您可以考虑将核心功能移至fs文件,并在fsx个文件中保留快速分析功能。

回到您的示例,toDigits'的改进是避免使用引用:

let toDigits'' (x:bigint) =
    let rec loop x acc =
        if x <> 0I then
            loop (x/10I) (int(x%10I)::acc)
        else acc
    loop x [] |> List.toArray

结果显示,toDigits''toDigits'快1.5倍,比toDigits慢1.5倍。

由于toDigit上没有使用任何算术运算,因此很难超越biginttoDigit的一个明显缺点是它在负bigint s上给出了毫无意义的结果。