【问题标题】:Optimization of function value access in record using F#使用 F# 优化记录中的函数值访问
【发布时间】:2013-05-01 02:46:53
【问题描述】:

有什么原因导致 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

【问题讨论】:

  • 我认为您的问题不太正确。正确的问题是为什么安装程序优化它,而不是为什么不能优化它。

标签: function optimization f# record


【解决方案1】:

有什么原因导致 F# 不够聪明,无法优化以下代码?

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

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

在这个例子中,fn 函数确实是内联的(添加inline 不会改变运行时间)。缓慢的例子是您为拥有更强大的构造而付出的代价。

每当您必须创建函数记录时,请记住接口和对象表达式是更好的选择(开销更少,智能感知更好):

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-bit 中执行得到的结果:

fast = 614
slow = 7498
a bit slow = 2765

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

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 抱歉,这并不能真正回答您为什么编译器无法优化它的问题。
    • +1 不过,它很好地说明了这个问题。
    猜你喜欢
    • 1970-01-01
    • 2011-04-13
    • 1970-01-01
    • 1970-01-01
    • 2021-02-13
    • 1970-01-01
    • 1970-01-01
    • 2011-10-03
    • 1970-01-01
    相关资源
    最近更新 更多