F #: Частичное применение и предварительный расчет - PullRequest
5 голосов
/ 16 сентября 2009

Сегодня, глядя на функцию в моем коде, я подумал, можно ли объединить частичное сочетание и оптимизацию:

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 ]

4 голосов
/ 16 сентября 2009

Я думаю, что большинство таких «магических оптимизаций» потребует «анализа эффектов», который выполняется только мифическим «достаточно умным компилятором».

Обдумайте это:

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

*)    

Дело в том, что языковая семантика в отношении эффектов требует, чтобы компилятор вел себя точно так же, если только «Дорогой» не имеет эффектов, а компилятор действительно очень умен и может обнаружить это самостоятельно.

3 голосов
/ 16 сентября 2009

(Это не репутация, я честно не думал об этом, когда начал задавать свой вопрос)

Вот одно решение, которое я придумала, но не уверена, что оно лучшее:

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

Работает намного быстрее.

1 голос
/ 16 сентября 2009

Я не удивлен, что в режиме отладки ничего не изменилось. Почему бы вам на самом деле не рассчитать 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 (из Бенджлола):

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

Обратите внимание, что просто использование foo3 в качестве функции с четырьмя аргументами в цикле в два раза медленнее, чем в foo2, но хранение частичного приложения вне цикла - в 3 раза быстрее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...