Julia: automatische Neukompilierung abhängiger Funktionen

Erstellt am 26. Nov. 2011  ·  55Kommentare  ·  Quelle: JuliaLang/julia

Wenn ich eine Funktion d2 vor einer Funktion d1 definiere, die d2 aufruft, und dann d2 ändere, verwendet d1 die alte Definition für d2.
Ich nehme an, das liegt daran, dass alles vorkompiliert ist, aber vielleicht sollte es eine Hinweiswarnung geben? Oder wäre es möglich, die alte Definition durch eine longjmp durch die neue zu ersetzen?
(Am wichtigsten für die REPL, da ich nicht immer eine volle Ladung mache)

julia> function d2()
       a
       end

julia> function d()
         d2()
       end

julia> d()
in d: a not defined

julia> function d2()
       b=2
       end

julia> d()
in d: a not defined

julia> d2
Methods for generic function d2
d2() at prompt:2

julia> function d()
         d2()
       end

julia> d()
2
bug

Hilfreichster Kommentar

Beeindruckend. Was für ein großartiger Moment!

Alle 55 Kommentare

Ich glaube, das liegt daran, dass der Code für d2() generiert wird, wenn die Funktion definiert wird, sodass die Methode d() zu diesem Zeitpunkt aufgelöst wird. Wie Sie bereits bemerkt haben, können Sie d2() neu definieren, wie es ursprünglich definiert wurde, damit es wie erwartet funktioniert. Im Idealfall würden wir alles, was von d() abhängt, automatisch neu definieren, wenn es so geändert wird, aber wir haben keine der Metadaten, die dies zulassen würden. Ich denke, wir könnten eine Warnung zu diesem Verhalten in das Handbuch aufnehmen. Noch besser wäre es, das Problem zu beheben, aber ich denke, es ist etwas schwierig: - /

Ich hatte gehofft, niemand würde das bemerken, aber ich denke, das war nicht realistisch für mich :)

Es macht mehr Spaß als das; Damit können Sie die JIT wirklich bei der Arbeit sehen, da sie erst nach der Ausführung aufgelöst wird:

julia> function f2()
       a
       end

julia> function f1()
       f2()
       end

julia> function f2()
       2
       end

julia> f1()
2

Ich befürchte jedoch, dass dies zu unerwarteten Ergebnissen bei der REPL führen kann, obwohl ich nicht wirklich weiß, wie ich am besten damit umgehen soll. Obwohl ich einen Fehler verwendet habe, um den Unterschied auf dramatische Weise zu zeigen, könnte es auch passieren, wenn ich versuche, den Effekt der Änderung einer inneren Gleichung oder Konstante zu sehen!

Es gibt zwei Arten von Speicherorten, die möglicherweise aktualisiert werden müssen, wenn eine Methode geändert wird:

  • Stellen, an denen die Methode explizit als Funktion aufgerufen wird (call / ret)
  • Stellen, an denen die Methode eingefügt wurde

Ersteres könnte nur durch Ändern des Funktionskörpers aktualisiert werden: Es könnte an Ort und Stelle geändert werden, oder die aufrufenden Sites könnten aktiv aktualisiert werden, um einen neuen Speicherort aufzurufen, oder der alte Funktionskörper könnte durch einen Stub ersetzt werden, der zur neuen Version springt , optional das Patchen der aufrufenden Site. Zum Inlining müssen wir die Anrufer verfolgen, die die Funktion inlined haben, und sie erneut JIT.

Wir können nur Code verwerfen, denn selbst wenn eine Methode keine Funktion enthält, die sie aufruft, kann dies vom Typverhalten abhängen, das sich ändern kann, wenn sich die Methode ändert. Dies ist hauptsächlich für interaktive Fälle gedacht, sodass das Neukompilieren von Code keine große Sache ist.
Erfordert die gleiche Arbeit wie Ausgabe 47.

Schade. Nun, da in erster Linie die Antwort betroffen ist, ist es in Ordnung, langsam das Richtige zu tun. Zur Laufzeit sollte dies so gut wie nie eintreten - warum sollte jemand etwas definieren und es dann außer interaktiv neu definieren? Wenn die Lösung viel Overhead verursacht, sollte sie möglicherweise optional sein und automatisch in der Antwort ausgeführt werden.

Wir müssen dokumentieren, dass dies derzeit nicht definiert ist.

Technisch gesehen ist dies kein Fehler, sondern ein undefiniertes Verhalten. Wenn Sie eine Methode neu definieren, ist das resultierende Verhalten undefiniert. So kann Julia tun, was sie will, auch was sie gerade tut. Das Bereitstellen eines genau definierten Verhaltens bei der Neudefinition von Methoden ist eine Funktion und keine Fehlerbehebung. Ich bin auch nicht davon überzeugt, dass dies ein Problem der Version 1.0 ist, da der Übergang von einem undefinierten Verhalten zu einem genau definierten Verhalten keine bahnbrechende Änderung darstellt. Dies könnte in v1.1 implementiert werden, ohne dass ein gültiger v1.0-Code beschädigt wird.

Greg Clayton von den LLVM / LLDB-Mitarbeitern von Apple war so freundlich, zu dokumentieren, wie (mit lldb-Bibliotheken, einem Teilprojekt von llvm) die erforderlichen Informationen ermittelt werden können, um die Abhängigkeiten einer Binärdatei von den eingebetteten Symbolinformationen (Symbolimporte) zu bestimmen. sowie die von einer Binärdatei exportierten Symbole (erforderlich, um den vollständigen Abhängigkeitsgraphen zu erstellen).

  • Jason

Am 31. März 2012 um 23:02 Uhr schrieb Jason E. Aten:

Liebe LLDB-Enthusiasten,

Ich frage mich, ob ich die lldb-Bibliothek / -Bibliotheken verwenden kann, um den bestimmten Code zu ersetzen, der unter OSX ausgeführt wird und jetzt zwei Symbollisten zurückgibt - ähnlich der Ausgabe von (dyldinfo -lazy_bind -exports)); dh ich muss die Symbole auflisten, die von einem binären gemeinsam genutzten Objekt oder Bundle importiert und exportiert werden.

Meine Hoffnung war, dass ich durch die Verwendung einer lldb-Bibliothek unter OSX denselben Clientcode wie unter Linux verwenden kann. (Die Linux-Version des Codes verwendet derzeit libbfd und libdld, um dasselbe zu tun, aber die spätere Version bekommt wenig Liebe / Wartung).

Ich sehe mich in include / lldb / um, da lldb anscheinend dieselben Informationen (importierte Symbolliste und exportierte Symbolliste für eine Mach-O-Datei) benötigt, um zu funktionieren, aber es ist nicht klar, welche API verwendet werden soll. Alle Vorschläge / Hinweise auf Beispielcode in lldb wären willkommen!

Vielen Dank.
Jason

Falls unklar ist, was dyldinfo macht, hier ein Beispiel: (aber ich brauche nur die Symbolnamen; nicht die Adressen oder Segmente oder Abschnitte):

$ file / tmp / sample_bundle
/ tmp / sample_bundle: Mach-O 64-Bit-Bundle x86_64

$ dyldinfo -lazy_bind -export / tmp / sample_bundle

Lazy Binding Information (aus dem Lazy_Bind-Teil der Dyld-Info):
Segmentabschnitt Adressindex Dylib-Symbol
__DATA __la_symbol_ptr 0x00001030 0x0000 flacher Namespace __mm_pop_chunk
__DATA __la_symbol_ptr 0x00001038 0x0015 flacher Namespace _dh_define
Exportinformationen (von trie):
0x000008A0 _C_ipair
0x00000920 _init_ipair
0x00000BC0 _C_iprot
0x00000C40 _C_ipi2
0x00000CC0 _C_ipi1
0x00001040 _K_ipair_R43808f40
0x00001160 _K_ipi1_R5cb4475d
0x00001260 _K_ipi2_R5cb4475d
0x00001360 _K_iprot_Rfc8fe739
0x00001460 _majver_ipair
0x00001464 _minver_ipair

Am Montag, den 2. April 2012 um 15:13 Uhr schrieb Greg Clayton [email protected] :

Yes you can do this with LLDB. If you load a binary and dump its symbol table, you will see the information you want. For symbols that are lazily bound, you can look for "Trampoline" symbols:

cd lldb/test/lang/objc/foundation
make
lldb a.out
(lldb) target modules dump symtab a.out
Symtab, file = .../lldb/test/lang/objc/foundation/a.out, num_symbols = 54:
              Debug symbol
              |Synthetic symbol
              ||Externally Visible
              |||
Index   UserID DSX Type         File Address/Value Load Address       Size               Flags      Name
------- ------ --- ------------ ------------------ ------------------ ------------------ ---------- ----------------------------------
[    0]      0 D   SourceFile   0x0000000000000000                    Sibling -> [   15] 0x00640000 /Volumes/work/gclayton/Documents/src/lldb/test/lang/objc/foundation/main.m
[    1]      2 D   ObjectFile   0x000000004f79f1ca                    0x0000000000000000 0x00660001 /Volumes/work/gclayton/Documents/src/lldb/test/lang/objc/foundation/main.o
[    2]      4 D   Code         0x00000001000010f0                    0x00000000000000c0 0x000e0000 -[MyString initWithNSString:]
[    3]      8 D   Code         0x00000001000011b0                    0x0000000000000090 0x000e0000 -[MyString dealloc]
[    4]     12 D   Code         0x0000000100001240                    0x00000000000000a0 0x000e0000 -[MyString description]
[    5]     16 D   Code         0x00000001000012e0                    0x0000000000000020 0x000e0000 -[MyString descriptionPauses]
[    6]     20 D   Code         0x0000000100001300                    0x0000000000000030 0x000e0000 -[MyString setDescriptionPauses:]
[    7]     24 D   Code         0x0000000100001330                    0x0000000000000030 0x000e0000 -[MyString str_property]
[    8]     28 D   Code         0x0000000100001360                    0x0000000000000050 0x000e0000 -[MyString setStr_property:]
[    9]     32 D   Code         0x00000001000013b0                    0x0000000000000040 0x000f0000 Test_Selector
[   10]     36 D   Code         0x00000001000013f0                    0x0000000000000130 0x000f0000 Test_NSString
[   11]     40 D   Code         0x0000000100001520                    0x0000000000000120 0x000f0000 Test_MyString
[   12]     44 D   Code         0x0000000100001640                    0x00000000000001b0 0x000f0000 Test_NSArray
[   13]     48 D   Code         0x00000001000017f0                    0x00000000000000e1 0x000f0000 main
[   14]     56 D X Data         0x0000000100002680                    0x0000000000000000 0x00200000 my_global_str
[   15]     58 D   SourceFile   0x0000000000000000                    Sibling -> [   19] 0x00640000 /Volumes/work/gclayton/Documents/src/lldb/test/lang/objc/foundation/my-base.m
[   16]     60 D   ObjectFile   0x000000004f79f1ca                    0x0000000000000000 0x00660001 /Volumes/work/gclayton/Documents/src/lldb/test/lang/objc/foundation/my-base.o
[   17]     62 D   Code         0x00000001000018e0                    0x0000000000000020 0x000e0000 -[MyBase propertyMovesThings]
[   18]     66 D   Code         0x0000000100001900                    0x000000000000001f 0x000e0000 -[MyBase setPropertyMovesThings:]
[   19]     82     Data         0x0000000100002000                    0x0000000000000460 0x000e0000 pvars
[   20]     83     ObjCIVar     0x0000000100002518                    0x0000000000000148 0x001e0000 MyBase.propertyMovesThings
[   21]     84   X Data         0x0000000100002660                    0x0000000000000008 0x000f0000 NXArgc
[   22]     85   X Data         0x0000000100002668                    0x0000000000000008 0x000f0000 NXArgv
[   23]     86   X ObjCClass    0x00000001000024d8                    0x0000000000000028 0x000f0000 MyBase
[   24]     87   X ObjCClass    0x0000000100002460                    0x0000000000000028 0x000f0000 MyString
[   25]     88   X ObjCIVar     0x0000000100002510                    0x0000000000000008 0x000f0000 MyString._desc_pauses
[   26]     89   X ObjCIVar     0x0000000100002508                    0x0000000000000008 0x000f0000 MyString.date
[   27]     90   X ObjCIVar     0x0000000100002500                    0x0000000000000008 0x000f0000 MyString.str
[   28]     91   X ObjCMetaClass 0x00000001000024b0                    0x0000000000000028 0x000f0000 MyBase
[   29]     92   X ObjCMetaClass 0x0000000100002488                    0x0000000000000028 0x000f0000 MyString
[   30]     97   X Data         0x0000000100002678                    0x0000000000000008 0x000f0000 __progname
[   31]     98   X Data         0x0000000100000000                    0x00000000000010b0 0x000f0010 _mh_execute_header
[   32]     99   X Data         0x0000000100002670                    0x0000000000000008 0x000f0000 environ
[   33]    101   X Data         0x0000000100002680                    0x0000000000000000 0x000f0000 my_global_str
[   34]    102   X Code         0x00000001000010b0                    0x0000000000000040 0x000f0000 start
[   35]    103     Trampoline   0x0000000100001938                    0x0000000000000006 0x00010200 NSLog
[   36]    104   X Undefined    0x0000000000000000                    0x0000000000000000 0x00010400 OBJC_CLASS_$_NSArray
[   37]    105   X Undefined    0x0000000000000000                    0x0000000000000000 0x00010200 OBJC_CLASS_$_NSAutoreleasePool
[   38]    106   X Undefined    0x0000000000000000                    0x0000000000000000 0x00010400 OBJC_CLASS_$_NSDate
[   39]    107   X Undefined    0x0000000000000000                    0x0000000000000000 0x00010400 OBJC_CLASS_$_NSObject
[   40]    108   X Undefined    0x0000000000000000                    0x0000000000000000 0x00010200 OBJC_CLASS_$_NSString
[   41]    109   X Undefined    0x0000000000000000                    0x0000000000000000 0x00010400 OBJC_METACLASS_$_NSObject
[   42]    110   X Undefined    0x0000000000000000                    0x0000000000000000 0x00010400 __CFConstantStringClassReference
[   43]    111   X Undefined    0x0000000000000000                    0x0000000000000000 0x00010100 _objc_empty_cache
[   44]    112   X Undefined    0x0000000000000000                    0x0000000000000000 0x00010100 _objc_empty_vtable
[   45]    113     Trampoline   0x000000010000193e                    0x0000000000000006 0x00010300 exit
[   46]    114     Trampoline   0x0000000100001920                    0x0000000000000006 0x00010100 objc_getProperty
[   47]    115     Trampoline   0x0000000100001926                    0x0000000000000006 0x00010100 objc_msgSend
[   48]    116     Trampoline   0x000000010000192c                    0x0000000000000006 0x00010100 objc_msgSendSuper2
[   49]    117   X Undefined    0x0000000000000000                    0x0000000000000000 0x00010100 objc_msgSend_fixup
[   50]    118     Trampoline   0x0000000100001932                    0x0000000000000006 0x00010100 objc_setProperty
[   51]    119     Trampoline   0x0000000100001944                    0x0000000000000006 0x00010300 printf
[   52]    120     Trampoline   0x000000010000194a                    0x0000000000000006 0x00010300 usleep
[   53]    121   X Undefined    0x0000000000000000                    0x0000000000000000 0x00010300 dyld_stub_binder
(lldb)


All lazily bound symbols will have type Trampoline:

[   45]    113     Trampoline   0x000000010000193e                    0x0000000000000006 0x00010300 exit
[   46]    114     Trampoline   0x0000000100001920                    0x0000000000000006 0x00010100 objc_getProperty
[   47]    115     Trampoline   0x0000000100001926                    0x0000000000000006 0x00010100 objc_msgSend
[   48]    116     Trampoline   0x000000010000192c                    0x0000000000000006 0x00010100 objc_msgSendSuper2
[   50]    118     Trampoline   0x0000000100001932                    0x0000000000000006 0x00010100 objc_setProperty
[   51]    119     Trampoline   0x0000000100001944                    0x0000000000000006 0x00010300 printf
[   52]    120     Trampoline   0x000000010000194a                    0x0000000000000006 0x00010300 usleep

The other symbols that are exernal are marked with an "X" (which is a boolean flag on each symbol).

The symbols can be accessed via the SBModule:

   size_t
   SBModule::GetNumSymbols ();

   lldb::SBSymbol
   SBModule::GetSymbolAtIndex (size_t idx);

And then you can get the symbol type from each SBSymbol:

   SymbolType
   SBSymbol::GetType ();


I just added the ability to see if a symbol is externally visible:
% svn commit
Sending        include/lldb/API/SBSymbol.h
Sending        scripts/Python/interface/SBSymbol.i
Sending        source/API/SBSymbol.cpp
Transmitting file data ...
Committed revision 153893.


   bool
   SBSymbol::IsExternal();


So your flow should be:

SBDebugger::Initialize();
SBDebugger debugger(SBDebugger::Create());
SBTarget target (debugger.CreateTarget (const char *filename,
                                       const char *target_triple,
                                       const char *platform_name,
                                       bool add_dependent_modules,
                                       lldb::SBError& error));

SBFileSpec exe_file_spec (filename);
SBModule exe_module (target.FindModule(exe_file_spec));
if (exe_module.IsValid()
{
   const size_t num_symbols = exe_module. GetNumSymbols();
   for (size_t i=0; i<num_symbols; ++i)
   {
       SBSymbol symbol (exe_module. GetSymbolAtIndex(i));
       if (symbol.IsExternal())
       {
       }

       if (symbol.GetType() == lldb::eSymbolTypeTrampoline)
       {
       }
   }
}




> _______________________________________________
> lldb-dev mailing list
> [email protected]
> http://lists.cs.uiuc.edu/mailman/listinfo/lldb-dev

Am Montag, den 2. April 2012 um 16:05 Uhr schrieb Greg Clayton [email protected] :

A quick clarification on the args to CreateTarget:

"filename" is just a full path to the local object file you want to observer. "target_triple" is your <arch>-<vendor>-<os>, or something like "x86_64-apple-darwin" or "i386-pc-linux" and can be NULL if the file is only a single architecture. "platform_name" can be NULL. "add_dependent_modules" should be false, since you are only interested in seeing the one object file itself, an SBError instance  should be created and passed in.

Diskussion der Entwicklerliste hier: https://groups.google.com/forum/?fromgroups=#!topic/julia -dev / snnGKJul4vg.

Dies ist ein erstaunlich alter Thread, aber mir wurde klar, dass ich möglicherweise mit meinem Codespeed-Benchmarking-Code an den Rändern dieses Problems flirte. Ich habe wiederholt Core.include() Dateien, die zwei Funktionen enthalten, listTests() und runTests() . Ich rufe dann einfach listTests() und runTests() , definiere sie neu und rufe sie jedes Mal auf, wenn ich eine neue Benchmark-Datei lade. Da ich Funktionen der zweiten Ebene nicht neu definiere, glaube ich nicht, dass ich auf die hier aufgeführten Probleme stoßen werde, aber ist die Neudefinition solcher Dinge ein schlechter Weg, um mehrere Dateien mit derselben "API" zu laden?

Es könnte besser sein, sie wiederholt in ein Modul aufzunehmen, aber mir ist klar, dass dies auf das anonyme Modulproblem stößt, das auch auf der Entwicklerliste aufgeführt ist. In diesem Fall kann dies jedoch in Ordnung sein, da Sie dasselbe Modul einfach mehrmals definieren und die ausgegebene Warnung ignorieren können.

Vielleicht ein alter Thread, aber so relevant wie immer.
Möglicherweise besteht ein anderer Ansatz darin, evalfile und die Datei ein Tupel von Funktionen "zurückgeben" zu lassen. Was ist das anonyme Modulproblem?

Hat jemand zusätzliche Gedanken zu Implementierungsstrategien? Ich dachte daran, ein einfaches Schema zu codieren, das ein invertiertes Aufrufdiagramm verwaltet, das aktualisiert wird, wenn eine neue Methode erstellt wird (basierend auf dem nicht optimierten AST). Das Diagramm wird dann durchlaufen, wenn eine Methode neu definiert wird, wobei die kompilierte Version aller Nachkommen der neu definierten Funktion entfernt wird.

Das ist so vernünftig wie jeder andere Versuch. Wenn Sie sich dazu motiviert fühlen, würde ich sagen, machen Sie weiter und lassen Sie uns sehen, was passiert!

Was passiert, wenn die Ausführung des Angerufenen eine Neukompilierung des Anrufers auslöst?

Ich frage, weil ich verspätet erkannt habe, dass die Lösung dieses Problems interessante Konsequenzen haben kann, die über die Benutzererfahrung in der REPL hinausgehen: Sie könnte die endgültige Antwort auf ein Metaprogrammierungsproblem ermöglichen, nämlich die Erstellung effizienter "inszenierter" Funktionen. Als Hintergrund für diejenigen, die es vielleicht nicht wissen: Einige Funktionen werden derzeit durch Metaprogrammierung implementiert, insbesondere diejenigen, für die ein Aspekt des Algorithmus auf nicht triviale Weise von den Arten der Eingaben abhängt - das kanonische Beispiel ist eines, bei dem die Zahl Die Anzahl der Schleifen im Funktionskörper entspricht der Dimensionalität eines Arrays. Die Art und Weise, wie wir normalerweise damit umgehen, besteht darin, die Funktion explizit für z. B. die Dimensionen 1 und 2 zu definieren und dann einen Wrapper zu haben, der ungefähr so ​​aussieht:

_method_cache = Dict()
function myfunction(A::AbstractArray)
    N = ndims(A)
    if !haskey(_method_cache, N)
        func = eval(<an expression that generates the function definition for N dimensions>)
        _method_cache[N] = func
    else
        func = _method_cache[N]
    end
    return func(A)
end

Wenn myfunction zum ersten Mal für ein 4-dimensionales Array ausgeführt wird, definiert es zunächst eine für 4 Dimensionen spezifische Version, fügt sie _method_cache und wertet dann die neue Funktion für die Eingabe aus. Bei zukünftigen Aufrufen von myfunction mit einem 4-dimensionalen Array wird lediglich die Definition aus dem Wörterbuch abgerufen und ausgewertet. _method_cache ist eine Art "Schattenmethodentabelle" parallel zu Julias eigener interner Methodentabelle, die nur für diese bestimmte Funktion verwendet wird. (Um es privat zu halten, wird es normalerweise von einem let eingekapselt.)

Während der Ansatz in diesem Beispiel für Funktionskörper gut funktioniert, deren Ausführung einige Zeit in Anspruch nimmt, eignet er sich nicht sehr gut für Funktionen, die in kürzerer Zeit als für eine Wörterbuchsuche erforderlich ausgeführt werden, und er ist besonders schlecht für Funktionen, die Sie sein möchten inline können.

Ein besserer Weg, dies zu tun, könnte der folgende sein:

function myfunction(A::AbstractArray)
    bodyexpr = <an expression for the body of the function specific for N dimensions>
    f = <strong i="17">@eval</strong> begin
        function myfunction(A::$(typeof(A)))
            $bodyexpr
        end
    end
    return f(A)
end

Hier erzeugt die Ausführung von myfunction eine spezifischere Version von myfunction für diese bestimmten Eingabetypen. Code, der kompiliert wird, nachdem diese neue Definition verfügbar ist, verwendet gegebenenfalls diese neue Version. Die obige Version wird zum generischen "Fallback" für Fälle, in denen Sie noch nichts Spezifischeres definiert haben. Folglich wäre dies eine Möglichkeit, abgestufte Funktionen zu erstellen, die Julias eigene interne Methodentabellenmechanismen ausnutzen und es daher ermöglichen würden, effizienten Code zu generieren.

Derzeit funktioniert dies aus einem entscheidenden Grund nicht: Was auch immer myfunction wurde, wurde bereits kompiliert, sodass es nichts über die neue Definition weiß. Infolgedessen generiert dieser bestimmte Anrufer immer eine neue Version von myfunction mit eval , was sehr langsam sein wird.

Der Trick wäre also, den Aufrufer neu zu kompilieren, aber beachten Sie, dass dies geschehen muss, während es sich mitten in der Ausführung befindet. Was wird passieren?

Siehe auch # 5395.

Es gibt ein verwandtes Problem mit abstrakten Typen, das eigentlich keine Neudefinition der Methode beinhaltet, aber eine ähnliche Behandlung erfordert. Erwägen:

abstract A
immutable B <: A; end
immutable C <: A; end

g(x::Vector{A}) = f(x[1])

f(::B) = 1
g(A[B()])

f(::C) = 0.5
g(A[C()])

Die letzte Zeile gibt 4602678819172646912 . Wir müssen den Code für g wegwerfen, da die Typinferenz für f(::A) nicht mehr gültig ist.

Ja, das ist ganz klar. Wir wissen, dass das Ersetzen von Methoden nur ein Sonderfall ist. Es geht aber immer um neue Methodendefinitionen.

Es scheint, dass die aktuelle Situation seitdem irgendwie mehrdeutig sein kann

f() = x()
x() = 1
println(f())
x() = 2
println(f())

gibt

1
1

während

g() = y()
precompile(g, ())
y() = 1
println(g())
y() = 2
println(g())

gibt

1
2

Es scheint, dass der spätere Fall als Problemumgehung verwendet werden kann, um die Neukompilierung von Anrufern zu emulieren (ich weiß, dass es sich nicht wirklich um eine Neukompilierung handelt).

Ich habe das Gefühl, dass ich etwas Dummes sagen werde, aber konnte dieses Problem nicht mit einer einzigen Indirektionsebene beseitigt werden? Anstatt eine Funktionsadresse fest zu codieren, suchen Sie sie an einem festen Ort und rufen Sie sie auf. Hätte dies nicht eine ähnliche Leistung wie virtuelle C ++ - oder Java-Funktionen? Sie können dann Funktionen mit Anmerkungen versehen, um eine statische oder dynamische Adresse zu erhalten, und eine Warnung / einen Fehler erhalten, wenn Sie versuchen, eine Funktion mit einer statischen Adresse neu zu definieren. Es könnte sogar einen Schalter geben, um das Standardfunktionsverhalten einzustellen. Ich bin neu in der Sprache und mit der Codebasis völlig unbekannt, aber wenn dies machbar klingt, könnte ich es wahrscheinlich versuchen.

@ omer4d Ein Problem mit dieser Idee ist, dass Julia viele kleinere Funktionen inline nachzuschlagen .

Es könnten nur diejenigen mit statischen Adressen inline sein, wie es C ++ tut. Dies sollte keine Unannehmlichkeiten verursachen.
Personen, die sich nicht für interaktive Entwicklung interessieren, sind überhaupt nicht betroffen, da standardmäßig statische Adressen für alle Funktionen verwendet werden.
Personen, die sich nicht für die Leistung interessieren, aber eine interaktive Entwicklung wünschen, können mithilfe eines Schalters Funktionsadressen standardmäßig dynamisch gestalten.
Personen, die sowohl Interaktivität als auch Leistung wünschen, können einfach die relevanten Funktionen mit Anmerkungen versehen, was nicht viel Arbeit bedeuten sollte, da Inlining nur für kurze Funktionen auf niedriger Ebene relevant ist, die häufig aufgerufen werden.
Möglicherweise könnte das Standardverhalten sogar pro Modul festgelegt werden.

Ich glaube, es gibt mehrere Gründe, warum dieser Ansatz nicht praktikabel ist. Zuerst,
Anmerkungen sind wirklich ein Ärgernis, besonders wenn sie zum Hacken benötigt werden
um Implementierungsdetails wie dieses. Wenn zur Lösung Anmerkungen verwendet werden 2
oder 3 Probleme, sie beginnen sich erheblich zu häufen. Zweitens gibt es keine
gute Möglichkeit, die richtige Anmerkung für eine Funktion auszuwählen. Es gibt kein
Verbindung zwischen, ob Sie etwas Inline wollen und ob Sie es sind
es interaktiv entwickeln. Drittens geben Sie Abzüge in andere Funktionen ein
Möglicherweise müssen Änderungen vorgenommen werden, damit nicht nur Websites aufgerufen werden. In der Tat sind wir derzeit
Ableiten weniger Typinformationen, als wir möglicherweise könnten, um Dinge zu machen
sicher angesichts dieses Problems.

Nun, als jemand, der den größten Teil seiner Arbeit in Sprachen mit Dutzenden von Qualifikationsschlüsselwörtern und wiederholter manueller Neukompilierung erledigt, klingt es nicht so schlecht, gelegentlich ein oder zwei Funktionen zu qualifizieren und nach einigen Zyklen interaktiver Entwicklung neu zu kompilieren, aber das bin nur ich . Ich weiß jedoch nicht genug, um den dritten Punkt anzusprechen, also werde ich zurücktreten. Ich finde es bedauerlich, dass eine solche Lösung nicht realisierbar ist, da eine Neukompilierung bedeutet, dass die Wurzel des Aufrufbaums einer modifizierten Funktion, die ein Spiel oder eine Animationsschleife enthält, beendet und neu gestartet werden muss. = ((

@ JeffBezanson Ich würde mich wahrscheinlich am meisten für den einfachen Fall interessieren, zumindest zuerst. Es ist mir egal, wie viel laufender Code aktualisiert wird, im Gegensatz zu Dingen, auf die über eval zugegriffen wird (z. B. an der REPL-Eingabeaufforderung eingegeben). Schließlich wird es auch wichtig sein, zB die richtige Version von display , wenn sie neu definiert wird. Aber ich glaube, das sollte uns schnell näher an inkrementelle Kompilierungen bringen.

Ich war ein wenig überrascht, dieses Problem zu kennen, nicht weil ich denke, dass es einfach ist, damit umzugehen, sondern weil das Folgende wie erwartet funktioniert.

julia> f(a, b) = a + b
f (generic function with 1 method)

julia> g(args...) = f(args...)
g (generic function with 1 method)

julia> g(1, 2)
3

julia> f(a::Int, b::Int) = a - b
f (generic function with 2 methods)

julia> g(1, 2)
-1

Bearbeiten: Eigentlich sieht dies wie ein Sonderfall für Vararg-Eingaben aus ..... Bedeutet dies, dass eine Vararg-Funktion bei jedem Aufruf neu kompiliert wird? Oder ist es klug genug, nur bei Bedarf neu zu kompilieren? Und ist es möglich, andere Funktionen gleich zu behandeln?

julia> f(a, b) = a + b
f (generic function with 1 method)

julia> g(a, b) = f(a, b)
g (generic function with 1 method)

julia> g(1, 2)
3

julia> f(a::Int, b::Int) = a - b
f (generic function with 2 methods)

julia> g(1, 2)
3

Tatsächlich gibt die Funktion das richtige Ergebnis zurück, aber @code_typed gibt ein falsches Ergebnis zurück .....

julia> f(a, b) = a + b
f (generic function with 1 method)

julia> g(args...) = f(args...)
g (generic function with 1 method)

julia> g(1, 2)
3

julia> <strong i="7">@code_typed</strong> g(1, 2)
1-element Array{Any,1}:
 :($(Expr(:lambda, Any[:(args::(top(apply_type))(Vararg,Any)::Any::Any)], Any[Any[],Any[Any[:args,(Int64,Int64),0]],Any[]], :(begin  # none, line 1:
        return (top(box))(Int64,(top(add_int))((top(tupleref))(args::(Int64,Int64),1)::Int64,(top(tupleref))(args::(Int64,Int64),2)::Int64))
    end::Int64))))

julia> f(a::Int, b::Int) = a - b
f (generic function with 2 methods)

julia> g(1, 2)
-1

julia> <strong i="8">@code_typed</strong> g(1, 2)
1-element Array{Any,1}:
 :($(Expr(:lambda, Any[:(args::(top(apply_type))(Vararg,Any)::Any::Any)], Any[Any[],Any[Any[:args,(Int64,Int64),0]],Any[]], :(begin  # none, line 1:
        return (top(box))(Int64,(top(add_int))((top(tupleref))(args::(Int64,Int64),1)::Int64,(top(tupleref))(args::(Int64,Int64),2)::Int64))
    end::Int64))))

Und

julia> f(a, b) = a + b
f (generic function with 1 method)

julia> g(args...) = f(args...)
g (generic function with 1 method)

julia> g(1, 2)
3

julia> f(a::Int, b::Int) = a - b
f (generic function with 2 methods)

julia> g(1, 2)
-1

julia> <strong i="12">@code_typed</strong> g(1, 2)
1-element Array{Any,1}:
 :($(Expr(:lambda, Any[:(args::(top(apply_type))(Vararg,Any)::Any::Any)], Any[Any[],Any[Any[:args,(Int64,Int64),0]],Any[]], :(begin  # none, line 1:
        return (top(box))(Int64,(top(sub_int))((top(tupleref))(args::(Int64,Int64),1)::Int64,(top(tupleref))(args::(Int64,Int64),2)::Int64))
    end::Int64))))

Würde es funktionieren, Methoden von Funktionen (oder nur die Funktionstypen / Objekte selbst) mit einer Nummer zu indizieren, die sich jedes Mal ändert, wenn etwas zugewiesen wird, und so prüfen zu können, ob die mit der kompilierten (Aufrufer-) Funktion gespeicherte Nummer (dh unter Verwendung der neuen Struktur) von Funktionen, Speichern dieser Nummern als Feld usw.) stimmt immer noch mit der überein, die "aktuell" für die Funktion ist, die inline (aufgerufen) wurde?
(natürlich ohne große Leistungsstrafe)
Und im Falle einer Änderung sollten Sie die neue Version kompilieren oder durch einen expliziten Aufruf vervollständigen.

Dies würde die Auswirkungen auf die Leistung auf die Laufzeit verlagern (und das überall), was nicht wünschenswert ist. Was benötigt wird, ist das Verfolgen des Netzes von Abhängigkeiten und das Neukompilieren aller Elemente, die möglicherweise betroffen sind, wenn eine Methode teilweise neu definiert wird. Es ist wahrscheinlich machbar, es ist nur ein massiver Schmerz.

Ich weiß nicht, ob sich die Lösung für dieses Problem leicht auf Makros verallgemeinern lässt, aber wenn ja, wäre das eine schöne Sache.

Man sollte also eher umgekehrt vorgehen und alle Methoden speichern, die die Funktion eingebunden haben, und sie bei Änderungen neu kompilieren. (natürlich auf aktuelle Ausführung prüfen usw.)

Die schwierigen Probleme hier sind:

  • Wenn Sie sagen, dass der Code ungültig ist, finden Sie alle Stellen heraus, an denen alte Funktionen verwendet werden (im generierten Code). Dies bedeutet wahrscheinlich, dass der Code, der Funktionen aufruft, die sich geändert haben, für die sich jedoch die Aufrufkonvention und der Rückgabetyp nicht geändert haben, verschoben werden kann
  • schwieriger: Was passiert, wenn eine andere Aufgabe ausgeführt wird, während Sie eine neue Methode definieren? Welchen Zustand sieht es? Sie möchten dies ohne Austausch auf dem Stapel lösen, da dies sehr schwer effizient zu implementieren ist. Wir denken, wir haben eine Lösung für das, was die Leistung nicht beeinträchtigt, und ich denke,

Während an einer Lösung für das Problem gearbeitet wird, bin ich gespannt, ob es möglich und eine gute Idee ist, jeder gesetzten Funktion ein Flag hinzuzufügen, wenn (und nur wenn) die angegebene Funktion auf eine der oberen Arten verwendet wurde ( Inlining usw.), um eine Warnung zu erzeugen, dass eine Inlined-Methode neu definiert wurde. (Ähnlich wie die mehrdeutige Warnung funktioniert)
Wenn es keinen besseren Weg gibt, das Flag zu speichern, dann vielleicht noch einmal, indem Sie die neue Struktur der Methoden / Funktionen in Julia 0.5 verwenden

Es ist jetzt noch einfacher, dies in der Repl zu treffen und dieses Verhalten sollte vielleicht für die Karte dokumentiert werden?

julia> function f(x)
         1
       end 
f (generic function with 1 method)

julia> map(f, 1:10)
10-element Array{Int64,1}:
 1
 1
 1
 1
 1
 1
 1
 1
 1
 1

julia> function f(x)
         2
       end
WARNING: Method definition f(Any) in module Main at REPL[9]:2 overwritten at REPL[11]:2.
f (generic function with 1 method)

julia> map(f, 1:10)
10-element Array{Int64,1}:
 1
 1
 1
 1
 1
 1
 1
 1
 1
 1

Das ist immer noch nur Vanille # 265. Ich denke, Sie werden es zu schätzen wissen, dass dies bald in v0.6-dev behoben wird :)

@vtjnash gibt es eine Laufzeitleistung oder einen Speicherkompromiss?

Es benötigt etwas Speicher (140 MB -> 170 MB), sollte jedoch keinen großen Einfluss auf die Leistung (Kompilierung oder Laufzeit) haben. Und ich habe noch nicht wirklich viel Optimierung versucht.

Die bisherigen Demos machen Spaß:

julia> f() = 1
f (generic function with 1 method)

julia> function g(x)
    <strong i="7">@eval</strong> f() = $x # this is the correct way to write `global f() = x` (which should be a syntax error, but isn't currently)
    return @eval(f)() # use `eval` to hide the value from optimization / inlining, but the call is not inside eval
end
g (generic function with 1 method)

julia> g(2)
WARNING: Method definition f() in module Main at REPL[1]:1 overwritten at REPL[2]:2.
1

julia> g(3)
WARNING: Method definition f() in module Main at REPL[2]:2 overwritten at REPL[2]:2.
2

julia> g(4)
WARNING: Method definition f() in module Main at REPL[2]:2 overwritten at REPL[2]:2.
3

Liegt der Grund, der nicht 2, 3, 4 zurückgibt, an der Reihenfolge, in der die Kompilierung von g gegen die Ausführung erfolgt, die f neu definiert?

Das ist ungefähr richtig. Beachten Sie, dass die Sprachsemantik keine Kompilierung definiert. Daher ist es korrekter zu sagen, dass die Reihenfolge, in der g interpretiert wird, im Vergleich zu der Zeit, in der die Neudefinition von f für den Interpreter sichtbar wird

Okay, hier ist eine weitere lustige kleine Demo, die das + -Primitiv neu definiert, um zu zählen, wie viel es verwendet wird:

julia> add_ctr = UInt(0)
0x0000000000000000

julia> Base.:+(a::Int, b::Int) = (global add_ctr += 1; Core.Intrinsics.add_int(a, b))

julia> add_ctr
0x0000000000000016

julia> last = 0;

julia> println(Int(add_ctr - last)); last = add_ctr;
287

julia> println(Int(add_ctr - last)); last = add_ctr;
17

Dieser betrügt: Ich glaube nicht, dass + von Base verwendet wird, also gab es nichts, was neu kompiliert werden konnte. Definieren Sie eine echte Funktion wie svd , von der Sie wissen, dass sie mindestens 100 Mal aufgerufen werden muss, bevor die REPL angezeigt wird. Dann werden wir beeindruckt sein.

Die Möglichkeiten zur Live-Instrumentierung von Code für die Profilerstellung / Nachverfolgung haben mich umgehauen.

Tatsächlich. Und hier wollte ich gerade eine neue Tastatur kaufen, weil die Sequenz Ctrl-D; julia<Enter> fast abgenutzt ist. Sieht so aus, als müsste ich das nicht.

Es mag ein bisschen geheimnisvoll sein, die Versionshinweise einzufügen, aber sollte irgendwo angemerkt werden, dass Verständnis jetzt Syntax für collect -ing a Generator (im Gegensatz zu etwas, das möglicherweise auf der Parsing-Ebene liegt in 0.4? - @which funktioniert dort nicht)? Dieses Beispiel, ähnlich wie das obige von vchuravy, ist ein bisschen gotcha, selbst wenn man bedenkt, dass jede Funktion jetzt ein Typ in 0,5 ist:

               _
   _       _ _(_)_     |  A fresh approach to technical computing
  (_)     | (_) (_)    |  Documentation: http://docs.julialang.org
   _ _   _| |_  __ _   |  Type "?help" for help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 0.5.0-rc3+0 (2016-08-22 23:43 UTC)
 _/ |\__'_|_|_|\__'_|  |
|__/                   |  x86_64-linux-gnu

julia> f(x) = 1
f (generic function with 1 method)

julia> [f(x) for x in 1:5]
5-element Array{Int64,1}:
 1
 1
 1
 1
 1

julia> f(x) = 2
WARNING: Method definition f(Any) in module Main at REPL[1]:1 overwritten at REPL[3]:1.
f (generic function with 1 method)

julia> [f(x) for x in 1:5]
5-element Array{Int64,1}:
 1
 1
 1
 1
 1

julia> <strong i="9">@which</strong> [f(x) for x in 1:5]
collect(itr::Base.Generator) at array.jl:295

nicht ganz richtig?

nützlich für Fix # 265

Ist das wirklich behoben 😲

Ist geplant, diese Lösung noch auf 0.5.x zurückportieren zu lassen?

Nein.

@ rapus95 , dies ist eine sehr invasive Änderung, und macht , kann dies

Ich bin so froh, Fortschritte in diesem Bereich zu sehen! Es wäre erstaunlich, wenn dies dem Compiler auch ermöglichen würde, den in https://groups.google.com/forum/#!topic/julia -users / OBs0fmNmjCU beschriebenen Fall zu optimieren.

Beeindruckend. Was für ein großartiger Moment!

Hallo Leute!

Gibt es eine Möglichkeit, diesen Fix auf 0,5 Serien zurück zu portieren? Oder funktioniert es nur in 0,6?

Dies ist eine bahnbrechende Änderung und wird nur in Version 0.6 verfügbar sein

Es ist schwer zu übertreiben, wie großartig es ist, dieses Problem zu beheben. Alte Gewohnheiten sterben schwer, so dass ich Julia manchmal unnötig neu starte und / oder neu aufbaue, aber wenn ich mich erinnere, ist es ein kompletter Game-Changer, um Probleme zu beheben.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

felixrehren picture felixrehren  ·  3Kommentare

wilburtownsend picture wilburtownsend  ·  3Kommentare

yurivish picture yurivish  ·  3Kommentare

omus picture omus  ·  3Kommentare

StefanKarpinski picture StefanKarpinski  ·  3Kommentare