Julia: recopilación automática de funciones dependientes

Creado en 26 nov. 2011  ·  55Comentarios  ·  Fuente: JuliaLang/julia

si defino una función d2 antes de una función d1 que llama a d2 y luego cambio d2, d1 usa la antigua definición de d2.
Supongo que esto se debe a que todo está precompilado, pero tal vez debería haber una nota de advertencia de esto. ¿O sería posible reemplazar la antigua definición con un longjmp a la nueva?
(Principalmente importante para REPL, ya que no siempre hago una carga completa)

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

Comentario más útil

Guau. ¡Qué gran momento!

Todos 55 comentarios

Creo que esto se debe a que el código para d2() se genera cuando se define la función, por lo que el método d() se resuelve en ese momento. Como señaló, puede redefinir d2() como se definió originalmente para que funcione como se esperaba. Idealmente, redefiniríamos automáticamente cualquier cosa que dependa de d() cuando se cambia de esta manera, pero no tenemos ninguno de los metadatos en su lugar que lo permitan. Supongo que podríamos poner una advertencia sobre este comportamiento en el manual. Mejor aún sería arreglarlo, pero creo que es un poco complicado de hacer: - /

Esperaba que nadie se diera cuenta de esto, pero supongo que no fue realista de mi parte :)

Es más divertido que eso; realmente le permite ver el JIT en funcionamiento, ya que no se resuelve hasta la ejecución:

julia> function f2()
       a
       end

julia> function f1()
       f2()
       end

julia> function f2()
       2
       end

julia> f1()
2

Sin embargo, me temo que esto puede llevar a algunos resultados inesperados en el REPL, aunque realmente no sé cuál es la mejor manera de lidiar con eso. Aunque utilicé un error para mostrar la diferencia de manera dramática, ¡también podría suceder si estaba tratando de ver el efecto de cambiar una ecuación interna o una constante!

Hay dos tipos de ubicaciones que es posible que deban actualizarse cuando se modifica un método:

  • lugares donde el método se llama explícitamente como una función (llamada / ret)
  • lugares donde el método ha sido insertado

El primero podría actualizarse simplemente cambiando el cuerpo de la función: podría modificarse en su lugar, o los sitios de llamada podrían actualizarse activamente para llamar a una nueva ubicación, o el cuerpo de la función anterior podría reemplazarse con un código auxiliar que salte a la nueva versión , opcionalmente parcheando el sitio que llama. Para la inserción, necesitamos rastrear a las personas que llaman que han integrado la función y volver a JIT.

Realmente todo lo que podemos hacer es descartar el código, porque incluso si un método no estuviera en línea, cualquier función que lo llame podría depender del comportamiento del tipo que puede cambiar si el método cambia. Esto es principalmente para casos interactivos, por lo que volver a compilar el código no es un gran problema.
Requiere el mismo trabajo que el número 47.

Gorrón. Bueno, dado que es principalmente la respuesta la que se ve afectada, hacer lo correcto lentamente está bien. En tiempo de ejecución, esto casi nunca debería activarse: ¿por qué alguien definiría algo y luego lo redefiniría de nuevo, excepto de forma interactiva? Si la solución termina provocando una gran cantidad de gastos generales, tal vez convertirla en uno debería ser opcional y hacerse automáticamente en la respuesta.

Necesitamos documentar que esto no está definido actualmente.

Técnicamente, esto no es un error, es un comportamiento indefinido. Cuando redefine un método, el comportamiento resultante no está definido. Entonces Julia puede hacer lo que quiera, incluido lo que hace actualmente. Proporcionar un comportamiento bien definido tras la redefinición del método es una característica, no una corrección de errores. Tampoco estoy convencido de que este sea un problema de la versión 1.0, ya que pasar de un comportamiento indefinido a proporcionar un comportamiento bien definido no es un cambio rotundo. Esto podría implementarse en v1.1 sin romper ningún código v1.0 válido.

Greg Clayton, del personal LLVM / LLDB de Apple, tuvo la amabilidad de documentar cómo obtener (con bibliotecas lldb, un subproyecto de llvm) la información necesaria para determinar las dependencias de un binario a partir de la información del símbolo incrustado (importaciones de símbolos); así como los símbolos exportados por un binario (necesarios para construir el gráfico de dependencia completo).

  • Jason

El 31 de marzo de 2012, a las 11:02 p.m., Jason E. Aten escribió:

Estimados entusiastas de LLDB,

Me pregunto si puedo usar la biblioteca / bibliotecas lldb para reemplazar cierto código que se ejecuta en OSX y que ahora devuelve dos listas de símbolos, similar a la salida de (dyldinfo -lazy_bind -exports); es decir, necesito enumerar los símbolos importados y exportados por un objeto o paquete binario compartido.

Mi esperanza era que al usar una biblioteca lldb, pudiera usar el mismo código de cliente en OSX que en linux. (La versión linux del código actualmente usa libbfd y libdld para hacer lo mismo, pero la última tiene poco cuidado / mantenimiento).

Estoy revisando include / lldb /, ya que parece que lldb necesitaría esta misma información (lista de símbolos importados y lista de símbolos exportados para un archivo Mach-O) para funcionar, pero no está claro qué API usar. Todas las sugerencias / punteros al código de ejemplo en lldb serían bienvenidas.

Gracias.
Jason

En caso de que no esté claro qué hace dyldinfo, aquí hay un ejemplo: (pero solo necesito los nombres de los símbolos; no las direcciones, segmentos o secciones):

$ file / tmp / sample_bundle
/ tmp / sample_bundle: paquete Mach-O de 64 bits x86_64

$ dyldinfo -lazy_bind -export / tmp / sample_bundle

información de enlace perezoso (de lazy_bind parte de dyld info)
segmento sección dirección índice símbolo dylib
__DATA __la_symbol_ptr 0x00001030 0x0000 espacio de nombres plano __mm_pop_chunk
__DATA __la_symbol_ptr 0x00001038 0x0015 espacio de nombres plano _dh_define
exportar información (de 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

El lunes 2 de abril de 2012 a las 3:13 p.m., Greg Clayton [email protected] escribió:

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

El lunes 2 de abril de 2012 a las 4:05 p.m., Greg Clayton [email protected] escribió:

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.

Discusión de la lista de desarrolladores aquí: https://groups.google.com/forum/?fromgroups=#!topic/julia -dev / snnGKJul4vg.

Esto está desenterrando un hilo increíblemente antiguo, pero me di cuenta de que posiblemente estoy coqueteando con los bordes de este problema con mi código de evaluación comparativa de codespeed. Repetidamente Core.include() archivos que contienen dos funciones, listTests() y runTests() . Luego simplemente llamo listTests() y runTests() , redefiniéndolos y llamándolos cada vez que cargo un nuevo archivo de referencia. Como no estoy redefiniendo las funciones de segundo nivel, no creo que me encuentre con los problemas enumerados aquí, pero ¿redefinir cosas como esta es una mala manera de cargar varios archivos con la misma "API"?

Podría ser mejor incluirlos en un módulo repetidamente, pero me doy cuenta de que se enfrenta al problema del módulo anónimo que también se ha planteado en la lista de desarrolladores. En este caso, puede estar bien, sin embargo, ya que puede definir el mismo módulo varias veces e ignorar la advertencia que emite.

Quizás un hilo antiguo, pero tan relevante como siempre.
Quizás otro enfoque sea usar evalfile y hacer que el archivo "devuelva" una tupla de funciones. ¿Cuál es el problema del módulo anónimo?

¿Alguien tiene ideas adicionales sobre las estrategias de implementación? Estaba pensando en codificar un esquema simple que mantiene un gráfico de llamadas invertido que se actualiza cada vez que se crea un nuevo método (basado en el AST no optimizado). Luego recorre ese gráfico cuando se redefine un método, eliminando la versión compilada de cada uno de los descendientes de la función redefinida.

Ese es un enfoque tan razonable como cualquier otro. Si se siente motivado para hacerlo, le diría que siga adelante y veamos qué sucede.

¿Qué sucede si la _ ejecución_ del destinatario de la llamada activa la recompilación del llamador?

Pregunto porque me he dado cuenta tardíamente de que resolver este problema puede tener consecuencias interesantes que van más allá de la experiencia del usuario en el REPL: podría permitir la respuesta definitiva a un problema de metaprogramación, el de crear funciones eficientes "por etapas". Como antecedente para aquellos que quizás no lo sepan: algunas funciones se implementan actualmente mediante metaprogramación, en particular aquellas para las que algún aspecto del algoritmo depende de los tipos de entradas de una manera no trivial --- el ejemplo canónico es aquel en el que el número de bucles en el cuerpo de la función es igual a la dimensionalidad de una matriz. La forma en que usualmente manejamos esto es definir la función explícitamente para, por ejemplo, las dimensiones 1 y 2, y luego tener un contenedor que se parece a esto:

_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

Entonces, la primera vez que myfunction se ejecuta para una matriz de 4 dimensiones, primero define una versión específica para 4 dimensiones, la agrega a _method_cache y luego evalúa la nueva función en la entrada. En futuras llamadas a myfunction con una matriz de 4 dimensiones, simplemente recupera la definición del diccionario y la evalúa. _method_cache es una especie de "tabla de métodos de sombra" en paralelo a la propia tabla de métodos internos de Julia, una que se usa solo para esta función en particular. (Para mantenerlo privado, generalmente está encapsulado por let ).

Si bien el enfoque en este ejemplo funciona bien para cuerpos de funciones que tardan algún tiempo en ejecutarse, no es muy adecuado para funciones que se ejecutan en menos tiempo del requerido para una búsqueda de diccionario, y es especialmente malo para las funciones que desea capaz de en línea.

Una mejor forma de hacer esto podría ser la siguiente:

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

Aquí, la ejecución de myfunction genera una versión _más específica_ de myfunction para estos tipos de entrada particulares. El código que se compila después de que esta nueva definición esté disponible utilizará esta nueva versión cuando corresponda; la versión anterior se convierte en el "respaldo" genérico para aquellos casos en los que aún no ha definido algo más específico. En consecuencia, esta sería una forma de crear funciones por etapas que explota los propios mecanismos internos de tabla de métodos de Julia y, por lo tanto, permitiría generar código eficiente.

Actualmente, esto no funciona por una razón crucial: lo que se acaba de llamar myfunction ya se ha compilado, por lo que no conoce la nueva definición. En consecuencia, esta persona que llama en particular siempre generará una nueva versión nueva de myfunction usando eval , lo cual será terriblemente lento.

Entonces, el truco sería volver a compilar la persona que llama, pero tenga en cuenta que esto debe ocurrir _mientras está en medio de la ejecución_. ¿Qué va a pasar?

Vea también # 5395.

Existe un problema relacionado con los tipos abstractos que en realidad no implica la redefinición del método, pero necesita un tratamiento similar. Considerar:

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 última línea da 4602678819172646912 . Necesitamos descartar el código de g porque la inferencia de tipo para f(::A) ya no es válida.

Sí, eso está bastante claro. Sabemos que los métodos de reemplazo son solo un caso especial. Pero siempre implica nuevas definiciones de métodos.

Parece que la situación actual puede ser de alguna manera ambigua ya que

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

da

1
1

mientras

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

da

1
2

Parece que el último caso se puede utilizar como una solución para emular la recompilación de llamadas (sé que no es realmente una recompilación).

Tengo la sensación de que estoy a punto de decir algo estúpido, pero ¿no podría eliminarse este problema con un solo nivel de indirecta? En lugar de codificar la dirección de una función, búsquela desde una ubicación fija y llámela. ¿No tendría esto un rendimiento similar a las funciones virtuales de C ++ o Java? Luego, podría anotar funciones para que tengan una dirección estática o dinámica, y obtener una advertencia / error cuando intente redefinir una función con una dirección estática. Incluso podría haber un interruptor para establecer el comportamiento de la función predeterminada. Soy nuevo en el lenguaje y no estoy familiarizado por completo con el código base, pero si esto suena factible, supongo que podría darle un golpe.

@ omer4d Un problema con esa idea es que Julia

Solo podría incorporar los que tienen direcciones estáticas, como lo hace C ++. Esto no debería causar ningún inconveniente.
Las personas a las que no les importa el desarrollo interactivo no se verán afectadas en absoluto, ya que el comportamiento predeterminado sería usar direcciones estáticas para todas las funciones.
Las personas que no se preocupan por el rendimiento pero desean un desarrollo interactivo pueden usar un interruptor para hacer que las direcciones de funciones sean dinámicas de forma predeterminada.
Las personas que desean tanto interactividad como rendimiento podrían simplemente anotar las funciones relevantes, lo que no debería suponer mucho trabajo, ya que la inserción solo es relevante para funciones breves de bajo nivel que reciben muchas llamadas.
Quizás el comportamiento predeterminado incluso podría establecerse por módulo.

Creo que hay varias razones por las que el enfoque no es viable. Primero,
las anotaciones son realmente una molestia, especialmente cuando se necesitan para piratear
en torno a detalles de implementación como este. Si se utilizan anotaciones para resolver 2
o 3 problemas, comienzan a acumularse significativamente. En segundo lugar, no hay
buena forma de elegir la anotación correcta para una función. No hay
conexión entre si quieres algo en línea y si estás
desarrollándolo de forma interactiva. En tercer lugar, escriba deducciones en otras funciones.
es posible que deba cambiar, por lo que no se trata solo de sitios de llamadas. De hecho estamos actualmente
derivando menos información de tipo de la que potencialmente podríamos para hacer cosas
a salvo frente a este problema.

Bueno, como alguien que hace la mayor parte de su trabajo en idiomas con docenas de palabras clave calificativas y recopilación manual repetida, tener que calificar ocasionalmente una función o dos y volver a compilar después de algunos ciclos de desarrollo interactivo no suena tan mal, pero soy solo yo. . Sin embargo, no sé lo suficiente para abordar el tercer punto, así que renunciaré. Creo que es desafortunado que dicha solución no sea viable, ya que la recompilación significa que si la raíz del árbol de llamadas de una función modificada contiene un bucle de juego o animación, será necesario finalizarlo y reiniciarlo. = (

@JeffBezanson Probablemente estaría más interesado en el caso fácil, al menos al principio. No me importa tanto el código en ejecución se actualice, a diferencia de las cosas a las que se accede a través de eval (por ejemplo, ingresado en el indicador REPL). Eventualmente, también será importante, por ejemplo, obtener la versión correcta de display llamada cuando se redefina. Pero creo que eso debería acercarnos rápidamente a tener compilaciones incrementales.

Me sorprendió un poco conocer este problema, no porque crea que sea fácil de manejar, sino porque lo siguiente funciona como se esperaba.

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

Editar: En realidad, esto parece un caso especial para la entrada de Vararg ... ¿Significa esto que una función vararg se recompila cada vez que se llama? ¿O es lo suficientemente inteligente como para volver a compilar solo cuando sea necesario? ¿Y es posible tratar otras funciones de la misma manera?

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 realidad, la función devuelve el resultado correcto, pero @code_typed devuelve un resultado incorrecto .....

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

Y

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

¿Funcionaría para indexar métodos de funciones (o solo los tipos de función / objetos en sí) con un número que cambia cada vez que se asigna algo y así poder verificar si el número almacenado con la función compilada (llamador) (es decir, usando la nueva estructura? de funciones, guardar estos números como un campo, etc.) todavía coincide con el que está "actual" en la función que estaba en línea (llamada)?
(sin una gran penalización de rendimiento, por supuesto)
Y en caso de cambio, considere compilar la nueva versión o completar mediante una llamada explícita.

Eso movería el impacto del rendimiento al tiempo de ejecución (y estaría en todas partes), lo cual no es deseable. Lo que se necesita es realizar un seguimiento de la red de dependencias y volver a compilar todo lo que pueda verse afectado cuando un método se redefine parcialmente. Probablemente sea factible, es solo un dolor _masivo_.

No sé si la solución a este problema se generalizaría fácilmente a las macros, pero de ser así sería bueno tenerla.

Entonces, uno debería ir al revés y almacenar todos los métodos que alinearon la función y, al cambiar, recompilarlos. (por supuesto, comprobando la ejecución actual, etc.)

Los problemas difíciles aquí son:

  • invalidación de código como usted dice, descubra todos los lugares que usan funciones antiguas (en el código generado). Eso probablemente implica poder reubicar el código que llama a las funciones que cambiaron pero para las cuales la convención de llamada y el tipo de retorno no cambiaron
  • más difícil: ¿qué sucede si se está ejecutando otra tarea mientras define un nuevo método? que estado ve? Desea resolver eso sin reemplazo en la pila porque es muy difícil de implementar de manera eficiente. Creemos que tenemos una solución para eso que no mata el rendimiento y creo que @vtjnash redactará una pequeña

Si bien se trabaja en una solución para el problema, tengo curiosidad por saber si es posible y una buena idea agregar una bandera a cada función que se establece si (y solo si) la función dada se usó en cualquiera de las formas anteriores ( inlining, etc.) para producir una advertencia de que se redefinió un método en línea. (Similar a la forma en que funciona la advertencia ambigua)
Si no hay mejor manera de guardar la bandera, entonces nuevamente tal vez usando la nueva estructura de métodos / funciones en julia 0.5

Ahora es incluso más fácil hacer clic en esto en la respuesta y este comportamiento tal vez debería documentarse para el mapa

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

Eso es solo vainilla # 265. Creo que apreciará que esto se solucionará pronto en v0.6-dev :)

@vtjnash ¿hay alguna compensación de rendimiento o memoria en tiempo de ejecución?

requiere un poco de memoria (140 MB -> 170 MB), pero no debería tener mucho efecto en el rendimiento (compilación o tiempo de ejecución). Y todavía no he intentado mucha optimización.

Las demostraciones hasta ahora son divertidas:

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 razón por la que no devuelve 2, 3, 4 se debe al orden en el que ocurre la compilación de g vs la ejecución que redefine f ?

Eso es más o menos correcto. Tenga en cuenta que la semántica del lenguaje no define la compilación, por lo que es más correcto decir que el orden en el que se interpreta g frente al momento en que la redefinición de f vuelve visible para el intérprete

bien, aquí hay otra pequeña demostración divertida que redefine la primitiva + para llevar la cuenta de cuánto se usa:

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

Esa es una trampa: no creo que Base use + en absoluto, así que no había nada que recompilar. Redefina una función real , como svd , que sepa que debe ser llamada al menos 100 veces antes de que aparezca el REPL. Entonces estaremos impresionados.

Las oportunidades para la reinstrumentación en vivo del código para la creación de perfiles / rastreo me están volviendo loca.

En efecto. Y aquí estaba a punto de comprarme un teclado nuevo, porque la secuencia Ctrl-D; julia<Enter> está casi gastada. Parece que no debería hacerlo.

Podría ser un poco arcano poner las notas de la versión, pero debería notarse en alguna parte que las comprensiones ahora son sintaxis para collect -ing a Generator (en contraposición a algo tal vez en el nivel de análisis en 0.4? - @which no funciona allí)? Este ejemplo, similar al de vchuravy anterior, es un poco complicado incluso considerando que cada función es ahora un tipo 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

¿no del todo bien?

útil para corregir # 265

¿Está esto realmente arreglado?

¿Se planea que esta solución se vuelva a exportar a 0.5.x?

No.

@ rapus95 , este es un cambio muy invasivo, y hacerlo mal podría desestabilizar a julia para muchos usuarios. Dado que el objetivo de las versiones es proporcionar más estabilidad que seguir al maestro, es mucho mejor tenerlo en 0.6. (Siempre puede seguir al maestro si lo desea antes del lanzamiento de 0.6).

¡Estoy muy feliz de ver el progreso en este tema! Sería increíble si esto también permitiera al compilador optimizar el caso descrito en https://groups.google.com/forum/#!topic/julia -users / OBs0fmNmjCU.

Guau. ¡Qué gran momento!

¡Hola chicos!

¿Existe alguna posibilidad de trasladar esta corrección a la serie 0.5? ¿O solo funciona en lo que se convertirá en 0.6?

Este es un cambio importante y solo estará disponible en 0.6

Es difícil exagerar lo maravilloso que es arreglar esto. Los viejos hábitos son difíciles de morir, así que a veces reinicio y / o reconstruyo julia innecesariamente, pero cuando recuerdo es un cambio total para solucionar problemas.

¿Fue útil esta página
0 / 5 - 0 calificaciones