我需要将任意整数(即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吗?这种软件开发有什么特殊的工作流程吗?
答案 0 :(得分:4)
也许这不是你想要的答案,但在函数式编程的世界中,任务的正确选择(算法,数据结构......)可能会带来更多优点,而不是.NET Profiler可能提供的任何类似OOP的微观代码分析。
为了说明我的观点,在您的演示案例中,选择在toDigits
和toDigits'
函数中使用序列进行操作是很难证明的。如果我们选择保留在数组空间内,则可以将等效功能表示为
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
编译代码的好处是:
随着代码的发展,您可以考虑将核心功能移至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
上没有使用任何算术运算,因此很难超越bigint
。 toDigit
的一个明显缺点是它在负bigint
s上给出了毫无意义的结果。