F#:部分应用和预先计算

时间:2009-09-16 07:15:10

标签: optimization f#

今天在我的代码中查看一个函数,我想知道是否可以组合部分组合和优化:

let foo (X:float) y1 y2 dx = 
    y1 + (y2 - y1) * dx / X

基本上,只需应用一个比率 - 所以前三个参数在给定的循环中通常是相同的。

我想也许如果我这样做了:

let foo2 (X:float) y1 y2 dx = 
    let dy = (y2 - y1) / X
    y1 + dy * dx
当我部分应用前三个参数时,F#会变得聪明并为我优化,但是调试模式看起来并不是这样(虽然我不确定我是否在右边测试它方式)。

问题是,这应该有效吗?如果没有更好的方法(除了用两个参数编写另一个函数)?

3 个答案:

答案 0 :(得分:4)

我认为大多数这样的“魔术优化”都需要“效果分析”,这只能通过神秘的“足够聪明的编译器”来完成。

考虑一下:

let Expensive x y = 
    printfn "I am a side-effect of Expensive"
    x + y  // imagine something expensive

let F x y z =
    let tmp = Expensive x y
    z + tmp

printfn "Least chance of magic compiler optimization"
for i in 1..3 do    
    F 3 4 i

printfn "Slightly better chance"
let Part = F 3 4
for i in 1..3 do    
    Part i

printfn "Now for real"
let F2 x y =
    let tmp = Expensive x y
    (fun z -> z + tmp)

printfn "Of course this still re-does it"
for i in 1..3 do    
    F2 3 4 i

printfn "Of course this finally saves re-do-ing Expensive"
let Opt = F2 3 4
for i in 1..3 do    
    Opt i

(* output

Least chance of magic compiler optimization
I am a side-effect of Expensive
I am a side-effect of Expensive
I am a side-effect of Expensive
Slightly better chance
I am a side-effect of Expensive
I am a side-effect of Expensive
I am a side-effect of Expensive
Now for real
Of course this still re-does it
I am a side-effect of Expensive
I am a side-effect of Expensive
I am a side-effect of Expensive
Of course this finally saves re-do-ing Expensive
I am a side-effect of Expensive

*)    

关键是,关于效果的语言语义要求编译器的行为与此完全相同,除非“昂贵”没有效果且编译器真的很聪明并且可以自己发现它。

答案 1 :(得分:3)

(这不是声誉唠叨,当我开始提问时,老实说我没有想到这一点)

这是我提出的一个解决方案,不确定它是否是最好的:

let foo3 (X:float) y1 y2 =
    let dy = (y2 - y1) / X
    (fun dx -> y1 + dy * dx)

工作得更快。

答案 2 :(得分:1)

在调试模式下,没有什么明显不同,我并不感到惊讶。为什么不实际重复N次重复(#time;;在F#交互式提示下。)

至于你希望分享除dx之外的所有固定值的通用计算,试试这个:

let fdx = foo2 X y1 y2
for dx in dxes do
    fdx dx

也就是说,fdx是部分应用程序。将它明确地存储在循环之外会让我对优化器有更多的希望。

至少在交互式提示中(我认为没有完全优化在那里,是吗?)看起来我的建议只有15%的加速(很奇怪,有任何加速,因为它肯定重复完整foo2的身体。这样做要快得多:

let fdx = foo3 X y1 y2
for dx in dxes do
    fdx dx

foo3来自(来自Benjlol):

let foo3 (X:float) y1 y2 =
    let dy = (y2 - y1) / X
    (fun dx -> y1 + dy * dx)

请注意,在循环中仅使用foo3作为4参数函数的速度是foo2的两倍,但是将部分应用程序存储在循环外部,速度提高了3倍。