【问题标题】:Runtime polymorphism overhead in JuliaJulia 中的运行时多态性开销
【发布时间】:2016-05-10 16:29:13
【问题描述】:

我了解 Julia 严重依赖即时静态类型派生(基本上所有代码都需要被视为 c++ 模板)。我也明白这意味着在对不同类型的对象使用单一算法时没有运行时开销,只要这些类型在编译时是已知的。

当谈到运行时多态性时,我不太清楚事情是如何工作的。假设我们有以下情况:

abstract Shape

type Circle <: Shape
    radius::Float64
end

type Square <: Shape
    width::Float64
end

dist(x::Circle, y::Circle) = ...
dist(x::Circle, y::Square) = ...
dist(x::Square, y::Circle) = ...
dist(x::Square, y::Square) = ...


s = get_shape()
t = get_shape()
a = dist(s,t)

在这里,get_shape 可以返回圆形或正方形,例如基于用户输入。在 C++ 中,分派将简单地进行虚拟表查找。这在 Julia 中是如何工作的?多重调度背后的机制是什么?它比虚拟表查找贵得多吗?从同一个抽象类型派生SquareCircle 有什么好处,或者这与运行时调度的上下文完全无关?

EDT:在此示例上运行 @code_warntype 给出:

Variables:
  s::Union{Circle,Square}
  t::Union{Circle,Square}

Body:
  begin  # none, line 2:
      s = (Main.get_shape)()::Union{Circle,Square} # none, line 3:
      t = (Main.get_shape)()::Union{Circle,Square} # none, line 4:
      return (Main.dist)(s::Union{Circle,Square},t::Union{Circle,Square})::ASCIIString
  end::ASCIIString

所以编译器并不是完全不知道 s 和 t 的类型。调用dist时,这些知识是否用于加快调度?

【问题讨论】:

  • 如果我不得不在这种情况下猜测自己(编译时类型是复合类型的联合),我会说编译器生成了四个虚拟表(对于四种可能的类型组合)并且随后的运行时调度在速度上与 c++' 相当。听起来对吗?

标签: performance polymorphism julia


【解决方案1】:

当您对同一个函数有多个方法时,julia 通过类型交集(将参数的类型与签名中的类型匹配)使用方法查找来确定要调用的方法。如果可以推断出类型,则可以在编译代码时执行该计算。通过提前进行查找,它不必在运行时执行类型交集,这样可以提供最佳性能。

当类型不可预测时,julia 必须在运行时确定要分派到哪个方法。如果被调用的函数做的工作量很小,这有时会成为运行时的瓶颈。 (当它做大量工作时,查找对于性能来说基本上是无关紧要的)。

对于 julia 来说,这是一个比 OOP 语言稍微复杂的问题,因为正确的方法取决于所有的参数,而不仅仅是第一个。

【讨论】:

  • 在这种情况下它目前在做什么?例如,如果它知道只有两种可能的类型,并且所有其他类型都是已知的,那么它是否应该能够进行一次简单的比较,然后分支到针对一种或另一种优化的代码,几乎没有额外的开销,但我认为目前情况并非如此。在这种情况下,有 4 种可能的类型组合,它应该能够通过两次比较来选择优化方法。
  • ScottJones 准确地谈到了我感兴趣的实现细节类型,以便在性能权衡方面做出明智的决定。如果 Julia 必须在调度之前执行复杂的类型识别,在我看来 get_shape 应该返回一个与返回类型相对应的枚举值。然后我可以在嵌套的if(2x2 分支)块中使用这种类型的 id 来手动进行廉价的调度。但是,我仍然希望 Julia JIT 编译器能够代替我创建这样的 if 块。有可能发生这种情况吗?
  • 我认为 Julia 编译器目前不会创建这样的 if 块,但总有一天它会创建这样的块似乎是合理的,至少在案例数量有限的情况下。但不是返回枚举值,您可以在 if 块中使用 isa 检查类型。
  • 我不确定。这肯定是摆在桌面上的,也许其中大部分已经实施,但我认为它还没有完全到位。相关性能基准发布在groups.google.com/d/msg/julia-users/jUMu9A3QKQQ/qjgVWr7vAwAJ
  • 另一个类似的性能问题是当你有一个部分参数化的类型时如何处理分派,比如一个类型有 3 个参数,但在方法的参数中只指定了第一个参数。它是否必须进行运行时调度?
【解决方案2】:

我知道这是旧的,但如果有人对数字感兴趣,我发现了一点 benchmark。事实证明,目前(1.1)使用抽象类型的运行时多态性非常慢(30x vs static dispatch,6x vs C++ virtual function calls)。

【讨论】:

  • 当一个人在搜索有关某个主题的内容并点击指向他们自己写/说的内容的链接时,这有多有趣?我很久以前写过那个代码:)要考虑的一件事是,有一个由有限数量的不同类型组成的数组(我不知道限制,但我怀疑它不是很高),其元素类型是 Union of这种类型,有效地将调度开销减少到与 C++ 虚拟调用大致相同(这是 Julia 测试的最后一次)。
猜你喜欢
  • 2013-06-08
  • 2011-05-05
  • 2014-01-19
  • 2019-01-16
  • 2011-01-10
  • 1970-01-01
  • 2015-11-25
  • 2013-02-22
相关资源
最近更新 更多