Есть ли разница между забавой (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) будет генерировать только одну специализацию для всех DataTypes. Потому что типы так важны для Юлии, Type Сам тип является особенным и позволяет параметризацию, как Type{Integer} чтобы позволить вам указать только Integer тип. Ты можешь использовать f(::Type{T}) where T<:Integer требовать, чтобы Юлия специализировалась на точном типе Type это становится аргументом, позволяющим Integer или любые его подтипы.
Другие вопросы по тегам