2009-09-16 3 views
5

Глядя на функции в моем коде сегодня, я подумал, если бы можно было совместить частичное сочетание и оптимизация: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 # будет получить умный и оптимизировать для меня, когда я частично применить первые три параметра, но режим отладки не кажется, дело (хотя я не уверен, что я начал проверять его правильно).

Вопрос в том, должна ли эта работа? И если нет, то лучший способ сделать это (кроме простого написания другой функции с двумя параметрами)?

ответ

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 

*)  

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

+0

В стороне, вот почему люди хотят, например, «PureAttribute» в .Net, который вы могли бы надеть «Дорого» (при условии, что он не печатал, в отличие от моего иллюстративного примера), чтобы подталкивать компиляторы в эту оптимизацию. В качестве альтернативы, именно поэтому такие люди, как Haskell, где все чисто, а компиляторам/runtimes разрешено «кэшировать» любой вызов функции. В конце дня мое личное мнение состоит в том, что надеясь, что система «волшебным образом оптимизирует» это для вас, - мечта о трубе. Если вы хотите, чтобы ваш код был быстрым, пропишите его по адресу г-н Компьютер шаг за шагом. Люди должны всегда выполнять тяжелую работу. – Brian

+0

Ваша скорость в бодах на этом была точно такой же, как у моего мозга :) – Benjol

1

Я не удивлен, что в режиме отладки ничего не меняется. Почему бы вам на самом деле не повторить N повторений (#time;; в интерактивном приглашении F #).

Что касается вашей надежды разделить общий расчет для фиксированных значений всех, кроме йх, попробуйте следующее:

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 раза быстрее.

3

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

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

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

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

+0

Это должно совпадать с моим ответом.Вот как работает частичное применение карри-функций (F # по умолчанию). –

+0

Хорошо ... это быстрее в неоптимизированном режиме. Weird. Это должно быть исправлено. –

+0

@ wrang-wrang, это не то же самое, что простое частичное применение карриных функций, поскольку эффекты были переупорядочены. Простое eta-преобразование, которое оставило «fun dx ->» в верхней части функции, было бы эквивалентным, но перемещение «fun dx ->» после другого кода означает, что другой код запускается один раз после применения первых двух аргументов , (В отсутствие эффектов это неразличимо, но в присутствии их разница ясна.) – Brian

Смежные вопросы