2015-12-01 3 views

Antwort

15

The Good Way

Der empfohlene Weg, dies zu tun ist, um die Funktionsnamen zu einem Symbol konvertieren und dann das Symbol nachschlagen in die entsprechende Namespace:

julia> fn = "time" 
"time" 

julia> Symbol(fn) 
:time 

julia> getfield(Main, Symbol(fn)) 
time (generic function with 2 methods) 

julia> getfield(Main, Symbol(fn))() 
1.448981716732318e9 

Sie Main hier zu jedem Modul nur bei Funktionen in dem aussehen ändern Modul. Dadurch können Sie die Funktionen einschränken, die nur für die in diesem Modul verfügbaren Funktionen verfügbar sind. Sie können ein "blankes Modul" verwenden, um einen Namespace zu erstellen, der nur die Funktionen enthält, mit denen Sie ihn füllen, ohne dass standardmäßig der Name aus Base importiert wird.

Das Bad Way

Ein anderer Ansatz, der nicht empfohlen, aber die viele Menschen für als Erster zu erreichen scheinen ist eine Zeichenfolge für Code zu konstruieren, die die Funktion aufruft und dann die Zeichenfolge analysieren und auswerten. Zum Beispiel:

julia> eval(parse("$fn()")) # NOT RECOMMENDED 
1.464877410113412e9 

Während diese temptingly einfach ist, ist es nicht zu empfehlen, da es langsam, spröde und gefährlich ist. Das Analysieren und Auswerten von Code ist inhärent viel komplizierter und somit langsamer als das Durchführen einer Namenssuche in einem Modul - die Namenssuche ist im Wesentlichen nur eine Hashtabellensuche. In Julia, wo Code Just-in-Time kompiliert und nicht interpretiert wird, ist Eval langsamer und teurer, da es nicht nur das Parsen, sondern auch das Generieren von LLVM-Code, das Ausführen von Optimierungsdurchläufen, das Ausgeben von Maschinencode und dann umfasst endlich eine Funktion aufrufen. Das Parsen und Auswerten einer Zeichenfolge ist ebenfalls fragwürdig, da alle beabsichtigten Bedeutungen verworfen werden, wenn Code in Text umgewandelt wird. Nehmen wir zum Beispiel, bietet jemand versehentlich eine leere Funktionsnamen - dann ist die Tatsache, dass dieser Code soll eine Funktion aufrufen, wird vollständig durch zufällige Ähnlichkeit der Syntaxen verloren:

julia> fn = "" 
"" 

julia> eval(parse("$fn()")) 
() 

Oops. Das wollten wir gar nicht. In diesem Fall ist das Verhalten ziemlich harmlos, aber es könnte leicht noch viel schlimmer:

julia> fn = "println(\"rm -rf /important/directory\"); time" 
"println(\"rm -rf /important/directory\"); time" 

julia> eval(parse("$fn()")) 
rm -rf /important/directory 
1.448981974309033e9 

Wenn die Eingabe des Benutzers nicht vertrauenswürdig ist, ist dies eine massive Sicherheitslücke. Selbst wenn Sie dem Benutzer vertrauen, ist es dennoch möglich, dass er versehentlich Eingaben liefert, die etwas Unerwartetes und Schlechteres bewirken. Der Name Lookup-Ansatz vermeidet diese Probleme:

julia> getfield(Main, Symbol(fn))() 
ERROR: UndefVarError: println("rm -rf /important/directory"); time not defined 
in eval(::Module, ::Any) at ./boot.jl:225 
in macro expansion at ./REPL.jl:92 [inlined] 
in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:46 

Die Absicht der Suche einen Namen und nannte es dann als eine Funktion explizit ist, statt implizit in der String-Syntax erzeugt, so im schlimmsten Fall eines über ein einen Fehler bekommt seltsamer Name ist undefiniert.

Leistung

Wenn Sie eine dynamisch bestimmte Funktion in einer inneren Schleife zu nennen gehst oder als Teil einer rekursiven Berechnung, werden Sie jedes Mal, wenn Sie die Funktion aufrufen, tun ein getfield Lookup zu vermeiden. In diesem Fall müssen Sie lediglich eine const Bindung an die dynamisch angegebene Funktion erstellen, bevor Sie die iterative/rekursive Prozedur definieren, die sie aufruft.Zum Beispiel:

fn = "deg2rad" # converts angles in degrees to radians 

const f = getfield(Main, Symbol(fn)) 

function fast(n) 
    t = 0.0 
    for i = 1:n 
     t += f(i) 
    end 
    return t 
end 

julia> @time fast(10^6) # once for JIT compilation 
    0.010055 seconds (2.97 k allocations: 142.459 KB) 
8.72665498661791e9 

julia> @time fast(10^6) # now it's fast 
    0.003055 seconds (6 allocations: 192 bytes) 
8.72665498661791e9 

julia> @time fast(10^6) # see? 
    0.002952 seconds (6 allocations: 192 bytes) 
8.72665498661791e9 

Die Bindung f muss für eine optimale Leistung konstant sein, da sonst der Compiler nicht wissen kann, dass Sie nicht f an einer anderen Funktion jederzeit (oder sogar etwas, das nicht ist zu Punkt ändern Funktion), so muss es Code, der f dynamisch auf jeder Schleife Iteration aussieht emittieren - effektiv die gleiche Sache, als ob Sie getfield in der Schleife manuell aufrufen. Hier, da f ist const, der Compiler weiß f kann nicht ändern, so kann es schnellen Code ausstrahlen, der nur die richtige Funktion direkt aufruft. Aber der Compiler kann manchmal noch besser machen als das - in diesem Fall ist es tatsächlich inlines die Umsetzung der deg2rad-Funktion, die durch pi/180 nur eine Multiplikation ist:

julia> @code_llvm fast(100000) 

define double @julia_fast_51089(i64) #0 { 
top: 
    %1 = icmp slt i64 %0, 1 
    br i1 %1, label %L2, label %if.preheader 

if.preheader:          ; preds = %top 
    br label %if 

L2.loopexit:          ; preds = %if 
    br label %L2 

L2:            ; preds = %L2.loopexit, %top 
    %t.0.lcssa = phi double [ 0.000000e+00, %top ], [ %5, %L2.loopexit ] 
    ret double %t.0.lcssa 

if:            ; preds = %if.preheader, %if 
    %t.04 = phi double [ %5, %if ], [ 0.000000e+00, %if.preheader ] 
    %"#temp#.03" = phi i64 [ %2, %if ], [ 1, %if.preheader ] 
    %2 = add i64 %"#temp#.03", 1 
    %3 = sitofp i64 %"#temp#.03" to double 
    %4 = fmul double %3, 0x3F91DF46A2529D39   ; deg2rad(x) = x*(pi/180) 
    %5 = fadd double %t.04, %4 
    %6 = icmp eq i64 %"#temp#.03", %0 
    br i1 %6, label %L2.loopexit, label %if 
} 

Wenn Sie dies tun müssen, um mit vielen verschiedenen dynamisch angegeben

function fast(f,n) 
    t = 0.0 
    for i = 1:n 
     t += f(i) 
    end 
    return t 
end 

julia> @time fast(getfield(Main, Symbol(fn)), 10^6) 
    0.007483 seconds (1.70 k allocations: 76.670 KB) 
8.72665498661791e9 

julia> @time fast(getfield(Main, Symbol(fn)), 10^6) 
    0.002908 seconds (6 allocations: 192 bytes) 
8.72665498661791e9 

Dies erzeugt den gleichen schnellen Code als Single-Argument fast oben, sondern erzeugen: Funktionen und Sie verwenden Julia 0,5 (jede Nacht), dann können Sie auch die Funktion übergeben, um in als Argument aufgerufen werden eine neue Version für jede andere Funktion f, mit dem Sie es anrufen. Wenn Sie Julia 0.4 (stable) verwenden, wird dies funktionieren, aber es wird langsamer und kompiliert keine neue Version für Sie. Wenn Sie bei 0.4 denselben Effekt erzielen möchten, müssen Sie selbst eine benutzerdefinierte Funktion erstellen, indem Sie ein wenig Metaprogrammierung verwenden.

+3

Danke dafür. Ich wusste, dass es einen besseren Weg geben musste. – Jubobs

Verwandte Themen