2009-09-16 15 views
5

En cuanto a una función en mi código de hoy, me preguntó si era posible combinar combinación parcial y optimización:F #: Aplicación parcial y cálculo previo

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

Básicamente, la mera aplicación de una relación - por lo que los tres primeros parámetros son generalmente los mismos dentro de un bucle dado.

pensé que tal vez si yo acabo de hacer esto:

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

F # obtendría inteligente y optimizar para mí cuando parcialmente aplicar los tres primeros parámetros, pero el modo de depuración que no parece ser el caso (aunque no estoy seguro de haberlo probado de la manera correcta).

La pregunta es, ¿debería funcionar? Y si no, ¿hay una forma mejor de hacerlo (aparte de simplemente escribir otra función con dos parámetros)?

Respuesta

4

Creo que la mayoría de tales 'optimizaciones mágicas' requerirían 'análisis de efectos' que solo hace el mítico 'compilador suficientemente inteligente'.

Ponder esto:

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 

*)  

El punto es, la semántica del lenguaje respecto a los efectos requerir el compilador se comporte exactamente como esta, a menos que 'caro' no tiene ningún efecto y el compilador es realmente muy inteligente y puede descubrir que por sí mismo.

+0

Como comentario aparte, esta es la razón por la cual las personas desean, p. un "PureAttribute" en .Net, que podría poner en "Caro" (asumiendo que no se imprimió, a diferencia de mi ejemplo de exposición), para empujar a los compiladores hacia esta optimización. Alternativamente, esta es la razón por la cual a las personas les gusta Haskell, donde todo es puro y los compiladores/tiempos de ejecución tienen permitido 'cache' cualquier llamada a función. Al final del día, mi opinión personal es que la esperanza de que el sistema 'optimizará mágicamente' esto para usted es una quimera. Si quiere que su código sea rápido, deletérelo al Sr. Computadora paso a paso. Los humanos siempre deben hacer el trabajo duro. – Brian

+0

Su velocidad en baudios en este caso fue exactamente la misma que la de mi cerebro :) – Benjol

1

No me sorprende que aparentemente no haya nada diferente en el modo de depuración. ¿Por qué no hace realmente N repeticiones de N (#time;; en el indicador interactivo F #)?

En cuanto a su esperanza de compartir el cálculo común para valores fijos de todos, pero dx, intente esto:

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

Es decir, FDX es la aplicación parcial. Almacenarlo explícitamente fuera del ciclo me hace esperar más del optimizador.

Al menos en el mensaje interactivo (no creo que se realice una optimización completa allí, ¿no?) Parece que mi sugerencia es solo un 15% de aceleración (es raro que haya una aceleración, porque definitivamente repite el cuerpo completo de foo2). Hacer esto es mucho más rápido:

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

Dónde foo3 es (de Benjlol):

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

Tenga en cuenta que sólo usar foo3 como una función 4 argumento en el bucle es dos veces tan lento como foo2, pero el almacenamiento la aplicación parcial fuera del ciclo, es 3 veces más rápido.

3

(Esto se putear no la reputación, la verdad es que no había pensado en esto cuando empecé a hacer mi pregunta)

Aquí hay una solución que he llegado con, no está seguro de si es la mejor:

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

Funciona mucho más rápido.

+0

Esto debería ser lo mismo que mi respuesta.Así es como funciona la aplicación parcial de funciones al curry (el valor predeterminado de F #). –

+0

Ok ... es más rápido en modo no optimizado. Extraño. Eso debería ser arreglado. –

+0

@ wrang-wrang, esto no es lo mismo que una simple aplicación parcial de funciones curried, ya que los efectos han sido reordenados. Una conversión de eta simple que dejara el "funcion dx ->" en la parte superior de la función sería equivalente, pero mover el "funcion dx ->" después de otro código significa que el otro código se ejecuta una vez que se aplican los dos primeros argumentos . (En ausencia de efectos, esto es indistinguible, pero en presencia de ellos, la diferencia es clara). – Brian