Есть ли разница между забавой (n::Integer) и забавой (n::T), где T <: целое число в производительности / генерации кода?
В Юлии я чаще всего вижу код, написанный как fun(n::T) where T<:Integer
, когда функция работает для всех подтипов Integer
, Но иногда я также вижу fun(n::Integer)
, что некоторые руководства утверждают, эквивалентно выше, в то время как другие говорят, что это менее эффективно, потому что Джулия не специализируется на конкретном подтипе, если на подтип T явно не ссылаются.
Последняя форма, очевидно, более удобна, и я хотел бы использовать ее, если это возможно, но эквивалентны ли эти две формы? Если нет, то какие практические различия между ними?
2 ответа
Оба определения эквивалентны. Обычно вы будете использовать fun(n::Integer)
формировать и применять fun(n::T) where T<:Integer
только если вам нужно использовать определенный тип T
прямо в вашем коде. Например, рассмотрим следующие определения из базы (все следующие определения также из базы), где он имеет естественное использование:
zero(::Type{T}) where {T<:Number} = convert(T,0)
или же
(+)(x::T, y::T) where {T<:BitInteger} = add_int(x, y)
И даже если вам нужна информация о типе во многих случаях, достаточно использовать typeof
функция. Опять пример определения:
oftype(x, y) = convert(typeof(x), y)
Даже если вы используете параметрический тип, вы часто можете избежать использования where
предложение (которое немного многословно) как в:
median(r::AbstractRange{<:Real}) = mean(r)
потому что вас не волнует фактическое значение параметра в теле функции.
Теперь, если вы являетесь пользователем Julia, как я, вопрос в том, как убедить себя, что это работает, как и ожидалось. Есть следующие методы:
- Вы можете проверить, что одно определение перезаписывает другое в таблице методов (т. е. после оценки обоих определений для этой функции присутствует только один метод);
- Вы можете проверить код, сгенерированный обеими функциями, используя
@code_typed
,@code_warntype
,@code_llvm
или же@code_native
и т.д. и узнайте, что это то же самое - наконец, вы можете сравнить код для производительности, используя
BenchmarkTools
Хороший сюжет, объясняющий, что Джулия делает с вашим кодом, находится здесь http://slides.com/valentinchuravy/julia-parallelism (я также рекомендую всю презентацию любому пользователю Джулии - это отлично). И вы можете видеть на нем, что Джулия после понижения AST применяет шаг вывода типа, чтобы специализировать вызов функции перед шагом кодирования LLVM.
Вы можете намекнуть компилятору Julia, чтобы избежать специализации. Это сделано с помощью @nospecialize
макрос на Юлии 0.7 (это только подсказка).
Да, Bogumił Kamiński прав в своем комментарии: f(n::T) where T<:Integer
а также f(n::Integer)
будет вести себя точно так же, за исключением того, что прежний метод будет иметь имя T
уже определено в его теле. Конечно, в последнем случае вы можете просто явно назначить T = typeof(n)
и это будет вычислено во время компиляции.
Есть несколько других случаев, когда использование TypeVar, как это, крайне важно, хотя, и, вероятно, стоит их вызвать:
f(::Array{T}) where T<:Integer
действительно очень отличается отf(::Array{Integer})
, Это обычная параметрическая инвариантность готча ( документы и еще один SO вопрос по этому поводу).f(::Type)
будет генерировать только одну специализацию для всехDataType
s. Потому что типы так важны для Юлии,Type
Сам тип является особенным и позволяет параметризацию, какType{Integer}
чтобы позволить вам указать толькоInteger
тип. Ты можешь использоватьf(::Type{T}) where T<:Integer
требовать, чтобы Юлия специализировалась на точном типеType
это становится аргументом, позволяющимInteger
или любые его подтипы.