Julia: recompilation automatique des fonctions dépendantes

Créé le 26 nov. 2011  ·  55Commentaires  ·  Source: JuliaLang/julia

si je définis une fonction d2 avant une fonction d1 qui appelle d2 puis changez d2, d1 utilise l'ancienne définition pour d2.
Je suppose que c'est parce que tout est précompilé, mais peut-être qu'il devrait y avoir une note d'avertissement à ce sujet? Ou serait-il possible de remplacer l'ancienne définition par un longjmp par la nouvelle?
(Surtout important pour le REPL, car je ne fais pas toujours une charge complète)

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

Commentaire le plus utile

Sensationnel. Quel beau moment!

Tous les 55 commentaires

Je crois que c'est parce que le code pour d2() est généré lorsque la fonction est définie, de sorte que la méthode d() est résolue à ce moment-là. Comme vous l'avez noté, vous pouvez redéfinir d2() tel qu'il a été initialement défini pour le faire fonctionner comme prévu. Idéalement, nous redéfinirions automatiquement tout ce qui dépend de d() quand il est modifié comme ça, mais nous n'avons aucune des métadonnées en place qui permettrait cela. Je suppose que nous pourrions mettre un avertissement à propos de ce comportement dans le manuel. Mieux encore serait de le réparer, mais je pense que c'est un peu difficile à faire: - /

J'espérais que personne ne remarquerait cela, mais je suppose que ce n'était pas réaliste de ma part :)

C'est plus amusant que ça; il vous permet vraiment de voir le JIT au travail, car il n'est pas résolu avant l'exécution:

julia> function f2()
       a
       end

julia> function f1()
       f2()
       end

julia> function f2()
       2
       end

julia> f1()
2

Cependant, je crains que cela puisse conduire à des résultats inattendus au REPL, même si je ne connais pas vraiment la meilleure façon de gérer cela. Bien que j'aie utilisé une erreur pour montrer la différence de façon dramatique, cela pourrait aussi arriver si j'essayais de voir l'effet du changement d'une équation interne ou d'une constante!

Il existe deux types d'emplacements qui peuvent avoir besoin d'être mis à jour lorsqu'une méthode est modifiée:

  • endroits où la méthode est appelée explicitement en tant que fonction (call / ret)
  • les endroits où la méthode a été intégrée

Le premier pourrait être mis à jour simplement en changeant le corps de la fonction: il pourrait être modifié sur place, ou les sites appelants pourraient être activement mis à jour pour appeler un nouvel emplacement, ou l'ancien corps de la fonction pourrait être remplacé par un stub qui saute vers la nouvelle version , éventuellement patcher le site appelant. Pour l'inlining, nous devons suivre les appelants qui ont incorporé la fonction et les re-JIT.

En réalité, tout ce que nous pouvons faire est de supprimer le code, car même si une méthode n'était pas incorporée, toute fonction qui l'appelait pourrait dépendre du comportement du type qui peut changer si la méthode change. C'est principalement pour les cas interactifs, donc la recompilation du code n'est pas un problème.
Nécessite le même travail que le numéro 47.

Dommage. Eh bien, puisque c'est principalement la réplique qui est affectée, faire la bonne chose lentement est bien. Au moment de l'exécution, cela ne devrait presque jamais entrer en jeu - pourquoi quelqu'un définirait-il quelque chose puis le redéfinirait à nouveau, sauf de manière interactive? Si la solution finit par induire beaucoup de frais généraux, peut-être qu'elle devrait être optionnelle et automatiquement effectuée dans le repl?

Nous devons documenter que cela n'est pas défini actuellement.

Techniquement, ce n'est pas un bug, c'est un comportement indéfini. Lorsque vous redéfinissez une méthode, le comportement qui en résulte n'est pas défini. Ainsi, Julia peut faire ce qu'elle veut, y compris ce qu'elle fait actuellement. Fournir un comportement bien défini lors de la redéfinition de la méthode est une fonctionnalité, pas une correction de bogue. Je ne suis pas non plus convaincu qu'il s'agit d'un problème v1.0 car passer d'un comportement indéfini à un comportement bien défini n'est pas un changement radical. Cela pourrait être implémenté dans la v1.1 sans casser un code v1.0 valide.

Greg Clayton du personnel LLVM / LLDB d'Apple a eu la gentillesse de documenter comment obtenir (avec les bibliothèques lldb, un sous-projet de llvm) les informations nécessaires pour déterminer les dépendances d'un binaire à partir des informations de symboles intégrées (importations de symboles); ainsi que les symboles exportés par un binaire (nécessaire pour construire le graphe de dépendances complet).

  • Jason

Le 31 mars 2012 à 23 h 02, Jason E. Aten a écrit:

Chers passionnés de LLDB,

Je me demande si je peux utiliser la bibliothèque / bibliothèques lldb pour remplacer le certain code fonctionnant sous OSX qui renvoie maintenant deux listes de symboles - similaires à la sortie de (dyldinfo -lazy_bind -exports); c'est-à-dire que j'ai besoin de lister les symboles importés et exportés par un objet ou un bundle partagé binaire.

Mon espoir était qu'en utilisant une bibliothèque lldb, je serais capable d'utiliser le même code client sur OSX que sur Linux. (La version linux du code utilise actuellement libbfd et libdld pour faire la même chose, mais cette dernière ne reçoit que peu d'amour / d'entretien).

Je regarde include / lldb /, car il semble que lldb aurait besoin de ces mêmes informations (liste de symboles importés et liste de symboles exportés pour un fichier Mach-O) pour fonctionner, mais on ne sait pas quelle API utiliser. Toutes les suggestions / pointeurs vers des exemples de code dans lldb seraient les bienvenus!

Je vous remercie.
Jason

Au cas où on ne sait pas ce que fait dyldinfo, voici un exemple: (mais je n'ai besoin que des noms de symboles; pas des adresses, des segments ou des sections):

$ fichier / tmp / sample_bundle
/ tmp / sample_bundle: ensemble Mach-O 64 bits x86_64

$ dyldinfo -lazy_bind -export / tmp / sample_bundle

informations de liaison paresseuse (de la partie lazy_bind de dyld info):
segment section adresse index symbole dylib
__DATA __la_symbol_ptr 0x00001030 0x0000 flat-namespace __mm_pop_chunk
__DATA __la_symbol_ptr 0x00001038 0x0015 espace de nom plat _dh_define
informations d'exportation (à partir du 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

Le lundi 2 avril 2012 à 15 h 13, Greg Clayton [email protected] a écrit:

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

Le lundi 2 avril 2012 à 16 h 05 , Greg Clayton

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.

Discussion sur la liste des développeurs ici: https://groups.google.com/forum/?fromgroups=#!topic/julia -dev / snnGKJul4vg.

C'est en train de remonter un fil étonnamment vieux, mais j'ai réalisé que je flirtais peut-être autour de ce problème avec mon code de benchmarking de vitesse de code. J'ai à plusieurs reprises Core.include() fichiers qui contiennent deux fonctions, listTests() et runTests() . Ensuite, j'appelle simplement listTests() et runTests() , redéfinissant ainsi puis les appelant chaque fois que je charge un nouveau fichier de référence. Comme je ne redéfinis pas les fonctions de deuxième niveau, je ne pense pas que je rencontrerai les problèmes énumérés ici, mais est-ce que redéfinir des choses comme celle-ci est une mauvaise façon de charger plusieurs fichiers avec la même «API»?

Il serait peut-être préférable de les inclure à plusieurs reprises dans un module, mais je me rends compte que cela se heurte au problème de module anonyme qui a également été soulevé dans la liste des développeurs. Dans ce cas, cela peut être bien, cependant, car vous pouvez simplement définir le même module plusieurs fois et ignorer l'avertissement qui émet.

Peut-être un vieux fil, mais toujours aussi pertinent.
Peut-être qu'une autre approche est d'utiliser evalfile et de faire "retourner" le fichier un tuple de fonctions. Quel est le problème du module anonyme?

Je faisais référence à ceci: https://github.com/JuliaLang/julia/issues/3661.

Quelqu'un a-t-il d'autres réflexions sur les stratégies de mise en œuvre? Je pensais coder un schéma simple qui maintient un graphe d'appel inversé qui est mis à jour chaque fois qu'une nouvelle méthode est créée (basée sur l'AST non optimisé). Il parcourt ensuite ce graphique lorsqu'une méthode est redéfinie, en supprimant la version compilée de chacun des descendants de la fonction redéfinie.

C'est une approche aussi raisonnable que n'importe quelle autre à essayer. Si vous vous sentez motivé pour le faire, je dirais allez-y et voyons ce qui se passe!

Que se passe-t-il si _execution_ de l'appelé déclenche la recompilation de l'appelant?

Je pose la question parce que je me suis rendu compte tardivement que la résolution de ce problème peut avoir des conséquences intéressantes qui vont au-delà de l'expérience utilisateur dans le REPL: cela pourrait permettre la réponse ultime à un problème de métaprogrammation, celui de créer des fonctions «par étapes» efficaces. Comme arrière-plan pour ceux qui ne le savent peut-être pas: certaines fonctions sont actuellement implémentées par métaprogrammation, en particulier celles pour lesquelles un aspect de l'algorithme dépend des types d'entrées de manière non triviale --- l'exemple canonique est celui dans lequel le nombre de boucles dans le corps de la fonction est égale à la dimensionnalité d'un tableau. La façon dont nous gérons généralement cela est de définir la fonction explicitement pour, par exemple, les dimensions 1 et 2, puis d'avoir un wrapper qui ressemble à ceci:

_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

Ainsi, la première fois que myfunction est exécuté pour un tableau à 4 dimensions, il définit d'abord une version spécifique pour 4 dimensions, l'ajoute à _method_cache , puis évalue la nouvelle fonction sur l'entrée. Lors des futurs appels à myfunction avec un tableau à 4 dimensions, il récupère simplement la définition du dictionnaire et l'évalue. _method_cache est une sorte de "table de méthodes shadow" en parallèle à la propre table de méthodes interne de Julia, une qui n'est utilisée que pour cette fonction particulière. (Pour le garder privé, il est généralement encapsulé par un let .)

Bien que l'approche de cet exemple fonctionne bien pour les corps de fonction qui prennent un certain temps à s'exécuter, elle n'est pas très adaptée aux fonctions qui s'exécutent en moins de temps que nécessaire pour une recherche dans le dictionnaire, et elle est particulièrement mauvaise pour les fonctions que vous voulez être capable de se connecter.

Une meilleure façon de procéder pourrait être la suivante:

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

Ici, l'exécution de myfunction génère une version plus spécifique de myfunction pour ces types d'entrée particuliers. Le code qui est compilé une fois que cette nouvelle définition sera disponible utilisera cette nouvelle version le cas échéant; la version ci-dessus devient la "solution de secours" générique pour les cas où vous n'avez pas encore défini quelque chose de plus spécifique. Par conséquent, ce serait une façon de créer des fonctions par étapes qui exploitent les mécanismes internes de table de méthodes de Julia, et permettrait donc de générer du code efficace.

Actuellement, cela ne fonctionne pas pour une raison cruciale: tout ce qui vient d'appeler myfunction a déjà été compilé, donc il ne connaît pas la nouvelle définition. Par conséquent, cet appelant particulier générera toujours une nouvelle version de myfunction utilisant eval , ce qui sera extrêmement lent.

L'astuce serait donc de recompiler l'appelant, mais notez que cela doit se produire _ alors qu'il est en cours d'exécution_. Qu'est-ce qui va se passer?

Voir également # 5395.

Il existe un problème connexe avec les types abstraits qui n'implique pas réellement de redéfinition de méthode mais qui nécessite un traitement similaire. Considérer:

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()])

La dernière ligne donne 4602678819172646912 . Nous devons jeter le code pour g car l'inférence de type pour f(::A) n'est plus valide.

Oui, c'est assez clair. Nous savons que le remplacement des méthodes n'est qu'un cas particulier. Mais cela implique toujours de nouvelles définitions de méthodes.

Il semble que la situation actuelle puisse être quelque peu ambiguë car

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

donne

1
1

tandis que

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

donne

1
2

Il semble que le dernier cas puisse être utilisé comme solution de contournement pour émuler la recompilation de l'appelant (je sais que ce n'est pas vraiment une recompilation).

J'ai le sentiment que je suis sur le point de dire quelque chose de stupide, mais ce problème ne pourrait-il pas être éliminé avec un seul niveau d'indirection? Au lieu de coder en dur une adresse de fonction, recherchez-la à partir d'un emplacement fixe et appelez-la. Cela n'aurait-il pas des performances similaires aux fonctions virtuelles C ++ ou Java? Vous pouvez ensuite annoter des fonctions pour avoir une adresse statique ou dynamique, et obtenir un avertissement / une erreur lorsque vous essayez de redéfinir une fonction avec une adresse statique. Il pourrait même y avoir un commutateur pour définir le comportement de la fonction par défaut. Je suis nouveau dans le langage et je ne suis pas du tout familier avec la base de code, mais si cela semble faisable, je suppose que je pourrais lui donner un coup de pouce.

@ omer4d un problème avec cette idée est que Julia fait en ligne beaucoup de fonctions plus petites, donc nous avons encore besoin d'un moyen de rechercher tous les "dépendants" d'une certaine fonction.

Il ne peut intégrer que ceux avec des adresses statiques, comme le fait C ++. Cela ne devrait causer aucun inconvénient.
Les personnes qui ne se soucient pas du développement interactif ne seront pas du tout affectées, car le comportement par défaut serait d'utiliser des adresses statiques pour toutes les fonctions.
Les personnes qui ne se soucient pas des performances mais qui souhaitent un développement interactif peuvent utiliser un commutateur pour rendre les adresses de fonction dynamiques par défaut.
Les personnes qui souhaitent à la fois l'interactivité et la performance peuvent simplement annoter les fonctions pertinentes, ce qui ne devrait pas demander beaucoup de travail, car l'inlining n'est pertinent que pour les fonctions courtes de bas niveau qui sont souvent appelées.
Peut-être que le comportement par défaut pourrait même être défini par module.

Je pense qu'il y a plusieurs raisons pour lesquelles cette approche n'est pas viable. Première,
les annotations sont vraiment une nuisance, surtout lorsqu'elles sont nécessaires pour pirater
autour de détails de mise en œuvre comme celui-ci. Si des annotations sont utilisées pour résoudre 2
ou 3 problèmes, ils commencent à s'empiler de manière significative. Deuxièmement, il n'y a pas
bon moyen de choisir la bonne annotation pour une fonction. Il n'y a pas
lien entre si vous voulez quelque chose en ligne et si vous êtes
le développer de manière interactive. Troisièmement, tapez les déductions dans d'autres fonctions
peut avoir besoin de changer, il ne s'agit donc pas seulement de sites d'appels. En fait, nous sommes actuellement
dériver moins d'informations de type que nous pourrions potentiellement faire des choses
sûr face à ce problème.

Eh bien, en tant que personne qui fait la plupart de son travail dans des langues avec des dizaines de mots-clés qualificatifs et des recompilations manuelles répétées, devoir parfois qualifier une fonction ou deux et recompiler après quelques cycles de développement interactif ne semble pas si mal, mais c'est juste moi . Je n'en sais pas assez pour aborder le troisième point, je vais donc me retirer. Je pense qu'il est malheureux qu'une telle solution ne soit pas viable, car la recompilation signifie que si la racine de l'arborescence des appels d'une fonction modifiée contient un jeu ou une boucle d'animation, elle devra être arrêtée et redémarrée. = (

@JeffBezanson Je serais probablement plus intéressé par le cas facile, du moins au début. Peu m'importe que le code en cours d'exécution soit mis à jour, par opposition aux choses accessibles via eval (par exemple, entré à l'invite REPL). Finalement, il sera également important, par exemple, d'obtenir la bonne version de display appelée une fois redéfinie. Mais je pense que cela devrait nous rapprocher rapidement des compilations incrémentielles.

J'ai été un peu surpris de connaître ce problème, non pas parce que je pense que c'est facile à gérer, mais parce que ce qui suit fonctionne comme prévu.

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

Edit: En fait, cela ressemble à un cas particulier pour l'entrée Vararg ..... Cela signifie-t-il qu'une fonction vararg est recompilée à chaque fois qu'elle est appelée? Ou est-il assez intelligent pour ne recompiler que lorsque cela est nécessaire? Et est-il possible de traiter simplement d'autres fonctions de la même manière?

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

En fait, la fonction renvoie le bon résultat mais le @code_typed renvoie un mauvais résultat .....

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))))

Et

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))))

fonctionnerait-il pour indexer les méthodes des fonctions (ou seulement les types de fonctions / objets eux-mêmes) avec un nombre qui change à chaque fois que quelque chose est assigné et ainsi devenir capable de vérifier si le nombre stocké avec la fonction compilée (appelant) (c'est-à-dire en utilisant la nouvelle structure de fonctions, en enregistrant ces nombres sous forme de champ, etc.) correspond toujours à celui qui est "courant" sur la fonction qui a été insérée (appelée)?
(sans grosse pénalité de performance bien sûr)
Et en cas de changement envisagez de compiler la nouvelle version ou de compléter par appel explicite.

Cela déplacerait l'impact sur les performances vers l'exécution (et ce serait partout), ce qui n'est pas souhaitable. Ce qu'il faut, c'est garder une trace du Web des dépendances et recompiler tout ce qui pourrait être affecté lorsqu'une méthode est partiellement redéfinie. C'est probablement faisable, c'est juste une douleur _massive_.

Je ne sais pas si la solution à ce problème se généraliserait facilement aux macros, mais si c'est le cas, ce serait bien d'avoir.

Donc, on devrait plutôt aller dans l'autre sens et stocker toutes les méthodes qui ont intégré la fonction et les recompiler en cas de changement. (bien sûr, vérification de l'exécution en cours, etc.)

Les problèmes difficiles ici sont:

  • invalidation de code comme vous le dites, découvrez tous les endroits qui utilisent d'anciennes fonctions (dans le code généré). Cela implique probablement de pouvoir re-déplacer le code qui appelle les fonctions qui ont changé mais pour lesquelles la convention d'appel et le type de retour n'ont pas changé
  • plus difficile: que se passe-t-il si une autre tâche est en cours d'exécution pendant que vous définissez une nouvelle méthode? quel état voit-il? Vous voulez résoudre ce problème sans remplacement sur pile, car c'est très difficile à mettre en œuvre efficacement. Nous pensons que nous avons une solution à ce qui ne tue pas les performances et je pense que @vtjnash proposera un petit article une fois qu'il sera en bon état

Bien qu'il soit travaillé sur une solution pour le problème, je suis curieux de savoir s'il est possible et une bonne idée d'ajouter un indicateur à chaque fonction qui est définie si (et seulement si) la fonction donnée a été utilisée de l'une des manières supérieures ( inlining etc) afin de produire un avertissement qu'une méthode inline a été redéfinie. (Semblable au fonctionnement de l'avertissement ambigu)
S'il n'y a pas de meilleur moyen de sauvegarder le drapeau, peut-être à nouveau en utilisant la nouvelle structure de méthodes / fonctions de julia 0.5

Il est maintenant encore plus facile de frapper ceci dans le repl et ce comportement devrait peut-être être documenté pour la carte?

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

Ce n'est encore que la vanille # 265. Je pense que vous apprécierez que cela sera bientôt corrigé dans la v0.6-dev :)

@vtjnash y a-t-il des performances d'exécution ou un compromis mémoire?

il nécessite un peu de mémoire (140 Mo -> 170 Mo), mais ne devrait pas avoir beaucoup d'effet sur les performances (compilation ou exécution). Et je n'ai pas encore vraiment essayé beaucoup d'optimisation.

Les démos jusqu'à présent sont amusantes:

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

La raison qui ne renvoie pas 2, 3, 4 est-elle due à l'ordre dans lequel la compilation de g vs l'exécution qui redéfinit f se produit?

C'est à peu près vrai. Notez que le langage sémantique ne définit pas la compilation, il est donc plus correct de dire que l'ordre dans lequel g est interprété par rapport au moment où la redéfinition de f devient visible pour l'interpréteur

très bien, voici une autre petite démo amusante redéfinissant la primitive + pour compter combien elle est utilisée:

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

Celui-là triche: je ne pense pas que + soit utilisé par Base, donc il n'y avait rien à recompiler. Redéfinissez une fonction réelle , comme svd , dont vous savez qu'elle doit être appelée au moins 100 fois avant que la REPL n'apparaisse. Ensuite, nous serons impressionnés.

Les opportunités de ré-instrumentation en direct du code pour le profilage / traçage m'époustouflent.

En effet. Et là, j'étais sur le point d'acheter un nouveau clavier, car la séquence Ctrl-D; julia<Enter> est presque épuisée. On dirait que je n'aurai peut-être pas à le faire.

Il peut être un peu obscur de mettre dans les notes de publication, mais faut-il noter quelque part que les compréhensions sont maintenant une syntaxe pour collect -ing a Generator (par opposition à quelque chose peut-être au niveau de l'analyse en 0.4? - @which ne fonctionne pas ici)? Cet exemple, similaire à celui de vchuravy ci-dessus, est un peu un piège même si l'on considère que chaque fonction est maintenant un type en 0.5:

               _
   _       _ _(_)_     |  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

pas tout à fait juste?

utile pour le correctif # 265

Est-ce vraiment corrigé?

Cette solution est-elle toujours prévue pour être rétroportée à 0.5.x?

Non.

@ rapus95 , c'est un changement très invasif, et se tromper pourrait déstabiliser Julia pour de nombreux utilisateurs. Étant donné que le but des versions est de fournir plus de stabilité que de suivre master, il est bien préférable de l'avoir en 0.6. (Vous pouvez toujours suivre master si vous le souhaitez avant la version 0.6.)

Je suis tellement heureux de voir les progrès sur cette question! Ce serait étonnant si cela permettait également au compilateur d'optimiser le cas décrit dans https://groups.google.com/forum/#!topic/julia -users / OBs0fmNmjCU.

Sensationnel. Quel beau moment!

Salut les gars!

Existe-t-il une possibilité de rétroporter ce correctif vers la série 0.5? Ou cela ne fonctionne-t-il que dans ce qui deviendra 0,6?

Il s'agit d'un changement radical et ne sera disponible que dans la version 0.6

Il est difficile de surestimer à quel point il est génial de résoudre ce problème. Les vieilles habitudes meurent fort, alors je redémarre et / ou reconstruis parfois julia inutilement, mais quand je me souviens, cela change complètement la donne pour résoudre les problèmes.

Cette page vous a été utile?
0 / 5 - 0 notes