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
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:
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).
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.
JasonEn 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?
Me refería a esto: https://github.com/JuliaLang/julia/issues/3661.
¿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:
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.
Comentario más útil
Guau. ¡Qué gran momento!