fun(n::Integer) 和 fun(n::T) 在性能/代码生成中 T<:Integer 之间有区别吗?
Posted
技术标签:
【中文标题】fun(n::Integer) 和 fun(n::T) 在性能/代码生成中 T<:Integer 之间有区别吗?【英文标题】:Is there a difference between fun(n::Integer) and fun(n::T) where T<:Integer in performance/code generation? 【发布时间】:2018-10-12 17:21:18 【问题描述】:在 Julia 中,我最常看到编写为 fun(n::T) where T<:Integer
的代码,该函数适用于 Integer
的所有子类型。但有时,我也看到fun(n::Integer)
,一些指南声称它与上述内容等价,而另一些人则说它的效率较低,因为 Julia 不专注于特定子类型,除非明确引用子类型 T。
后一种形式显然更方便,如果可能的话,我希望能够使用它,但是这两种形式是等价的吗?如果不是,它们之间的实际区别是什么?
【问题讨论】:
AFAIK 他们是等价的。我知道的建议是使用fun(n::Integer)
,除非你在函数体的某个地方需要T
。这正是docs.julialang.org/en/latest/manual/methods/#Defining-Methods-1 在f(x::Number, y::Number)
示例中推荐的内容。如果您检查fun
的方法,您可以看到,如果您运行这两个定义,它们会相互覆盖。您还可以通过传递不同类型的参数来检查两种表单是否使用@code_warntype
或@code_native
生成完全相同的代码。
它们似乎确实生成了相同的代码,我现在看到 Julia 源代码中的代码仅在定义或签名的另一部分使用子类型时才使用 T
。 (x::T, y::T)
,否则他们直接使用超类型。如果仅在签名中提到超类型,则 Julia 的编译器尽管很聪明,但不会费心生成特定于子类型的代码,这是一个有点疯狂的想法——我相信是一些 Youtube 初学者教程提到了它,我很高兴这不是真的。您能否添加您的评论作为答案?
偶然的机会,我进一步证实了这一点:an answer from Jeff Bezanson 本人认为“那些完全一样”。而现在 Julia 足够聪明,可以用第二个定义替换第一个定义,所以 f
最后只有一个方法。
【参考方案1】:
是的 Bogumił Kamiński 在他的评论中是正确的:f(n::T) where T<:Integer
和 f(n::Integer)
的行为完全相同,除了前一个方法的名称 T
已经在其主体中定义。当然,在后一种情况下,您可以显式分配T = typeof(n)
,它将在编译时计算。
不过,在其他一些情况下,使用这样的 TypeVar 至关重要,可能值得一提:
f(::ArrayT) where T<:Integer
确实与 f(::ArrayInteger)
有很大不同。这是常见的参数不变性问题(docs 和 another SO question 关于它)。
f(::Type)
将为所有 DataType
s 生成 一个 特化。因为类型对 Julia 来说非常重要,所以 Type
类型本身是特殊的,它允许像 TypeInteger
这样的参数化以允许您指定 just Integer
类型。您可以使用 f(::TypeT) where T<:Integer
要求 Julia 专注于作为参数获得的 Type
的确切类型,允许 Integer
或其任何子类型。
【讨论】:
【参考方案2】:这两个定义是等价的。通常,您将使用fun(n::Integer)
表单并仅在您需要在代码中直接使用特定类型T
时应用fun(n::T) where T<:Integer
。例如,考虑 Base 中的以下定义(以下所有定义也来自 Base),其中它具有自然用途:
zero(::TypeT) 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#/1/1 有一个很好的情节来解释 Julia 对您的代码所做的事情(我还向任何 Julia 用户推荐整个演示文稿 - 它非常棒)。您可以在上面看到,降低 AST 后的 Julia 在 LLVM 代码生成步骤之前应用类型推断步骤来专门化函数调用。
您可以提示 Julia 编译器以避免专门化。这是在 Julia 0.7 上使用 @nospecialize
宏完成的(但这只是一个提示)。
【讨论】:
以上是关于fun(n::Integer) 和 fun(n::T) 在性能/代码生成中 T<:Integer 之间有区别吗?的主要内容,如果未能解决你的问题,请参考以下文章
请用c语言编写一个函数fun功能是:计算n门课程的平均分,计算结果作为函数值返回
int fun(int n){switch(n){case 0: return 0;case 1: return 1;case 2: return 1;default:return fun(n-)}}