Julia: автоматическая перекомпиляция зависимых функций

Созданный на 26 нояб. 2011  ·  55Комментарии  ·  Источник: JuliaLang/julia

если я определяю функцию d2 перед функцией d1, которая вызывает d2, затем меняю d2, d1 использует старое определение для d2.
Я предполагаю, что это потому, что все это предварительно скомпилировано, но, может быть, должно быть предупреждение об этом? Или можно было бы заменить старое определение с longjmp на новое?
(Наиболее важно для REPL, так как я не всегда выполняю полную загрузку)

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

Самый полезный комментарий

Вау. Какой замечательный момент!

Все 55 Комментарий

Я считаю, что это потому, что код для d2() генерируется, когда функция определена, поэтому в это время разрешается метод d() . Как вы отметили, вы можете переопределить d2() виде, в котором он был изначально определен, чтобы заставить его работать должным образом. В идеале мы должны автоматически переопределять все, что зависит от d() когда оно изменяется таким образом, но у нас нет никаких метаданных, которые позволяли бы это. Думаю, мы могли бы поместить предупреждение об этом в руководство. Лучше бы все же исправить, но я думаю, что это немного сложно сделать: - /

Я надеялся, что этого никто не заметит, но, думаю, это было нереально для меня :)

Это веселее, чем это; он действительно позволяет вам увидеть JIT в действии, поскольку он не разрешается до выполнения:

julia> function f2()
       a
       end

julia> function f1()
       f2()
       end

julia> function f2()
       2
       end

julia> f1()
2

Однако я боюсь, что это может привести к некоторым неожиданным результатам на REPL, хотя я действительно не знаю, как с этим справиться. Хотя я использовал ошибку, чтобы показать разницу в драматической манере, она также могла произойти, если бы я пытался увидеть эффект изменения внутреннего уравнения или константы!

Существует два типа местоположений, которые могут потребоваться обновить при изменении метода:

  • места, где метод вызывается явно как функция (call / ret)
  • места, где метод был встроен

Первый можно обновить, просто изменив тело функции: его можно изменить на месте, или вызывающие сайты могут быть активно обновлены для вызова нового местоположения, или старое тело функции можно заменить заглушкой, которая переходит к новой версии , при желании исправление вызывающего сайта. Для встраивания нам нужно отслеживать вызывающих, которые встроили функцию, и повторно JIT их.

На самом деле все, что мы можем сделать, это отбросить код, потому что даже если метод не был встроен, любая вызывающая его функция может зависеть от поведения типа, которое может измениться при изменении метода. Это в основном для интерактивных случаев, поэтому повторная компиляция кода не представляет большого труда.
Требуется та же работа, что и для выпуска №47.

Облом. Что ж, так как это в первую очередь влияет на ответ, делать правильные вещи медленно - это нормально. Во время выполнения это почти никогда не должно сработать - зачем кому-то определять что-то, а затем переопределять это снова, кроме как интерактивно? Если решение в конечном итоге вызывает много накладных расходов, возможно, включение его должно быть необязательным и автоматически выполняться в ответе?

Нам нужно задокументировать, что в настоящее время это не определено.

Технически это не ошибка, это неопределенное поведение. Когда вы переопределяете метод, результирующее поведение не определено. Так что Джулия может делать все, что ей нравится, в том числе и то, что она делает сейчас. Обеспечение четко определенного поведения при переопределении метода - это функция, а не исправление ошибки. Я также не уверен, что это проблема версии 1.0, поскольку переход от неопределенного поведения к обеспечению четко определенного поведения не является критическим изменением. Это может быть реализовано в версии 1.1 без нарушения действующего кода версии 1.0.

Грег Клейтон из персонала Apple LLVM / LLDB был достаточно любезен, чтобы задокументировать, как извлекать (с библиотеками lldb, подпроектом llvm) необходимую информацию для определения зависимостей двоичного файла из информации встроенного символа (импорт символов); а также те символы, которые экспортируются двоичным кодом (необходимы для построения полного графа зависимостей).

  • Джейсон

31 марта 2012 г. в 23:02 Джейсон Э. Атен написал:

Уважаемые энтузиасты LLDB,

Мне интересно, могу ли я использовать библиотеку / библиотеки lldb для замены определенного кода, работающего в OSX, который теперь возвращает два списка символов - аналогично выводу (dyldinfo -lazy_bind -exports); т.е. мне нужно перечислить символы, импортируемые и экспортируемые двоичным общим объектом или пакетом.

Я надеялся, что, используя библиотеку lldb, я смогу использовать тот же клиентский код в OSX, что и в Linux. (Версия кода для Linux в настоящее время использует libbfd и libdld, чтобы сделать то же самое, но более поздняя версия не получает особого внимания / поддержки).

Я просматриваю include / lldb /, поскольку кажется, что lldb потребуется та же самая информация (импортированный список символов и экспортированный список символов для файла Mach-O) для работы, но неясно, какой API использовать. Все предложения / указатели на пример кода в lldb приветствуются!

Спасибо.
Джейсон

Если неясно, что делает dyldinfo, вот пример: (но мне нужны только имена символов, а не адреса, сегменты или разделы):

$ file / tmp / sample_bundle
/ tmp / sample_bundle: 64-разрядный пакет Mach-O x86_64

$ dyldinfo -lazy_bind -export / tmp / sample_bundle

информация о ленивом связывании (из части lazy_bind dyld info):
сегмент раздел адрес индекс индекс dylib символ
__DATA __la_symbol_ptr 0x00001030 0x0000 плоское пространство имен __mm_pop_chunk
__DATA __la_symbol_ptr 0x00001038 0x0015 плоское пространство имен _dh_define
информация об экспорте (из дерева):
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

2 апреля 2012 г. в 15:13 Грег Клейтон [email protected] написал:

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

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


All lazily bound symbols will have type Trampoline:

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

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

The symbols can be accessed via the SBModule:

   size_t
   SBModule::GetNumSymbols ();

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

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

   SymbolType
   SBSymbol::GetType ();


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


   bool
   SBSymbol::IsExternal();


So your flow should be:

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

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

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




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

2 апреля 2012 г. в 16:05 Грег Клейтон

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.

Обсуждение списка разработчиков здесь: https://groups.google.com/forum/?fromgroups=#!topic/julia -dev / snnGKJul4vg.

Это углубляется в удивительно старый тред, но я понял, что, возможно, пытаюсь обойти эту проблему с помощью своего кода тестирования скорости кода. Я неоднократно Core.include() файлы, содержащие две функции, listTests() и runTests() . Затем я просто вызываю listTests() и runTests() , тем самым переопределяя, а затем вызывая их каждый раз, когда загружаю новый файл теста. Поскольку я не переопределяю функции 2-го уровня, я не думаю, что столкнусь с перечисленными здесь проблемами, но разве переопределение таких вещей - плохой способ загрузки нескольких файлов с одним и тем же «API»?

Возможно, было бы лучше повторно включать их в модуль, но я понимаю, что это наталкивается на проблему анонимного модуля, которая также была поднята в списке разработчиков. Однако в этом случае это может быть нормально, поскольку вы можете просто определить один и тот же модуль несколько раз и игнорировать выводимое предупреждение.

Возможно, старая ветка, но актуальная как никогда.
Может быть, другой подход - использовать evalfile и заставить файл "возвращать" набор функций. В чем проблема анонимного модуля?

Я имел в виду это: https://github.com/JuliaLang/julia/issues/3661.

У кого-нибудь есть дополнительные мысли по стратегиям реализации? Я думал о написании простой схемы, которая поддерживает перевернутый граф вызовов, который обновляется всякий раз, когда создается новый метод (на основе неоптимизированного AST). Затем он просматривает этот график, когда метод переопределяется, удаляя скомпилированную версию каждого из потомков переопределенной функции.

Это самый разумный подход, который стоит попробовать. Если вы чувствуете мотивацию сделать это, я бы сказал, давай, посмотрим, что произойдет!

Что произойдет, если _execution_ вызываемого вызовет перекомпиляцию вызывающего?

Я спрашиваю, потому что с опозданием осознал, что решение этой проблемы может иметь интересные последствия, выходящие за рамки пользовательского опыта в REPL: это может дать окончательный ответ на проблему метапрограммирования, а именно создание эффективных «поэтапных» функций. В качестве фона для тех, кто может не знать: некоторые функции в настоящее время реализуются с помощью метапрограммирования, в частности те, для которых некоторые аспекты алгоритма нетривиальным образом зависят от типов входных данных --- канонический пример - это тот, в котором число циклов в теле функции равна размерности массива. Обычно мы с этим справляемся: явно определяем функцию, например, для размеров 1 и 2, а затем получаем оболочку, которая выглядит примерно так:

_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

Таким образом, в первый раз, когда myfunction выполняется для 4-мерного массива, он сначала определяет версию, специфичную для 4-х измерений, добавляет ее в _method_cache , а затем оценивает новую функцию на входе. При будущих вызовах myfunction с 4-мерным массивом он просто извлекает определение из словаря и оценивает его. _method_cache - это своего рода «таблица теневых методов», параллельная собственной внутренней таблице методов Джулии, которая используется только для этой конкретной функции. (Чтобы сохранить конфиденциальность, он обычно инкапсулируется в let .)

Хотя подход в этом примере хорошо работает для тел функций, выполнение которых занимает некоторое время, он не очень хорошо подходит для функций, которые выполняются за меньшее время, чем требуется для поиска по словарю, и особенно плохо для функций, которые вы хотите выполнять. в состоянии встроить.

Лучше всего сделать это следующим образом:

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

Здесь выполнение myfunction генерирует _более специфичную_ версию myfunction для этих конкретных типов ввода. Код, который скомпилирован после того, как это новое определение станет доступным, будет использовать эту новую версию, когда это применимо; версия, приведенная выше, становится универсальным «запасным вариантом» для тех случаев, когда вы еще не определили что-то более конкретное. Следовательно, это был бы способ создания поэтапных функций, использующих собственные внутренние механизмы таблиц методов Джулии, и, следовательно, позволял бы генерировать эффективный код.

В настоящее время это не работает по одной важной причине: все, что только что называется myfunction , уже скомпилировано, поэтому оно не знает о новом определении. Следовательно, этот конкретный вызывающий всегда будет генерировать новую новую версию myfunction используя eval , что будет чертовски медленно.

Таким образом, уловка состоит в том, чтобы перекомпилировать вызывающую программу, но обратите внимание, что это должно произойти, пока он находится в середине выполнения. Что произойдет?

См. Также # 5395.

Существует связанная проблема с абстрактными типами, которая на самом деле не требует переопределения метода, но требует аналогичного подхода. Рассматривать:

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

Последняя строка дает 4602678819172646912 . Нам нужно выбросить код для g потому что вывод типа для f(::A) больше не действителен.

Да, это совершенно ясно. Мы знаем, что замена методов - это только один особый случай. Но это всегда связано с новыми определениями методов.

Кажется, что текущая ситуация может быть как-то неоднозначной, поскольку

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

дает

1
1

в то время как

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

дает

1
2

Кажется, что более поздний случай можно использовать как обходной путь для имитации перекомпиляции вызывающего абонента (я знаю, что это не совсем перекомпиляция).

У меня такое чувство, что я собираюсь сказать что-то глупое, но нельзя ли устранить эту проблему с помощью одного уровня косвенного обращения? Вместо того, чтобы жестко кодировать адрес функции, найдите его в фиксированном месте и вызовите его. Разве это не имело бы аналогичную производительность с виртуальными функциями C ++ или Java? Затем вы можете аннотировать функции, чтобы они имели статический или динамический адрес, и получать предупреждение / ошибку при попытке переопределить функцию со статическим адресом. Может быть даже переключатель для установки поведения функции по умолчанию. Я новичок в языке и совершенно не знаком с базой кода, но если это кажется возможным, я полагаю, я мог бы дать ему удар.

@ omer4d Одна проблема с этой идеей заключается в том, что Джулия действительно встраивает множество более мелких функций, поэтому нам все еще нужен способ поиска всех «зависимых» от определенной функции.

Он может встроить только те, которые имеют статические адреса, как это делает C ++. Это не должно вызывать неудобств.
Людей, которые не заботятся об интерактивной разработке, это никак не коснется, поскольку по умолчанию для всех функций используются статические адреса.
Люди, которые не заботятся о производительности, но хотят интерактивной разработки, могут использовать переключатель, чтобы сделать адреса функций динамическими по умолчанию.
Люди, которым нужна как интерактивность, так и производительность, могут просто аннотировать соответствующие функции, что не должно быть трудоемким, поскольку встраивание актуально только для коротких низкоуровневых функций, которые вызываются часто.
Возможно, поведение по умолчанию можно даже установить для каждого модуля.

Я считаю, что такой подход нежизнеспособен по нескольким причинам. Первый,
аннотации действительно доставляют неудобства, особенно когда они нужны для взлома
вокруг деталей реализации, подобных этой. Если аннотации используются для решения 2
или 3 проблемы, они начинают сильно накапливаться. Во-вторых, нет
хороший способ подобрать правильную аннотацию для функции. Здесь нет
связь между тем, хотите ли вы что-то встроенное, и тем,
развивая его в интерактивном режиме. В-третьих, введите вычеты в других функциях
может потребоваться изменить, так что это не просто сайты звонков. Фактически мы в настоящее время
получать меньше информации о типе, чем мы могли бы
безопасно перед лицом этой проблемы.

Что ж, для человека, выполняющего большую часть своей работы на языках с десятками ключевых слов-квалификаторов и повторяющейся ручной перекомпиляцией, необходимость иногда квалифицировать одну или две функции и перекомпилировать после нескольких циклов интерактивной разработки звучит неплохо, но это только я . Однако я не знаю достаточно, чтобы рассмотреть третий пункт, поэтому я уйду. Мне жаль, что такое решение нежизнеспособно, поскольку перекомпиляция означает, что если корень модифицированного дерева вызовов функции содержит цикл игры или анимации, его необходимо будет завершить и перезапустить. знак равно

@JeffBezanson Меня, наверное, больше всего заинтересует легкий случай, по крайней мере, сначала. Меня не волнует, что обновляется много работающего кода, в отличие от вещей, доступ к которым осуществляется через eval (например, вводится в приглашении REPL). В конце концов, также будет важно, например, получить правильную версию display вызываемую при переопределении. Но я считаю, что это должно быстро приблизить нас к инкрементным компиляциям.

Я был немного удивлен, узнав об этой проблеме, не потому, что я думаю, что с ней легко справиться, а потому, что следующее работает, как ожидалось.

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

Изменить: На самом деле это выглядит как особый случай для ввода Vararg ..... Означает ли это, что функция vararg перекомпилируется каждый раз, когда она вызывается? Или он достаточно умен, чтобы перекомпилировать только при необходимости? И можно ли таким же образом обрабатывать другие функции?

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

На самом деле функция возвращает правильный результат, но @code_typed возвращает неправильный результат .....

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

И

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

будет ли он работать, чтобы индексировать методы функций (или только сами типы функций / объекты) с числом, которое меняется каждый раз, когда что-либо назначается, и, таким образом, получая возможность проверить, хранится ли число с скомпилированной (вызывающей) функцией (т.е. с использованием новой структуры функций, сохраняя эти числа как поле и т. д.) все еще совпадает с тем, который является "текущим" для функции, которая была встроена (вызвана)?
(конечно, без большой потери производительности)
А в случае изменения подумайте о том, чтобы скомпилировать новую версию или завершить явным вызовом.

Это переместит влияние на производительность во время выполнения (а это будет повсюду), что нежелательно. Что необходимо, так это отслеживать сеть зависимостей и перекомпилировать все, на что может повлиять частичное переопределение метода. Вероятно, это выполнимо, это просто массовая боль.

Я не знаю, можно ли было бы легко распространить решение этой проблемы на макросы, но если да, то было бы неплохо.

Поэтому лучше пойти другим путем и сохранить все методы, которые встроили функцию, и при изменении перекомпилировать их. (конечно, проверка текущего исполнения и т. д.)

Вот самые серьезные проблемы:

  • аннулирование кода, как вы говорите, выясните все места, где используются старые функции (в сгенерированном коде). Это, вероятно, подразумевает возможность переместить код, который вызывает функции, которые изменились, но для которых соглашение о вызовах и тип возврата не изменились.
  • сложнее: что произойдет, если во время определения нового метода выполняется другая задача? какое состояние он видит? Вы хотите решить эту проблему без замены в стеке, потому что это очень сложно реализовать эффективно. Мы думаем, что у нас есть решение, которое не убивает производительность, и я думаю, что @vtjnash предложит небольшую запись, когда он будет в приличной форме.

Пока он работает над решением проблемы, мне любопытно, возможно ли и неплохо добавить флаг к каждой установленной функции, если (и только если) данная функция использовалась одним из верхних способов ( inlining и т. д.), чтобы выдать предупреждение о переопределении встроенного метода. (Аналогично тому, как работает неоднозначное предупреждение)
Если нет лучшего способа сохранить флаг, возможно, снова используя новую структуру методов / функций в julia 0.5

Теперь еще проще указать это в ответе, и это поведение, возможно, следует задокументировать для карты?

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

Это все еще ванильный № 265. Думаю, вы оцените, что это скоро будет исправлено в v0.6-dev :)

@vtjnash есть ли компромисс между производительностью во время выполнения или памятью?

для этого требуется немного памяти (140 МБ -> 170 МБ), но не должно сильно влиять на производительность (компиляция или время выполнения). И я еще особо не пытался оптимизировать.

Демоверсии пока забавные:

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

Причина, по которой не возвращаются 2, 3, 4, связана с порядком, в котором происходит компиляция g и выполнение, которое переопределяет f ?

Это примерно верно. Обратите внимание, что семантика языка не определяет компиляцию, поэтому правильнее сказать, что порядок, в котором интерпретируется g зависит от времени, когда переопределение f становится видимым для интерпретатора

хорошо, вот еще одна забавная небольшая демонстрация, переопределяющая примитив + чтобы подсчитать, сколько он используется:

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

Этот обман: я не думаю, что + вообще используется Base, поэтому перекомпилировать нечего. Переопределите реальную функцию, например svd , которая, как вы знаете, должна вызываться как минимум 100 раз, прежде чем появится REPL. Тогда мы будем впечатлены.

Возможности живой реинструментации кода для профилирования / трассировки просто поражают меня.

Конечно. И вот я уже собирался купить новую клавиатуру, потому что последовательность Ctrl-D; julia<Enter> практически изношена. Похоже, мне и не пришлось бы.

Может показаться немного запутанным помещать примечания к выпуску, но следует где-то отметить, что понимания теперь являются синтаксисом для collect -ing a Generator (в отличие от чего-то, может быть, на уровне синтаксического анализа в 0.4? - @which там не работает)? Этот пример, аналогичный приведенному выше vchuravy, представляет собой небольшую ошибку, даже если учесть, что каждая функция теперь является типом в 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

не совсем так?

полезно для исправления # 265

Это действительно исправлено 😲

Планируется ли по-прежнему переносить это решение на версию 0.5.x?

Нет.

@ rapus95 , это очень инвазивное изменение, и неправильное его решение может дестабилизировать julia для многих пользователей. Поскольку основная цель выпусков - обеспечить большую стабильность, чем следование мастеру, гораздо лучше иметь его в версии 0.6. (Вы всегда можете следовать за мастером, если хотите, чтобы он был раньше, чем версия 0.6.)

Я так рад видеть прогресс в этом вопросе! Было бы замечательно, если бы это также позволило компилятору оптимизировать случай, описанный в https://groups.google.com/forum/#!topic/julia -users / OBs0fmNmjCU.

Вау. Какой замечательный момент!

Привет, ребята!

Есть ли возможность перенести это исправление на серию 0.5? Или работает только в том, что станет 0.6?

Это критическое изменение, оно будет доступно только в версии 0.6.

Трудно переоценить, насколько это круто, когда это исправлено. От старых привычек трудно избавиться, поэтому я иногда перезапускаю и / или перестраиваю julia без необходимости, но когда я вспоминаю, это полностью меняет правила игры для устранения проблем.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

iamed2 picture iamed2  ·  3Комментарии

wilburtownsend picture wilburtownsend  ·  3Комментарии

musm picture musm  ·  3Комментарии

sbromberger picture sbromberger  ·  3Комментарии

manor picture manor  ·  3Комментарии