fun(n::Integer) 和 fun(n::T) where T<:Integer in performance/code 代之间有区别吗?

Is there a difference between fun(n::Integer) and fun(n::T) where T<:Integer in performance/code generation?

在 Julia 中,当函数适用于 Integer 的所有子类型时,我最常看到的代码写成 fun(n::T) where T<:Integer。但有时,我也看到 fun(n::Integer),一些指南声称它等同于上面的内容,而其他人则说它效率较低,因为除非明确引用子类型 T,否则 Julia 不会专注于特定的子类型。

后一种形式显然更方便,如果可能的话我希望能够使用,但这两种形式是否等价?如果不是,它们之间的实际区别是什么?

是的 Bogumił Kamiński 在他的评论中是正确的:f(n::T) where T<:Integerf(n::Integer) 的行为完全相同,除了前一个方法的名称为 T已经在其主体中定义。当然,在后一种情况下,您可以显式分配 T = typeof(n) ,它将在编译时计算。

不过,在其他一些情况下,像这样使用 TypeVar 是至关重要的,可能值得将它们指出来:

  • f(::Array{T}) where T<:Integer确实和f(::Array{Integer})有很大的不同。这是常见的参数不变性问题(docs and another SO question)。
  • f(::Type) 只会为所有 DataType 生成 一个 专业化。因为类型对 Julia 来说非常重要,所以 Type 类型本身很特殊,它允许像 Type{Integer} 这样的参数化,允许您指定 只是 Integer 类型.您可以使用 f(::Type{T}) where T<:Integer 要求 Julia 专注于作为参数获取的 Type 的确切类型,允许 Integer 或其任何子类型。

这两个定义是等价的。通常,只有当您需要在代码中直接使用特定类型 T 时,您才会使用 fun(n::Integer) 形式并应用 fun(n::T) where T<:Integer。例如,考虑以下来自 Base 的定义(所有以下定义也来自 Base),它具有自然用途:

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 用户 - 问题是如何让自己相信这会按预期工作。有以下方法:

  • 您可以在方法 table 中检查一个定义是否覆盖了另一个(即在评估两个定义之后,此函数只存在一个方法);
  • 您可以使用@code_typed@code_warntype@code_llvm@code_native等检查两个函数生成的代码,发现它们是相同的
  • 最后,您可以使用 BenchmarkTools
  • 对代码进行性能基准测试

这里有一个解释 Julia 对您的代码做了什么的好图 http://slides.com/valentinchuravy/julia-parallelism#/1/1(我也向所有 Julia 用户推荐整个演示文稿 - 它非常棒)。你可以在上面看到,在降低 AST 之后,Julia 在 LLVM 代码生成步骤之前应用了类型推断步骤来专门化函数调用。

您可以提示 Julia 编译器避免特化。这是在 Julia 0.7 上使用 @nospecialize 宏完成的(不过这只是一个提示)。