【问题标题】:on the impulse to vectorise all the things冲动地矢量化所有的东西
【发布时间】:2018-09-21 21:35:30
【问题描述】:

我从多年的 R 和 Matlab 来到 julia,其中矢量化代码是性能的关键,并影响了我的思维。现在,阅读 S. Johnson 的这篇 wonderful blog post 'more dots',我确信 julia 的句法循环融合是该语言的一个很棒的特性,但是在移植我之前的向量化代码时它让我有点困惑。

将一些函数向量化到几个参数上是不是很糟糕/糟糕的形式? - 我应该改为编写所有例程的“标量”版本并调用点版本(或广播/map) 当我想遍历一个数组时?

为了论证起见,假设我写了以下内容

function sphere_vol(r)
    return 4/3*pi*r^3
end

然后在另一个函数中使用它,

function density(N, r)
    V = sphere_vol(r)
    return N/V
end

等等(许多函数相互调用)。我的直觉(来自其他语言)是将大多数函数定义为矢量化只要方便,如sphere_vol(r) = 4/3*pi*r.^3(微不足道的变化)。如果我可以自动调用sphere_vol.(r::Array)(或map(sphere_vol, r::Array)),这样做有什么意义吗?对于单参数函数,这不是一个重要的决定,但对于多个参数,它可以显着改变实现:我可以定义density(N, r) 的矢量化版本,通过rN 或两者(返回矩阵)进行矢量化,但以这种方式编写并不那么简单(需要通过sphere_vol.() 和@987654331 在内部调用广播@)。在 R 和 Matlab 中,我根据具体情况做出了这个选择,并考虑到了这种折衷:

  1. 矢量化(过去)更方便、更高效:我可以调用一次density(N::Vector, r::Vector) 并获得完整的值数组。

  2. 编写在多个参数上的矢量化函数很快就会变得繁琐和/或难以管理(通过一些技巧通常可以使用 2 个参数);特别是当返回值不是标量时。

在julia中实现一个新功能的时候不知道怎么做判断调用。

如果说在 julia 中我最好像上面那样编写 only“标量”版本,是否正确?或者,如果对某些参数进行向量化(例如调用 Fortran 例程的特殊函数,如 Bessel),某些例程是否会更有效? 我猜想找到一个微妙的平衡点(也根据口味/风格),但对性能的影响比 R 或 Matlab 小得多。

【问题讨论】:

    标签: julia vectorization broadcasting


    【解决方案1】:

    这是个好问题。对此我只能提供我个人的看法。

    我在 Julia 中编写代码的方式包括两个步骤:

    1. 编写一个紧凑的向量化(或矩阵化)函数版本:

      • 这通常很快就能完成,因为它与纸上的数学推导相匹配
      • 不易出错,您很少会引入索引错误
      • 这也很方便,因为正如您所指出的,一些内置函数期望数组只将它们转发到 LAPACK、BLAS、FFTW 等。
    2. 在我有一个工作(和测试)的实现之后,我有时将它去矢量化:

      • 例如,如果函数在一个循环中被多次调用,而矢量化版本分配的内存不恰当,则完全不使用数组,而只是显式循环索引,就有提高性能的空间。

    再说一次,这只是我目前的看法,也许我将来会改变主意?目前这对我来说效果很好,因为我正处于项目的原型设计阶段。

    更进一步,我们常说 Julia 解决了所谓的两种语言问题。您可以开始天真并构建非常复杂的 API。以后,如果你在设计方面没有严重失败,总有办法让事情飞起来。

    【讨论】:

      【解决方案2】:

      Julia 编译器可以将任何函数内联到生成的代码中。

      在您的示例中,我将实现标量版本 sphere_vol(r::Real) = 4/3*pi*r^3 并继续嵌套函数调用,如下所述:density(N::Real, r::Real) = N / sphere_vol(r)

      编译器可能会将sphere_vol 拉入其已编译的density 实现中。

      广播方法也是如此。如果需要编译多个球体的密度:

      function f(x, y, z) ... stuff ... vols .= sphere_vol.(rs) ... more stuff ... end

      编译后的版本可能会将标量方法烘焙到广播中。

      编写最容易检查和维护的代码。使用类型注释。余下的事由 Julia 负责。

      【讨论】:

      • 很有趣,谢谢。这是否意味着编译器最终会创建巨大的可执行代码,因为它内联了同一代码的多个版本?另外,有多少可以保证(我在您的回答中注意到一些“可能”)?
      • 内联或inline expansion 由大多数编译器在有价值的时候完成。文章 op 指的正是讨论了这样的内联。在关于高阶内联重要性的部分中,它讨论了 Julia 的编译器如何更好地决定何时这样做。编译器避免了巨大的可执行代码。它仅在启发式有用时才内联。据我所知,不能保证内联。如果您想确定,可以使用code_native 查看生成的代码。
      • 我明白了,谢谢。关于Write the code that is easiest to inspect and maintain.,这听起来很合理,但来自不同的语言,我对优雅可能不适合朱莉娅喜欢的风格有偏见。我应该提一下,我已经在 R、C++ 和 Matlab 中编写了完全相同的代码(由于各种原因,结果总是令人失望),所以我真的希望这次能在性能上有所提高,这需要我弄清楚如何在 julia 中有效地编码(不仅仅是得到有效的东西)。
      • 对 - 我的那一行我的意思是,如果您的团队更容易阅读矢量化代码,请使用它,如果您的团队更容易阅读非矢量化代码,请使用它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-15
      • 1970-01-01
      • 2010-09-29
      • 1970-01-01
      • 2023-03-07
      • 2021-10-18
      • 2021-08-05
      相关资源
      最近更新 更多