fun(n :: Integer)和fun(n :: T)之间有什么区别,其中T<:整数在性能/代码生成中?

时间:2018-05-02 14:40:39

标签: performance julia multiple-dispatch

在Julia中,当函数适用于fun(n::T) where T<:Integer的所有子类型时,我经常会看到类似Integer的代码。但有时候,我也看到fun(n::Integer),有些指南认为它与上述相同,而其他人则认为效率较低,因为朱莉娅并不专注于特定的子类型,除非子类型T是明确的提到。

后一种形式显然更方便,如果可能,我希望能够使用它,但这两种形式是否相同?如果没有,它们之间的实际差异是什么?

2 个答案:

答案 0 :(得分:5)

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})非常不同。这是常见的参数不变性问题(docsanother SO question关于它。)
  • f(::Type)将为所有DataType生成一个专门化。由于类型对Julia非常重要,因此Type类型本身很特殊,并允许参数化Type{Integer}等参数化,以允许您指定 Integer类型。您可以使用f(::Type{T}) where T<:Integer要求Julia专注于它作为参数获取的Type的确切类型,允许Integer或其任何子类型。

答案 1 :(得分:4)

两个定义都是等价的。通常,只有当您需要在代码中直接使用特定类型fun(n::Integer)时,才会使用fun(n::T) where T<:Integer表单并应用T。例如,考虑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用户 - 问题是如何说服自己这是按预期工作的。有以下方法:

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

一个很好的情节解释了Julia对你的代码所做的事情http://slides.com/valentinchuravy/julia-parallelism#/1/1(我还向所有Julia用户推荐整个演示文稿 - 非常棒)。并且您可以看到,在降低AST之后,Julia应用类型推断步骤来专门化LLVM codegen步骤之前的函数调用。

您可以提示Julia编译器以避免专门化。这是使用Julia 0.7上的@nospecialize宏完成的(虽然它只是一个提示)。