2013-04-30 32 views
3

有没有什么理由为什么F#不够聪明以优化下面的代码? fast = 880slow = 8090使用F#优化记录中的函数值存取

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 
+0

我认为你的问题不太对。正确的问题是为什么不安装程序优化它,而不是为什么_优化它。 – mydogisbox 2013-04-30 18:02:17

回答

7

是否有任何理由为什么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#互动64执行结果位:

fast = 614 
slow = 7498 
a bit slow = 2765 

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

4

快速路径内嵌fn,但慢路径正在执行函数调用。

注意,你甚至不需要备案,就足以做到:

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

对不起,这并没有真正回答你为什么编译器无法优化它的问题。 – 2013-04-30 17:41:58

+0

+1不过,这是一个很好的例子。 – Shredderroy 2013-05-01 04:38:08