【问题标题】:Julia memoized code for Fuss-Catalan numbersFuss-Catalan 数字的 Julia 记忆代码
【发布时间】:2017-07-29 22:09:04
【问题描述】:

从 Fuss-Catalan 系列 C{4}_n(请参阅整数序列在线百科全书OEIS A002293),我想使用记忆法计算第 n 项。我在下面编写的代码有效,但在我的笔记本电脑上 n=200 时需要大约 43 秒。有没有办法进一步加快速度?

numterms = 20
C4 = Array{BigInt}(numterms+1) # memoization dictionary
fill!(C4,-1) # -1 implies not yet computed
C4[1] = 1 # Base case for n=0, C[n+1] provides nth term

function catalan4(n,C)
    C[n+1] == -1 || return C[n+1]
    sum1 = convert(BigInt,0)
    for i in 1:n
       sum2 = convert(BigInt,0)
       for j in 1:(n-i+1)
            sum3 = convert(BigInt,0)
            for k in 1:(n-i-j+2)
                sum3+= catalan4(k-1,C)*catalan4(n-i-j-k+2,C)
            end
            sum2 +=  catalan4(j-1,C)*sum3          
       end
       sum1 +=  catalan4(i-1,C)*sum2 
    end
    C[n+1] = sum1    
    return sum1
end 

for i in 1:numterms
    println(i,"\t",catalan4(i,C4))
end    

这按预期提供:

1   1
2   4
3   22
4   140
5   969
6   7084
7   53820
8   420732
9   3362260
10  27343888
11  225568798
12  1882933364
13  15875338990
14  134993766600
15  1156393243320
16  9969937491420
17  86445222719724
18  753310723010608
19  6594154339031800
20  57956002331347120

谢谢!

【问题讨论】:

  • 虽然我没有对你的代码进行基准测试,但我猜最重要的是将C 声明为const。避免使用可变的全局变量。
  • 我在 C 之前添加了 const 关键字 - 不幸的是并没有什么不同。
  • 啊,我明白了,它是作为参数传递的。
  • 这些数字似乎有明确的公式?
  • 是的,确实有一个公式可以计算树无限长时的节点排列数。然而,当树被限制在给定的深度时,稍微复杂一点的递归就成立了,它似乎没有封闭的形式。我正在尝试使用这个更简单的示例来学习如何加快受限树案例的记忆。

标签: algorithm julia memoization


【解决方案1】:

所以,在上面 Stefan 的回答之后 - 我尝试了几件事。为了查看cuplrit 是否真的是 Julia 的 BigInt,我在上述代码的 C 版本中尝试了 GMP mpz_t 整数。事实上,当 n=200 时,它快了大约 10 倍。但是,有时我需要 n=4096,而 GMP 和 C 并不能真正帮助我减少计算时间。然后我注意到内部循环也被反复重新计算。所以我将两个内部和记下来如下,现在代码给了我 n=200,Julia 和 BigInts 的评估时间为 0.06 秒(低于 43 秒)!

numterms = 200
C4 = Array{BigInt}(numterms+1) # memoization dictionary
fill!(C4,-1) # -1 implies not yet computed
C4[1] = 1 # Base case for n=0, C[n+1] provides nth term

# memoize partial sums as well
sum2store = similar(C4)
sum3store = similar(C4)
fill!(sum2store, -1)
fill!(sum3store, -1)

function catalan4(n,C)
    C[n+1] == -1 || return C[n+1]
    sum1 = convert(BigInt,0)
    for i in 1:n
        if sum2store[n-i+1] == -1
            sum2 = convert(BigInt,0)
            for j in 1:(n-i+1)
                if sum3store[n-i-j+2] == -1
                    sum3 = convert(BigInt,0)
                    for k in 1:(n-i-j+2)
                        sum3+= catalan4(k-1,C)*catalan4(n-i-j-k+2,C)
                    end
                    sum3store[n-i-j+2] = sum3
                end
                sum2 +=  catalan4(j-1,C)*sum3store[n-i-j+2]
            end
            sum2store[n-i+1] = sum2
        end
        sum1 +=  catalan4(i-1,C)*sum2store[n-i+1]
    end
    C[n+1] = sum1
    return sum1
end

现在我不需要 Julia 的 BigInt 来快速 - 我很高兴我可以使用它。

【讨论】:

  • 太棒了!我很高兴你让它工作得更快。最快的计算是你不做的:)
【解决方案2】:

我怀疑糟糕的BigInt 性能是这里的罪魁祸首。您可能想尝试Nemo,它将Flint library 用于任意精度整数,据我所知,它的效率要高得多。如果您想留在标准 Julia 中(Nemo 是基于 Julia 的,但在某些语言语义上与 Julia 不同,afaik - 它是一个计算机代数系统)并且您的数字被限制为小于 2^127,那么您可以尝试使用Int128 而不是 - 这些可以堆栈分配并保存在寄存器中,与 BigInts 不同,后者必须是堆分配的,并且 LLVM 不知道如何推理(它不能转换创建新的 BigInts变成现有的突变)。创建自定义 Int256Int512 类型可能并不难,它们使用两个/四个 Int128 值来进行算术运算,尤其是在您只需要支持加法和乘法的情况下。

【讨论】:

  • 我想就是这样 - 但如果没有看到示例,我认为我不知道如何做到这一点。您能否指出如何创建一个由 m Int128 值组成的新位类型,并在其上定义 * 操作?
  • 这是一个例子:github.com/simonbyrne/DoubleDouble.jl/blob/master/src/…;这可能比使用一对 UInt128 值来表示 UInt256 更复杂。不过,您实际上不必自己滚动。基础 Julia 中的 BigInts 应该更高效——我们希望它们如此,但恐怕这不是首要任务。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-19
  • 1970-01-01
  • 2019-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-12
相关资源
最近更新 更多