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<:Integer
和 f(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
宏完成的(不过这只是一个提示)。
在 Julia 中,当函数适用于 Integer
的所有子类型时,我最常看到的代码写成 fun(n::T) where T<:Integer
。但有时,我也看到 fun(n::Integer)
,一些指南声称它等同于上面的内容,而其他人则说它效率较低,因为除非明确引用子类型 T,否则 Julia 不会专注于特定的子类型。
后一种形式显然更方便,如果可能的话我希望能够使用,但这两种形式是否等价?如果不是,它们之间的实际区别是什么?
是的 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})
有很大的不同。这是常见的参数不变性问题(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
宏完成的(不过这只是一个提示)。