Julia: recompilação automática de funções dependentes

Criado em 26 nov. 2011  ·  55Comentários  ·  Fonte: JuliaLang/julia

se eu definir uma função d2 antes de uma função d1 que chama d2 e, em seguida, alterar d2, d1 usa a definição antiga para d2.
Presumo que seja porque tudo é pré-compilado, mas talvez devesse haver uma nota avisando sobre isso. Ou seria possível substituir a definição antiga por um longjmp para a nova?
(Mais importante para o REPL, já que nem sempre faço um carregamento completo)

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

Comentários muito úteis

Uau. Que ótimo momento!

Todos 55 comentários

Acredito que isso seja porque o código para d2() é gerado quando a função é definida, portanto, o método d() é resolvido naquele momento. Como você observou, você pode redefinir d2() como foi originalmente definido para que funcione conforme o esperado. O ideal seria redefinir automaticamente qualquer coisa que dependa de d() quando for alterado dessa forma, mas não temos nenhum dos metadados no local que permitiria isso. Acho que poderíamos colocar um aviso sobre esse comportamento no manual. Melhor ainda seria consertá-lo, mas acho um pouco complicado de fazer: - /

Eu esperava que ninguém notasse isso, mas acho que não era realista da minha parte :)

É mais divertido do que isso; realmente permite que você veja o JIT em ação, uma vez que não é resolvido até a execução:

julia> function f2()
       a
       end

julia> function f1()
       f2()
       end

julia> function f2()
       2
       end

julia> f1()
2

No entanto, temo que isso possa levar a alguns resultados inesperados no REPL, embora eu realmente não saiba a melhor maneira de lidar com isso. Embora eu tenha usado um erro para mostrar a diferença de maneira dramática, isso também poderia acontecer se eu estivesse tentando ver o efeito de alterar uma equação interna ou constante!

Existem dois tipos de locais que podem precisar ser atualizados quando um método é alterado:

  • lugares onde o método é chamado explicitamente como uma função (call / ret)
  • lugares onde o método foi embutido

O primeiro pode ser atualizado apenas alterando o corpo da função: ele pode ser alterado no local, ou os sites de chamada podem ser atualizados ativamente para chamar um novo local, ou o corpo da função antiga pode ser substituído por um esboço que salta para a nova versão , opcionalmente corrigindo o site de chamada. Para inline, precisamos rastrear os chamadores que têm inline a função e re-JIT-los.

Na verdade, tudo o que podemos fazer é descartar o código, porque mesmo que um método não tenha sido embutido, qualquer função que o chame pode depender do comportamento do tipo que pode mudar se o método mudar. Isso é principalmente para casos interativos, portanto, recompilar o código não é um grande negócio.
Requer o mesmo trabalho do problema nº 47.

Vadio. Bem, como é principalmente o repl que é afetado, fazer a coisa certa lentamente é bom. Em tempo de execução, isso quase nunca deve entrar em ação - por que alguém definiria algo e redefiniria novamente, exceto de forma interativa? Se a solução acabar induzindo muita sobrecarga, talvez a ativação deva ser opcional e feita automaticamente no repl?

Precisamos documentar que isso não está definido atualmente.

Tecnicamente, isso não é bug, é um comportamento indefinido. Quando você redefine um método, o comportamento resultante é indefinido. Assim, Julia pode fazer o que quiser, incluindo o que faz atualmente. Fornecer um comportamento bem definido na redefinição do método é um recurso, não uma correção de bug. Também não estou convencido de que este seja um problema da v1.0, pois passar de um comportamento indefinido para fornecer um comportamento bem definido não é uma alteração significativa. Isso pode ser implementado na v1.1 sem quebrar nenhum código v1.0 válido.

Greg Clayton, da equipe LLVM / LLDB da Apple, foi gentil o suficiente para documentar como obter (com bibliotecas lldb, um subprojeto de llvm) as informações necessárias para determinar as dependências de um binário a partir das informações de símbolo embutidas (importações de símbolo); bem como aqueles símbolos exportados por um binário (necessário para construir o gráfico de dependência completo).

  • Jason

Em 31 de março de 2012, às 23h02, Jason E. Aten escreveu:

Caros entusiastas do LLDB,

Estou pensando se posso usar a biblioteca / bibliotecas lldb para substituir o determinado código em execução no OSX que agora retorna duas listas de símbolos - semelhante à saída de (dyldinfo -lazy_bind -exports); ou seja, preciso listar os símbolos importados e exportados por um objeto binário compartilhado ou pacote.

Minha esperança era que, usando uma biblioteca lldb, eu seria capaz de usar o mesmo código de cliente no OSX e no Linux. (A versão Linux do código atualmente usa libbfd e libdld para fazer a mesma coisa, mas a última está recebendo pouco amor / manutenção).

Estou procurando em include / lldb /, pois parece que o lldb precisaria dessas mesmas informações (lista de símbolos importados e lista de símbolos exportados para um arquivo Mach-O) para funcionar, mas não está claro qual API usar. Todas as sugestões / ponteiros para código de exemplo em lldb seriam bem-vindos!

Obrigado.
Jason

Caso não esteja claro o que dyldinfo faz, aqui está um exemplo: (mas eu só preciso dos nomes dos símbolos; não dos endereços, segmentos ou seções):

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

$ dyldinfo -lazy_bind -export / tmp / sample_bundle

informações de ligação lazy (da parte lazy_bind das informações dyld):
símbolo dylib do índice do endereço da seção do segmento
__DATA __la_symbol_ptr 0x00001030 0x0000 flat-namespace __mm_pop_chunk
__DATA __la_symbol_ptr 0x00001038 0x0015 espaço de nomes plano _dh_define
exportar informações (do 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

Na segunda-feira, 2 de abril de 2012 às 15:13, Greg Clayton [email protected] escreveu:

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

Na segunda-feira, 2 de abril de 2012 às 16h05, Greg Clayton [email protected] escreveu:

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.

Discussão da lista de desenvolvedores aqui: https://groups.google.com/forum/?fromgroups=#!topic/julia -dev / snnGKJul4vg.

Isso está trazendo à tona um tópico incrivelmente antigo, mas percebi que possivelmente estou flertando nas bordas desse problema com meu código de benchmarking de velocidade de código. Repetidamente Core.include() arquivos que contêm duas funções, listTests() e runTests() . Então, simplesmente chamo listTests() e runTests() , redefinindo e chamando-os toda vez que carrego um novo arquivo de benchmark. Como não estou redefinindo funções de segundo nível, acho que não vou encontrar os problemas listados aqui, mas redefinir coisas como essa é uma maneira ruim de carregar vários arquivos com a mesma "API"?

Pode ser melhor incluí-los em um módulo repetidamente, mas percebo que isso esbarra no problema do módulo anônimo que também foi levantado na lista de dev. Nesse caso, pode não haver problema, pois você pode apenas definir o mesmo módulo várias vezes e ignorar o aviso que é emitido.

Talvez um tópico antigo, mas tão relevante como sempre.
Talvez outra abordagem seja usar evalfile e fazer com que o arquivo "retorne" uma tupla de funções. Qual é o problema do módulo anônimo?

Eu estava me referindo a isso: https://github.com/JuliaLang/julia/issues/3661.

Alguém tem outras ideias sobre estratégias de implementação? Eu estava pensando em codificar um esquema simples que mantém um gráfico de chamadas invertidas que é atualizado sempre que um novo método é criado (baseado no AST não otimizado). Em seguida, ele percorre esse gráfico quando um método é redefinido, removendo a versão compilada de cada um dos descendentes da função redefinida.

Essa é uma abordagem tão razoável quanto qualquer outra para tentar. Se você estiver se sentindo motivado para fazer isso, eu diria que vá em frente e vamos ver o que acontece!

O que acontece se a _execução_ do receptor acionar a recompilação do chamador?

Pergunto porque percebi tardiamente que resolver esse problema pode ter consequências interessantes que vão além da experiência do usuário no REPL: pode permitir a resposta definitiva para um problema de metaprogramação, o de criar funções "preparadas" eficientes. Como pano de fundo para aqueles que podem não saber: algumas funções são atualmente implementadas por metaprogramação, em particular aquelas para as quais algum aspecto do algoritmo depende dos tipos de entradas de uma forma não trivial --- o exemplo canônico é aquele em que o número de loops no corpo da função é igual à dimensionalidade de uma matriz. A maneira como geralmente lidamos com isso é definir a função explicitamente para, por exemplo, dimensões 1 e 2, e então ter um invólucro parecido com este:

_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

Portanto, a primeira vez que myfunction é executado para um array quadridimensional, primeiro define uma versão específica para 4 dimensões, adiciona-a a _method_cache e, em seguida, avalia a nova função na entrada. Em chamadas futuras para myfunction com um array 4-dimensional, ele apenas recupera a definição do dicionário e a avalia. _method_cache é uma espécie de "tabela de métodos sombra" em paralelo à tabela de métodos internos da própria Julia, usada apenas para esta função em particular. (Para mantê-lo privado, geralmente é encapsulado por um let .)

Embora a abordagem neste exemplo funcione bem para corpos de função que levam algum tempo para serem executados, não é muito adequada para funções que são executadas em menos tempo do que o necessário para uma consulta de dicionário e é especialmente ruim para funções que você deseja capaz de embutir.

A melhor maneira de fazer isso pode ser a seguinte:

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

Aqui, a execução de myfunction gera uma versão _mais específica_ de myfunction para esses tipos de entrada em particular. O código compilado após a disponibilização dessa nova definição usará essa nova versão quando aplicável; a versão acima se torna o "substituto" genérico para os casos em que você ainda não definiu algo mais específico. Conseqüentemente, essa seria uma maneira de criar funções em estágios que exploram os próprios mecanismos de tabela de métodos internos de Julia e, portanto, permitiria a geração de código eficiente.

Atualmente, isso não funciona por uma razão crucial: tudo o que acabou de ser chamado de myfunction já foi compilado, então não sabe sobre a nova definição. Conseqüentemente, esse chamador em particular sempre gerará uma nova versão de myfunction usando eval , o que será terrivelmente lento.

Portanto, o truque seria recompilar o chamador, mas observe que isso precisa ocorrer _enquanto está no meio da execução_. O que vai acontecer?

Veja também # 5395.

Há um problema relacionado com os tipos abstratos que não envolve realmente a redefinição do método, mas precisa de um tratamento semelhante. 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()])

A última linha dá 4602678819172646912 . Precisamos descartar o código para g porque a inferência de tipo para f(::A) não é mais válida.

Sim, isso é bastante claro. Sabemos que a substituição de métodos é apenas um caso especial. Mas sempre envolve novas definições de método.

Parece que a situação atual pode ser de alguma forma ambígua, uma vez que

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

1
1

enquanto

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

1
2

Parece que o último caso pode ser usado como uma solução alternativa para emular a recompilação do chamador (eu sei que não é realmente uma recompilação).

Tenho a sensação de que estou prestes a dizer algo estúpido, mas esse problema não poderia ser eliminado com um único nível de indireção? Em vez de codificar um endereço de função, procure-o em um local fixo e chame-o. Isso não teria desempenho semelhante ao C ++ ou funções virtuais Java? Você pode então anotar funções para ter um endereço estático ou dinâmico e obter um aviso / erro quando tentar redefinir uma função com um endereço estático. Pode até haver uma chave para definir o comportamento padrão da função. Sou novo na linguagem e não estou familiarizado com a base de código, mas se isso parece viável, acho que posso dar um soco.

@ omer4d um problema com essa ideia é que Julia inline muitas funções menores, então ainda precisamos encontrar uma maneira de pesquisar todos os "dependentes" de uma determinada função.

Ele poderia embutir apenas aqueles com endereços estáticos, como o C ++ faz. Isso não deve causar nenhum inconveniente.
Pessoas que não se importam com o desenvolvimento interativo não serão afetadas de forma alguma, já que o comportamento padrão seria usar endereços estáticos para todas as funções.
Pessoas que não se importam com o desempenho, mas desejam desenvolvimento interativo, podem usar um switch para tornar os endereços de função dinâmicos por padrão.
Pessoas que desejam interatividade e desempenho podem apenas anotar as funções relevantes, o que não deve ser muito trabalhoso, já que inlining só é relevante para funções curtas de baixo nível que são muito chamadas.
Talvez o comportamento padrão possa ser definido por módulo.

Acredito que haja vários motivos pelos quais essa abordagem não é viável. Primeiro,
as anotações são realmente um incômodo, especialmente quando são necessárias para hackear
em torno de detalhes de implementação como este. Se as anotações forem usadas para resolver 2
ou 3 problemas, eles começam a se acumular significativamente. Em segundo lugar, não há
boa maneira de escolher a anotação certa para uma função. Não há
conexão entre se você deseja algo embutido e se você está
desenvolvendo-o interativamente. Terceiro, digite deduções em outras funções
pode precisar mudar, então não se trata apenas de sites de chamadas. Na verdade, estamos atualmente
derivando menos informações de tipo do que potencialmente poderíamos para fazer as coisas
seguro em face deste problema.

Bem, como alguém que faz a maior parte de seu trabalho em linguagens com dezenas de palavras-chave qualificadoras e recompilação manual repetida, ter que qualificar uma ou duas funções ocasionalmente e recompilar após alguns ciclos de desenvolvimento interativo não parece tão ruim, mas sou só eu . Não sei o suficiente para abordar o terceiro ponto, então vou deixar o cargo. Acho lamentável que tal solução não seja viável, pois a recompilação significa que, se a raiz da árvore de chamadas de uma função modificada contiver um loop de jogo ou animação, ela precisará ser encerrada e reiniciada. = (

@JeffBezanson Eu provavelmente estaria mais interessado no caso fácil, pelo menos no início. Eu não me importo se o código em execução será atualizado, ao contrário das coisas acessadas por meio de eval (por exemplo, inserido no prompt REPL). Eventualmente, também será importante, por exemplo, obter a versão correta de display chamada quando redefinida. Mas acredito que isso deve nos aproximar rapidamente de compilações incrementais.

Fiquei um pouco surpreso ao saber desse problema, não porque ache que seja fácil de lidar, mas porque o seguinte funciona conforme o esperado.

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: Na verdade, isso parece um caso especial para a entrada Vararg ... Isso significa que uma função vararg é recompilada toda vez que é chamada? Ou é inteligente o suficiente para apenas recompilar quando necessário? E é possível apenas tratar outras funções da mesma maneira?

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

Na verdade, a função retorna o resultado certo, mas o @code_typed retorna o resultado errado .....

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

E

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

funcionaria para indexar métodos de funções (ou apenas os próprios tipos / objetos de função) com um número que muda toda vez que algo é atribuído, tornando-se assim capaz de verificar se o número armazenado com a função compilada (chamador) (ou seja, usando a nova estrutura de funções, salvando esses números como um campo etc.) ainda corresponde ao que é "atual" na função que foi embutida (chamada)?
(sem uma grande penalidade de desempenho, é claro)
E em caso de alteração, considere compilar a nova versão ou concluí-la por chamada explícita.

Isso moveria o impacto no desempenho para o tempo de execução (e estaria em todos os lugares), o que não é desejável. O que é necessário é acompanhar a teia de dependências e recompilar qualquer coisa que possa ser afetada quando um método é parcialmente redefinido. Provavelmente é factível, é apenas uma dor _massiva_.

Não sei se a solução para esse problema seria facilmente generalizada para macros, mas se sim, seria bom ter.

Portanto, deve-se ir ao contrário e armazenar todos os métodos que embutem a função e, ao alterar, recompilá-los. (claro, verificando a execução atual, etc.)

Os problemas difíceis aqui são:

  • invalidação de código como você diz, descubra todos os lugares que usam funções antigas (no código gerado). Isso provavelmente implica ser capaz de relocar o código que chama funções que mudaram, mas para as quais a convenção de chamada e o tipo de retorno não mudaram
  • mais difícil: o que acontece se outra tarefa estiver em execução enquanto você define um novo método? que estado ele vê? Você deseja resolver isso sem a substituição na pilha, porque isso é muito difícil de implementar com eficiência. Achamos que temos uma solução para isso que não mata o desempenho e acho que @vtjnash vai escrever um pouco quando estiver em boa forma

Enquanto trabalhamos em uma solução para o problema, estou curioso para saber se é possível e uma boa ideia adicionar um sinalizador a cada função que está definida se (e somente se) a função fornecida foi usada em qualquer uma das formas superiores inlining etc) para produzir um aviso de que um método inlines foi redefinido. (Semelhante à forma como o aviso ambíguo funciona)
Se não houver maneira melhor de salvar o sinalizador, então talvez usando a nova estrutura de métodos / funções em julia 0,5

Agora é ainda mais fácil acertar isso no repl e esse comportamento talvez deva ser documentado para o 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

Isso ainda é apenas vanilla # 265. Acho que você vai gostar de saber que isso será corrigido em breve na v0.6-dev :)

@vtjnash existe algum desempenho de tempo de execução ou compensação de memória?

requer um pouco de memória (140 MB -> 170 MB), mas não deve afetar muito o desempenho (compilação ou tempo de execução). E ainda não tentei muita otimização.

As demonstrações até agora são 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

A razão de não retornar 2, 3, 4 é devido à ordem em que a compilação de g vs execução que redefine f acontece?

Isso é quase certo. Observe que a semântica da linguagem não define a compilação, então é mais correto dizer que a ordem em que g é interpretada vs. o tempo em que a redefinição de f se torna visível para o intérprete

tudo bem, aqui está outra pequena demonstração divertida redefinindo o primitivo + para manter a contagem de quanto ele é usado:

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

Aquele é trapaça: eu não acho que + seja usado pelo Base de forma alguma, então não havia nada para recompilar. Redefina uma função real , como svd , que você sabe que deve ser chamada pelo menos 100 vezes antes que o REPL apareça. Então ficaremos impressionados.

As oportunidades de re-instrumentação ao vivo de código para criação de perfil / rastreamento estão me surpreendendo.

De fato. E aqui estava eu ​​para comprar um novo teclado, porque a sequência Ctrl-D; julia<Enter> está quase gasta. Parece que não preciso.

Pode ser um pouco misterioso colocar nas notas de lançamento, mas deve ser observado em algum lugar que as compreensões agora são sintaxe para collect -ing a Generator (ao contrário de algo talvez no nível de análise em 0,4? - @which não funciona lá)? Este exemplo, semelhante ao de vchuravy acima, é meio complicado, mesmo considerando que cada função agora é um tipo em 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

não muito certo?

útil para a correção # 265

Isso é realmente consertado 😲

Esta solução ainda está planejada para ser portada para 0.5.x?

Não.

@ rapus95 , esta é uma mudança muito invasiva, e errar pode desestabilizar o Julia para muitos usuários. Uma vez que o objetivo dos lançamentos é fornecer mais estabilidade do que seguir o master, é muito melhor tê-lo no 0.6. (Você sempre pode seguir o mestre se quiser antes da versão 0,6.)

Estou muito feliz em ver o progresso neste problema! Seria incrível se isso também permitisse ao compilador otimizar o caso descrito em https://groups.google.com/forum/#!topic/julia -users / OBs0fmNmjCU.

Uau. Que ótimo momento!

Oi pessoal!

Existe alguma possibilidade de retroceder esta correção para a série 0,5? Ou só funciona no que se tornará 0,6?

Esta é uma alteração importante e só estará disponível em 0,6

É difícil exagerar o quão incrível é ter isso corrigido. Velhos hábitos são difíceis de morrer, então às vezes eu reinicio e / ou reconstruo o Julia desnecessariamente, mas quando me lembro, é uma virada de jogo completa para corrigir problemas.

Esta página foi útil?
0 / 5 - 0 avaliações