在朱莉娅结合repmat和transpose

时间:2015-03-02 04:35:57

标签: vectorization julia

我正在将一些代码从R移植到julia以熟悉该语言,我发现一些模式无法顺利翻译。考虑以下函数,

# Ricatti-Bessel and derivatives up to nmax, vectorised over x
function rb(x, nmax)

  n = 1:nmax
  nu = 0.5 + [0, n]

  bj = hcat([besselj(nu, _x) for _x in x]...).'
  # ^ first question ^ 
  sq = repmat(sqrt(pi/2*x), 1, nmax+1)

  bj .*= sq
  xm = repmat(x, 1, nmax)
  nm = repmat(n', length(x), 1)
  # ^ second question ^ 

  dpsi = bj[:,n] - nm .* bj[:,n+1] ./ xm 
  psi = bj[:,n+1]
  return psi, dpsi # it'd be nice to return a "named list" instead

end
# rb(1:5,3)
  • 第一个问题:这是使用besselj()获取具有nmax列和长度(x)行的矩阵的最佳方法吗?在找到一个有效的模式之前,我不得不挠头一段时间。

  • 第二个问题:我发现自己经常不得不在repmat内和/或外调换对象,是否可以指定输出大小和填充方向(行方式或逐方方式)?

也许我对整个事情采取了错误的方法:我习惯于使用矢量化函数(在R中,以及Matlab的旧记忆中),因为它们通常是线性代数快速例程的最短路径。 将x保持为标量是否更有意义,并且仅在最高级别循环?我担心通过这样做,我将无法利用BLAS等的快速矩阵/向量函数,并且基本上在julia中重写它们,更不用说明显的可读性损失了。 我应该强调我对最佳性能感兴趣,因为对于x的许多值,这个函数将被内部调用很多次。

1 个答案:

答案 0 :(得分:5)

对于你的第一个问题,我将用以下矩阵理解来替换它:

nu = (0:nmax)+0.5
bj = [besselj(i,j) for j in x, i in nu]

对于你的第二个,我认为在Julia编写高性能代码的一个好原则是避免不必要的分配(当然,阅读performance tips!)Julia生成非常快速的指令 - 这就是为什么{ {1}}循环是完全正常的,除了线性代数(例如矩阵乘法)之外,对矢量化除了实际上并不重要。它做得不好的是避免分配不必要的内存(像for这样的临时矩阵)。我用以下

替换了sq / bj
sq

这很好,因为它只有一个分配,在我们之前(假设我们从Q1的答案开始):

  1. 分配nu = (0:nmax)+0.5 bj = [besselj(i,j)*sqrt(pi/2*j) for j in x, i in nu]
  2. 分配bj
  3. 分配sq并将bj.*sq重新绑定到此新内存
  4. (请注意,bj 就地操作!)

    您对“命名列表”的请求现在可能最好通过为此函数返回.*=来实现(这根本不是一项昂贵的操作,并且在例如Julia的中很常见)矩阵分解代码,需要返回多个值)。或者,您可以返回type,但这并不是惯用的。

    对于Dict行,我会给你两个选择。第一个是另一个矩阵理解:

    dpsi

    ,另一个是loop-y:

    dpsi = [ bj[i,j] - j * bj[i,j+1] / i 
               for i in 1:length(x), j in 1:nmax]
    

    在这两种情况下,我都在避免分配临时工。同样,您的原始分配具有以下分配:

    1. 适用于dpsi = zeros(length(x),nmax) for i in 1:length(x), j in 1:nmax dpsi[i,j] = bj[i,j] - j * bj[i,j+1] / i end
    2. 适用于xm
    3. 对于nm(这将更改为0.4中的视图)
    4. bj[:,n](同上)
    5. 适用于bj[:,n+1]
    6. 适用于nm .* bj[:,n+1]
    7. 对于整个结果
    8. 我提出的两个版本只有一个分配,可能更接近问题的原始数学陈述

      我的最终版本是

      nm .* bj[:,n+1] ./ xm

      我对function myrb(x, nmax) bj = [ besselj(i,j)*sqrt(pi/2*j) for j in x, i in (0:nmax)+0.5] dpsi = [ bj[i,j] - j * bj[i,j+1] / i for i in 1:length(x), j in 1:nmax] psi = bj[:,2:nmax+1] return psi, dpsi end 一点都不太了解,但我猜它是整个事情中最慢的部分,所以所有这些在速度方面可能并不重要案件。对这个微观案例进行基准测试表明了这一点:

      besselj

      您可以使用分析器确认这一点(尽管我必须使用更大的输入:)

      # original
      elapsed time: 9.7578e-5 seconds (7176 bytes allocated)
      elapsed time: 7.2644e-5 seconds (7176 bytes allocated)
      elapsed time: 7.5709e-5 seconds (7176 bytes allocated)
      # revised
      elapsed time: 2.7536e-5 seconds (728 bytes allocated)
      elapsed time: 2.7097e-5 seconds (728 bytes allocated)
      elapsed time: 1.6601e-5 seconds (728 bytes allocated)
      

      在我的机器上,该函数收集了429个样本,其中426个位于Julia内的@profile myrb(1:500,300) Profile.print() 文件中,2个位于bessel.jl,1个位于dpsi。< / p>