2009-09-16 11 views
5

an einer Funktion heute in meinem Code Sehen, fragte ich mich, ob es möglich war teilweise Kombination und Optimierung zu kombinieren:F #: Partielle Anwendung und Vorkalkulation

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

Grundsätzlich nur ein Verhältnis der Anwendung - so die ersten drei Parameter sind innerhalb einer gegebenen Schleife im Allgemeinen gleich.

Ich dachte, vielleicht, wenn ich dies tue gerade:

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

F # wäre für mich klug und optimieren, wenn ich die ersten drei Parameter teilweise anwenden, aber es Debug-Modus scheint es nicht der Fall zu sein (obwohl ich nicht sicher bin, ob ich es richtig getestet habe).

Die Frage ist, sollte das funktionieren? Und wenn nicht, gibt es einen besseren Weg, es zu tun (abgesehen davon, nur eine andere Funktion mit zwei Parametern zu schreiben)?

Antwort

4

Ich denke, dass die meisten solcher "magischen Optimierungen" eine "Effektanalyse" erfordern würden, die nur durch den mythischen "hinreichend intelligenten Compiler" durchgeführt wird.

Ponder dies:

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 

*)  

Der Punkt ist, erfordern die Sprachsemantik in Bezug auf Auswirkungen der Compiler genau so verhalten, es sei denn, ‚teuer‘ keine Auswirkungen hat und der Compiler ist wirklich sehr, sehr klug und entdecken, dass allein.

+0

Nebenbei gesagt, das ist der Grund, warum Leute z.B. ein "PureAttribute" in .Net, das Sie "Expensive" verwenden könnten (vorausgesetzt, dass es nicht gedruckt wurde, im Gegensatz zu meinem Beispiel), um Compiler in diese Optimierung einzubeziehen. Alternativ dazu können Leute wie Haskell, wo alles rein ist und Compiler/Laufzeiten jeden Funktionsaufruf "cachen" dürfen. Am Ende des Tages, meine persönliche Meinung ist, dass die Hoffnung, dass das System "magisch optimieren" dies für Sie ist ein Pfeifentraum. Wenn Sie möchten, dass Ihr Code schnell ist, schreiben Sie ihn Schritt für Schritt an Herrn Computer. Menschen müssen immer die harte Arbeit machen. – Brian

+0

Ihre Baudrate war genau die gleiche wie die meines Gehirns :) – Benjol

1

Ich bin nicht überrascht, dass nichts im Debug-Modus scheinbar anders ist. Warum nimmst du nicht tatsächlich N Wiederholungen davon (#time;; bei der F # interaktiven Eingabeaufforderung).

Was Ihre Hoffnung, die gemeinsame Berechnung für feste Werte aller aber dx zu teilen, versuchen Sie dies:

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

Das heißt, fdx ist die partielle Anwendung. Wenn ich es explizit außerhalb der Schleife abspeichere, hoffe ich auf mehr vom Optimierer.

Zumindest in der interaktiven Eingabeaufforderung (Ich glaube nicht, vollständige Optimierung ist dort getan, ist es?) Es sieht aus wie mein Vorschlag ist nur eine 15% Beschleunigung (es ist seltsam, dass es irgendwelche Beschleunigung, weil es auf jeden Fall wiederholt voller Körper von foo2). Dadurch ist viel schneller:

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

Wo foo3 ist (von Benjlol):

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

Beachten Sie, dass in der Schleife doppelt so langsam mit foo3 als 4 Argument Funktion wie foo2 ist, aber die Speicherung die partielle Anwendung außerhalb der Schleife ist 3 mal schneller.

3

(Dieser Hurerei nicht Ruf ist, hatte ich ehrlich gesagt nicht gedacht, als ich meine Frage begann zu fragen)

hier eine Lösung ist mir nicht sicher, habe kommen mit, wenn es das Beste ist:

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

Funktioniert viel schneller.

+0

Dies sollte das gleiche wie meine Antwort sein.So funktioniert die teilweise Anwendung von Curry-Funktionen (der F # -Standard). –

+0

Ok ... im nicht optimierten Modus ist es schneller. Seltsam. Das sollte behoben werden. –

+0

@ Wrang-Wrang, das ist nicht das Gleiche wie einfache partielle Anwendung von Curry-Funktionen, da die Effekte neu geordnet wurden. Eine einfache Eta-Umwandlung, die den "fun dx ->" am Anfang der Funktion belassen würde, wäre äquivalent, aber das Verschieben von "fun dx ->" nach anderem Code bedeutet, dass der andere Code einmal ausgeführt wird, nachdem die ersten beiden Argumente angewendet wurden . (In Abwesenheit von Effekten ist dies nicht unterscheidbar, aber in Gegenwart von ihnen ist der Unterschied klar.) – Brian