使用F#优化记录中的函数值访问

时间:2013-04-30 17:17:03

标签: function optimization f# record

为什么F#不够聪明以优化以下代码? fast = 880slow = 8090

type Data = { fn: int * int -> int }
let fn (x, y) = x + y
let data = { fn = fn }

let mutable a = 0
let s = System.Diagnostics.Stopwatch()

s.Start()
for i in 0 .. 1000000000 do
  a <- fn(i, i)
printfn "fast = %d" s.ElapsedMilliseconds

s.Restart()
for i in 0 .. 1000000000 do
  a <- data.fn(i, i)
printfn "slow = %d" s.ElapsedMilliseconds

2 个答案:

答案 0 :(得分:7)

  

为什么F#不够聪明以优化以下代码?

如果F#编译器能够优化这种情况,我会感到惊讶。最后,fn是一个记录字段,用于保存数据,而不是执行函数。

即使在非静态成员上,编译器也无法内联它们,因为这些成员受到环境变化的限制。通过声明let绑定,您可以获得静态环境的优势,并且编译器能够在一些简单的情况下内联。

实际上,在此示例中,fn函数已内联(添加inline不会更改运行时间)。缓慢的例子是你为拥有更强大的构造而付出的代价。

每当你必须创建一个函数记录时,请记住接口和对象表达式是更好的选择(更少的开销,更好的intellisense):

type Data2 =
    abstract fn : int * int -> int

let data2 = 
    { new Data2 with
        member __.fn (x, y) = fn (x, y) }

s.Restart()
for i in 0 .. 1000000000 do
  a <- data2.fn(i, i)
printfn "a bit slow = %d" s.ElapsedMilliseconds

这是我在F#Interactive 64位中执行的结果:

fast = 614
slow = 7498
a bit slow = 2765

因此,基于接口的方法比基于记录的方法快3倍,比内联方法慢3倍。

答案 1 :(得分:4)

快速路径是内联fn,但慢速路径正在进行函数调用。

注意,你甚至不需要记录,这就足够了:

let fn' = fn
for i in 0 .. 1000000000 do
  a <- fn'(i, i)
printfn "slow = %d" s.ElapsedMilliseconds