если я определяю функцию 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
Я считаю, что это потому, что код для d2()
генерируется, когда функция определена, поэтому в это время разрешается метод d()
. Как вы отметили, вы можете переопределить d2()
виде, в котором он был изначально определен, чтобы заставить его работать должным образом. В идеале мы должны автоматически переопределять все, что зависит от d()
когда оно изменяется таким образом, но у нас нет никаких метаданных, которые позволяли бы это. Думаю, мы могли бы поместить предупреждение об этом в руководство. Лучше бы все же исправить, но я думаю, что это немного сложно сделать: - /
Я надеялся, что этого никто не заметит, но, думаю, это было нереально для меня :)
Это веселее, чем это; он действительно позволяет вам увидеть JIT в действии, поскольку он не разрешается до выполнения:
julia> function f2()
a
end
julia> function f1()
f2()
end
julia> function f2()
2
end
julia> f1()
2
Однако я боюсь, что это может привести к некоторым неожиданным результатам на REPL, хотя я действительно не знаю, как с этим справиться. Хотя я использовал ошибку, чтобы показать разницу в драматической манере, она также могла произойти, если бы я пытался увидеть эффект изменения внутреннего уравнения или константы!
Существует два типа местоположений, которые могут потребоваться обновить при изменении метода:
Первый можно обновить, просто изменив тело функции: его можно изменить на месте, или вызывающие сайты могут быть активно обновлены для вызова нового местоположения, или старое тело функции можно заменить заглушкой, которая переходит к новой версии , при желании исправление вызывающего сайта. Для встраивания нам нужно отслеживать вызывающих, которые встроили функцию, и повторно 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))))
будет ли он работать, чтобы индексировать методы функций (или только сами типы функций / объекты) с числом, которое меняется каждый раз, когда что-либо назначается, и, таким образом, получая возможность проверить, хранится ли число с скомпилированной (вызывающей) функцией (т.е. с использованием новой структуры функций, сохраняя эти числа как поле и т. д.) все еще совпадает с тем, который является "текущим" для функции, которая была встроена (вызвана)?
(конечно, без большой потери производительности)
А в случае изменения подумайте о том, чтобы скомпилировать новую версию или завершить явным вызовом.
Это переместит влияние на производительность во время выполнения (а это будет повсюду), что нежелательно. Что необходимо, так это отслеживать сеть зависимостей и перекомпилировать все, на что может повлиять частичное переопределение метода. Вероятно, это выполнимо, это просто массовая боль.
Я не знаю, можно ли было бы легко распространить решение этой проблемы на макросы, но если да, то было бы неплохо.
Поэтому лучше пойти другим путем и сохранить все методы, которые встроили функцию, и при изменении перекомпилировать их. (конечно, проверка текущего исполнения и т. д.)
Вот самые серьезные проблемы:
Пока он работает над решением проблемы, мне любопытно, возможно ли и неплохо добавить флаг к каждой установленной функции, если (и только если) данная функция использовалась одним из верхних способов ( 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 без необходимости, но когда я вспоминаю, это полностью меняет правила игры для устранения проблем.
Самый полезный комментарий
Вау. Какой замечательный момент!