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&lt;: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&lt;:Integerf(n::Integer) 的行为完全相同,除了前一个方法的名称 T 已经在其主体中定义。当然,在后一种情况下,您可以显式分配T = typeof(n),它将在编译时计算。

不过,在其他一些情况下,使用这样的 TypeVar 至关重要,可能值得一提:

f(::ArrayT) where T&lt;:Integer 确实与 f(::ArrayInteger) 有很大不同。这是常见的参数不变性问题(docs 和 another SO question 关于它)。 f(::Type) 将为所有 DataTypes 生成 一个 特化。因为类型对 Julia 来说非常重要,所以 Type 类型本身是特殊的,它允许像 TypeInteger 这样的参数化以允许您指定 just Integer 类型。您可以使用 f(::TypeT) where T&lt;:Integer 要求 Julia 专注于作为参数获得的 Type 的确切类型,允许 Integer 或其任何子类型。

【讨论】:

【参考方案2】:

这两个定义是等价的。通常,您将使用fun(n::Integer) 表单并仅在您需要在代码中直接使用特定类型T 时应用fun(n::T) where T&lt;: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-)}}

JavaScript 闭包

Javascript思考题(持续更新...)

递归练习1:斐波那契数列

c语言用fun函数求n以内的素数,求n=20的值?