1

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

不过,我发现自己在算法的两种可能实现之间徘徊:

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 合并时#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 分析器(在本例中为 ANTS 内存分析器)附加到 F# Interactive 吗?这种软件开发有什么特殊的工作流程吗?

4

2 回答 2

5

也许这不是您要寻找的答案,但在函数式编程的世界中,为任务选择正确的方法(算法、数据结构……)可能会带来更多的好处,而不是任何彻底的 OOP-like micro- .NET 分析器可能提供的级别代码分析。

为了说明我的观点,在您的演示案例中,选择在和函数中使用序列进行操作很难证明是合理的。如果我们选择留在数组空间内,则等效功能可以表示为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 分析器正是对这一事实的确认

于 2013-06-15T19:56:20.497 回答
1

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

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

#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 倍,比 . 慢 1.5 倍toDigits

它很难被击败toDigit,因为它不使用任何算术运算bigint。一个明显的缺点toDigit是它在负数上给出了毫无意义bigint的结果。

于 2013-06-15T19:46:51.580 回答